棟別設置

This commit is contained in:
koko 2025-07-10 10:34:52 +08:00
parent 14e3f9ea4a
commit f0e9d7fda6
7 changed files with 91 additions and 68 deletions

View File

@ -1,4 +1,4 @@
VITE_API_BASEURL = "https://ibms-cvilux-api.production.mjmtech.com.tw" VITE_API_BASEURL = "https://ibms-cvilux-demo-api.production.mjmtech.com.tw"
VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088" VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net" VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist" VITE_FORGE_BASEURL = "https://cgems.cvilux-group.com:8088/dist"

View File

@ -1,3 +1,3 @@
VITE_API_BASEURL = "http://220.132.206.5:8008" VITE_API_BASEURL = "https://ibms-cvilux-demo-api.production.mjmtech.com.tw"
VITE_FILE_API_BASEURL = "http://220.132.206.5:8085/file" VITE_FILE_API_BASEURL = "https://cgems.cvilux-group.com:8088"
VITE_FORGE_BASEURL = "http://localhost:5173" VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"

View File

@ -6,7 +6,8 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview" "preview": "vite preview",
"build:staging": "vite build --mode staging"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons-vue": "^7.0.1", "@ant-design/icons-vue": "^7.0.1",

View File

@ -6,7 +6,6 @@ const store = useBuildingStore();
const selectBuilding = (bui) => { const selectBuilding = (bui) => {
store.selectedBuilding = bui; // selectedBuildingwatch store.selectedBuilding = bui; // selectedBuildingwatch
localStorage.setItem("CviBuilding", JSON.stringify(bui));
}; };
onMounted(() => { onMounted(() => {
@ -28,14 +27,16 @@ onMounted(() => {
tabindex="0" tabindex="0"
class="dropdown-content w-48 left-8 translate-y-2 z-[1] menu py-3 shadow rounded bg-[#4c625e] border text-center" class="dropdown-content w-48 left-8 translate-y-2 z-[1] menu py-3 shadow rounded bg-[#4c625e] border text-center"
> >
<li <template v-if="store.buildings.length > 0">
class="text-white my-1 text-base cursor-pointer" <li
v-for="bui in store.buildings" class="text-white my-1 text-base cursor-pointer"
:key="bui.building_tag" v-for="bui in store.buildings"
@click="selectBuilding(bui)" :key="bui.building_tag"
> @click="selectBuilding(bui)"
{{ bui.full_name }} >
</li> {{ bui.full_name }}
</li>
</template>
</ul> </ul>
</div> </div>
</template> </template>

View File

@ -45,38 +45,46 @@ const useBuildingStore = defineStore("buildingInfo", () => {
// 獲取所有建築物 // 獲取所有建築物
const fetchBuildings = async () => { const fetchBuildings = async () => {
const res = await getBuildings(); // const res = await getBuildings();
buildings.value = res.data; buildings.value = JSON.parse(localStorage.getItem("CviBuildingList")) || [];
if (res.data.length > 0 && !selectedBuilding.value) { const storedBuilding = JSON.parse(localStorage.getItem("CviBuilding"));
const storedBuilding = JSON.parse(localStorage.getItem("CviBuilding")); if (buildings.value.length > 0) {
selectedBuilding.value = storedBuilding || res.data[0]; // 預設選第一個建築 selectedBuilding.value = storedBuilding || buildings.value[0]; // 預設選第一個建築
} else {
selectedBuilding.value = null; // 如果沒有建築物,清空選擇
} }
}; };
// 獲取樓層資料 // 獲取樓層資料
const fetchFloorList = async (building_guid) => { const fetchFloorList = async (building_guid) => {
const res = await getAssetFloorList(building_guid); const res = await getAssetFloorList(building_guid);
floorList.value = res.data[0]?.floors.map((d) => ({ floorList.value =
...d, res.data[0]?.floors.map((d) => ({
title: d.full_name, ...d,
key: d.floor_guid, title: d.full_name,
})) || []; key: d.floor_guid,
})) || [];
}; };
// 獲取部門資料 // 獲取部門資料
const fetchDepartmentList = async () => { const fetchDepartmentList = async () => {
const res = await getDepartmentList(); const res = await getDepartmentList();
deptList.value = res.data.map((d) => ({ deptList.value =
...d, res.data.map((d) => ({
title: d.name, ...d,
key: d.id, title: d.name,
})) || []; key: d.id,
})) || [];
}; };
// 當 selectedBuilding 改變時,更新 floorList 和 deptList // 當 selectedBuilding 改變時,更新 floorList 和 deptList
watch(selectedBuilding, async (newBuilding) => { watch(selectedBuilding, async (newBuilding) => {
if (newBuilding) { if (newBuilding) {
await Promise.all([fetchFloorList(newBuilding.building_guid), fetchDepartmentList()]); localStorage.setItem("CviBuilding", JSON.stringify(newBuilding))
await Promise.all([
fetchFloorList(newBuilding.building_guid),
fetchDepartmentList(),
]);
} }
}); });

View File

@ -2,38 +2,59 @@ import useGetCookie from "@/hooks/useGetCookie";
import axios from "axios"; import axios from "axios";
const BASEURL = import.meta.env.VITE_API_BASEURL; const BASEURL = import.meta.env.VITE_API_BASEURL;
// --- 請求攔截器的共用邏輯 ---
const requestInterceptor = (config) => {
// 確保 headers 物件存在
if (!config.headers) {
config.headers = {};
}
// 1. 取得並附加最新的 Token
const token = useGetCookie("JWT-Authorization");
if (token) {
// 正確做法:修改屬性,而不是覆蓋整個物件
config.headers.Authorization = `Bearer ${token}`;
}
// 2. 取得並附加選定的建築物 GUID
const storedBuilding = localStorage.getItem("CviBuilding");
if (storedBuilding) {
try {
const buildingObject = JSON.parse(storedBuilding);
if (buildingObject && buildingObject.building_guid) {
// 與後端約定好要用哪個標頭,這裡使用 'X-Building-GUID' 作為範例
config.headers["X-Building-GUID"] = buildingObject.building_guid;
}
} catch (e) {
console.error("解析 localStorage 中的 CviBuilding 失敗:", e);
}
}
return config;
};
const requestErrorInterceptor = (error) => {
return Promise.reject(error);
};
// --- 一般 API 實例 ---
const instance = axios.create({ const instance = axios.create({
baseURL: BASEURL, baseURL: BASEURL,
timeout: -1, timeout: 10000, // 建議設定超時
headers: { Authorization: `Bearer ${useGetCookie("JWT-Authorization")}` }, // 移除靜態 headers
}); });
// Add a request interceptor // 使用共用的攔截器
instance.interceptors.request.use( instance.interceptors.request.use(requestInterceptor, requestErrorInterceptor);
function (config) {
// Do something before request is sent
const token = useGetCookie("JWT-Authorization");
config.headers = {
Authorization: `Bearer ${token}`,
};
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
// Add a response interceptor // Add a response interceptor
instance.interceptors.response.use( instance.interceptors.response.use(
function (response) { function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger // Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data // Do something with response data
const { status, data, headers } = response; const { data } = response;
return { return { ...data };
...data,
};
}, },
function (error) { function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger // Any status codes that falls outside the range of 2xx cause this function to trigger
@ -45,27 +66,16 @@ instance.interceptors.response.use(
} }
); );
// --- 檔案處理 API 實例 ---
export const fileInstance = axios.create({ export const fileInstance = axios.create({
baseURL: BASEURL, baseURL: BASEURL,
timeout: -1, timeout: -1,
headers: { Authorization: `Bearer ${useGetCookie("JWT-Authorization")}` }, // 移除靜態 headers
}); });
// Add a request interceptor // 使用共用的攔截器
fileInstance.interceptors.request.use( fileInstance.interceptors.request.use(requestInterceptor, requestErrorInterceptor);
function (config) {
// Do something before request is sent
const token = useGetCookie("JWT-Authorization");
config.headers = {
Authorization: `Bearer ${token}`,
};
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
// Add a response interceptor // Add a response interceptor
fileInstance.interceptors.response.use( fileInstance.interceptors.response.use(

View File

@ -6,10 +6,12 @@ import * as yup from "yup";
import useFormErrorMessage from "@/hooks/useFormErrorMessage"; import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import useUserInfoStore from "@/stores/useUserInfoStore"; import useUserInfoStore from "@/stores/useUserInfoStore";
import useBuildingStore from "@/stores/useBuildingStore";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n(); const { t } = useI18n();
const { openToast } = inject("app_toast"); const { openToast } = inject("app_toast");
const store = useUserInfoStore(); const store = useUserInfoStore();
const storeBuild = useBuildingStore();
const router = useRouter(); const router = useRouter();
let schema = yup.object({ let schema = yup.object({
@ -35,6 +37,7 @@ const doLogin = async () => {
const res = await Login(value); const res = await Login(value);
if (res.isSuccess) { if (res.isSuccess) {
store.user = res.data; store.user = res.data;
localStorage.setItem("CviBuildingList", JSON.stringify(res.data.buildingIdList));
router.replace({ path: "/dashboard" }); router.replace({ path: "/dashboard" });
} else { } else {
openToast("error", res.msg); openToast("error", res.msg);