Compare commits
7 Commits
main
...
feature/do
Author | SHA1 | Date | |
---|---|---|---|
8e98cce2da | |||
b3f89ab5fe | |||
1aa76ad194 | |||
5f0ec629d0 | |||
ffdf13d156 | |||
ead85a5652 | |||
36e390d300 |
@ -1,4 +0,0 @@
|
|||||||
VITE_API_BASEURL = "https://ibms-Empower-api.production.mjmtech.com.tw"
|
|
||||||
VITE_FILE_API_BASEURL = "https://ibms-Empower.production.mjmtech.com.tw"
|
|
||||||
VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
|
|
||||||
VITE_FORGE_BASEURL = "https://ibms-Empower.production.mjmtech.com.tw/dist"
|
|
@ -1,4 +0,0 @@
|
|||||||
VITE_API_BASEURL = "https://ibms-Empower-api.production.mjmtech.com.tw"
|
|
||||||
VITE_FILE_API_BASEURL = "https://ibms-Empower.production.mjmtech.com.tw"
|
|
||||||
VITE_MQTT_BASEURL = "wss://mqttwss.mjm-staging.developers-homelab.net"
|
|
||||||
VITE_FORGE_BASEURL = "https://ibms-Empower.production.mjmtech.com.tw/dist"
|
|
@ -1,3 +0,0 @@
|
|||||||
VITE_API_BASEURL = "http://220.132.206.5:8008"
|
|
||||||
VITE_FILE_API_BASEURL = "http://220.132.206.5:8085/file"
|
|
||||||
VITE_FORGE_BASEURL = "http://localhost:5173"
|
|
7
Docker/svc.front/.dockerignore
Normal file
7
Docker/svc.front/.dockerignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
Dockerfile
|
||||||
|
docker-compose.yml
|
||||||
|
README.md
|
||||||
|
.vs
|
@ -7,7 +7,7 @@ NET_TRAEFIK=net-traefik_svc
|
|||||||
# Image: org/name
|
# Image: org/name
|
||||||
IMAGE_PROJ_NAME=proj_bims_ils
|
IMAGE_PROJ_NAME=proj_bims_ils
|
||||||
IMAGE_NAME=empower-front
|
IMAGE_NAME=empower-front
|
||||||
TAG_VERSION=0.1.0
|
TAG_VERSION=0.1.7
|
||||||
|
|
||||||
# Remote
|
# Remote
|
||||||
REMOTE_URL=harbor.mjm-staging.developers-homelab.net
|
REMOTE_URL=harbor.mjm-staging.developers-homelab.net
|
||||||
|
43
Dockerfile
Normal file
43
Dockerfile
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# 使用 Node.js 作為基礎映像
|
||||||
|
FROM node:18-alpine AS builder
|
||||||
|
|
||||||
|
# 設定工作目錄
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 複製 package.json 和 package-lock.json (或 yarn.lock) 到工作目錄
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# 安裝依賴
|
||||||
|
RUN npm install --legacy-peer-deps
|
||||||
|
|
||||||
|
# 複製所有檔案到工作目錄
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 清理緩存並重新構建
|
||||||
|
RUN npm cache clean --force
|
||||||
|
RUN rm -rf node_modules
|
||||||
|
RUN npm install --legacy-peer-deps
|
||||||
|
|
||||||
|
# 構建前端應用 (如果需要)
|
||||||
|
RUN npm run build --omit=dev
|
||||||
|
|
||||||
|
# 使用一個更小的映像來提供靜態文件 (例如 Nginx)
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# 將構建好的靜態檔案複製到 Nginx 的預設目錄
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# (可選) 複製自定義 Nginx 設定檔
|
||||||
|
# COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# 暴露 Nginx 預設的 80 端口
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# 2025-10-01 說明
|
||||||
|
LABEL changelog="2025-10-01: 1.fix 修正 draggable 元素的 mousedown 事件綁定 2.修正 nav 使用者無法按出drop down"
|
||||||
|
|
||||||
|
# Nginx 已經預設啟動,所以不需要 CMD 指令
|
||||||
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
RUN chmod +x /docker-entrypoint.sh
|
||||||
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
5
docker-entrypoint.sh
Normal file
5
docker-entrypoint.sh
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo "window.env = { VITE_API_BASEURL: '${VITE_API_BASEURL}', VITE_FILE_API_BASEURL: '${VITE_FILE_API_BASEURL}', VITE_APP_TITLE: '${VITE_APP_TITLE}' };" > /usr/share/nginx/html/env.js
|
||||||
|
sed -i "s|<title>.*</title>|<title>${VITE_APP_TITLE}</title>|g" /usr/share/nginx/html/index.html
|
||||||
|
sed -i "s|<link rel=\"icon\" href=\".*\"|<link rel=\"icon\" href=\"${VITE_FILE_API_BASEURL}/favicon.ico\"|g" /usr/share/nginx/html/index.html
|
||||||
|
exec "$@"
|
@ -10,6 +10,7 @@
|
|||||||
/>
|
/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>新創賦能</title>
|
<title>新創賦能</title>
|
||||||
|
<script src="/env.js"></script>
|
||||||
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
|
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
|
||||||
<!-- <script src="https://code.jquery.com/ui/1.13.3/jquery-ui.js"></script> -->
|
<!-- <script src="https://code.jquery.com/ui/1.13.3/jquery-ui.js"></script> -->
|
||||||
<!-- <script type="text/javascript" src="/requirejs/config.js"></script> -->
|
<!-- <script type="text/javascript" src="/requirejs/config.js"></script> -->
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
DELETE_ACCOUNT_USER_API,
|
DELETE_ACCOUNT_USER_API,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
|
|
||||||
export const getAccountUserList = async (search_condition = {}) => {
|
export const getAccountUserList = async (search_condition = {}) => {
|
||||||
const res = await instance.post(GET_ACCOUNT_USERLIST_API, search_condition);
|
const res = await instance.post(GET_ACCOUNT_USERLIST_API, search_condition);
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
POST_ALERT_MQTT_REFRESH,
|
POST_ALERT_MQTT_REFRESH,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
|
|
||||||
export const getAlertFormId = async (uuid) => {
|
export const getAlertFormId = async (uuid) => {
|
||||||
const res = await instance.post(GET_ALERT_FORMID_API, uuid);
|
const res = await instance.post(GET_ALERT_FORMID_API, uuid);
|
||||||
|
@ -28,7 +28,7 @@ import {
|
|||||||
POST_ASSET_ELEC_SETTING_API,
|
POST_ASSET_ELEC_SETTING_API,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
import { object } from "yup";
|
import { object } from "yup";
|
||||||
|
|
||||||
export const getAssetMainList = async (building_guid) => {
|
export const getAssetMainList = async (building_guid) => {
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
GET_ALL_DEVICE_API,
|
GET_ALL_DEVICE_API,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
|
|
||||||
export const getBuildings = async () => {
|
export const getBuildings = async () => {
|
||||||
const res = await instance.post(GET_BUILDING_API);
|
const res = await instance.post(GET_BUILDING_API);
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
GET_DASHBOARD_ALARMOPERATION_INFO_API,
|
GET_DASHBOARD_ALARMOPERATION_INFO_API,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
|
|
||||||
export const getDashboardInit = async (page_type = "SR") => {
|
export const getDashboardInit = async (page_type = "SR") => {
|
||||||
const res = await instance.post(GET_DASHBOARD_INIT_API, {
|
const res = await instance.post(GET_DASHBOARD_INIT_API, {
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
POST_TIME_ELEC_API,
|
POST_TIME_ELEC_API,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance, { fileInstance } from "@/util/request";
|
import instance, { fileInstance } from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
import downloadExcel from "@/util/downloadExcel";
|
import downloadExcel from "@/util/downloadExcel";
|
||||||
|
|
||||||
export const getRealTimeData = async () => {
|
export const getRealTimeData = async () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import { GET_FORGETOKEN_API, GET_FORGEURN_API } from "./api";
|
import { GET_FORGETOKEN_API, GET_FORGEURN_API } from "./api";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
|
|
||||||
export const getUrn = async () => {
|
export const getUrn = async () => {
|
||||||
const res = await instance.post(GET_FORGEURN_API);
|
const res = await instance.post(GET_FORGEURN_API);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// graph
|
// graph
|
||||||
const BASEURL = import.meta.env.VITE_API_BASEURL;
|
const BASEURL = window.env?.VITE_API_BASEURL;
|
||||||
export const GET_GRAPH_SIDEBAR_API = `/GraphManage/GraphManageTreeList`;
|
export const GET_GRAPH_SIDEBAR_API = `/GraphManage/GraphManageTreeList`;
|
||||||
|
|
||||||
export const UPDATE_GRAPH_SIDEBAR_API = `/GraphManage/EditGraphManageTree`;
|
export const UPDATE_GRAPH_SIDEBAR_API = `/GraphManage/EditGraphManageTree`;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// history
|
// history
|
||||||
const BASEURL = import.meta.env.VITE_API_BASEURL;
|
const BASEURL = window.env?.VITE_API_BASEURL;
|
||||||
export const GET_HISTORY_SIDEBAR_API = `/api/History/GetDeviceInfo`;
|
export const GET_HISTORY_SIDEBAR_API = `/api/History/GetDeviceInfo`;
|
||||||
export const GET_HISTORY_POINT_API = `/api/History/GetAllDevPoi`;
|
export const GET_HISTORY_POINT_API = `/api/History/GetAllDevPoi`;
|
||||||
export const GET_HISTORY_DATA_API = `/api/History/GetHistoryData`;
|
export const GET_HISTORY_DATA_API = `/api/History/GetHistoryData`;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { POST_LOGIN } from "./api";
|
import { POST_LOGIN } from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
|
|
||||||
export async function Login({ account, password }) {
|
export async function Login({ account, password }) {
|
||||||
const res = await instance.post(POST_LOGIN, {
|
const res = await instance.post(POST_LOGIN, {
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
DELETE_OPERATION_COMPANY_API,
|
DELETE_OPERATION_COMPANY_API,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
export const getOperationRecord = async ({
|
export const getOperationRecord = async ({
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
import {
|
import {
|
||||||
POST_SETTING_POINT_API,
|
POST_SETTING_POINT_API,
|
||||||
GET_SETTING_TYPE_API,
|
GET_SETTING_TYPE_API,
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
POST_SET_SAMBA_DIRECTORY,
|
POST_SET_SAMBA_DIRECTORY,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 開關 RTSP
|
* 開關 RTSP
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
POST_MQTT_TOPIC_STOP_API,
|
POST_MQTT_TOPIC_STOP_API,
|
||||||
} from "./api";
|
} from "./api";
|
||||||
import instance from "@/util/request";
|
import instance from "@/util/request";
|
||||||
import apihandler from "@/util/apihandler";
|
import apihandler from "@/util/apiHandler";
|
||||||
|
|
||||||
export const getSystemFloors = async (building_tag, sub_system_tag) => {
|
export const getSystemFloors = async (building_tag, sub_system_tag) => {
|
||||||
const res = await instance.post(GET_SYSTEM_FLOOR_LIST_API, {
|
const res = await instance.post(GET_SYSTEM_FLOOR_LIST_API, {
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import { computed, defineProps, onMounted, ref, watch } from "vue";
|
import { defineProps } from "vue";
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
|
||||||
id名.showModal(): 開啟 modal
|
|
||||||
id名.close(): 關閉 modal
|
|
||||||
詳細請參考 daisyUI
|
|
||||||
------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: String,
|
id: String,
|
||||||
@ -14,52 +8,40 @@ const props = defineProps({
|
|||||||
onCancel: Function,
|
onCancel: Function,
|
||||||
modalClass: String,
|
modalClass: String,
|
||||||
width: Number || String,
|
width: Number || String,
|
||||||
draggable: {
|
draggable: { type: Boolean, default: false },
|
||||||
type: Boolean,
|
backdrop: { type: Boolean, default: true },
|
||||||
default: false,
|
modalStyle: { type: Object, default: () => ({}) },
|
||||||
},
|
|
||||||
backdrop: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
modalStyle: {
|
|
||||||
type: Object,
|
|
||||||
default: {},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dom = ref(null)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
document.querySelector(`#${props.id}[open]`)?.addEventListener("load",()=>{
|
|
||||||
console.log("loading")
|
|
||||||
})
|
|
||||||
document.querySelector(`#${props.id}`).addEventListener("load",()=>{
|
|
||||||
console.log("loading")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- Open the modal using ID.showModal() method -->
|
<dialog
|
||||||
<!-- :class="twMerge('modal', open && innerOpen ? 'modal-open' : 'modal-close')" -->
|
:id="id"
|
||||||
<dialog ref="dom" :id="id" :class="twMerge(
|
:class="
|
||||||
|
twMerge(
|
||||||
'modal',
|
'modal',
|
||||||
backdrop
|
backdrop ? '' : 'focus-visible:outline-none backdrop:bg-transparent'
|
||||||
? ''
|
)
|
||||||
: 'focus-visible:outline-none backdrop:bg-transparent',
|
"
|
||||||
)" :style="modalStyle" v-draggable="draggable">
|
:style="modalStyle"
|
||||||
<div :class="twMerge(
|
v-draggable="
|
||||||
'modal-box static rounded-md border border-info py-5 px-6 overflow-y-scroll bg-normal',
|
draggable ? { handle: '[data-drag-handle]', target: '.modal-box' } : false
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="
|
||||||
|
twMerge(
|
||||||
|
'modal-box static rounded-md border border-info py-5 px-6 overflow-y-auto bg-normal',
|
||||||
modalClass
|
modalClass
|
||||||
)
|
)
|
||||||
" :style="{ maxWidth: isNaN(width) ? width : `${width}px` }">
|
"
|
||||||
<div class="text-2xl font-bold">
|
:style="{ maxWidth: isNaN(width) ? width : `${width}px` }"
|
||||||
<slot name="modalTitle">
|
>
|
||||||
{{ title }}
|
<!-- 把手:只在這裡按下才可拖動 -->
|
||||||
</slot>
|
<div class="text-2xl font-bold select-none" data-drag-handle>
|
||||||
|
<slot name="modalTitle">{{ title }}</slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="min-h-[200px]">
|
<div class="min-h-[200px]">
|
||||||
<slot name="modalContent"></slot>
|
<slot name="modalContent"></slot>
|
||||||
</div>
|
</div>
|
||||||
@ -68,25 +50,17 @@ onMounted(() => {
|
|||||||
<slot name="modalAction"></slot>
|
<slot name="modalAction"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form v-if="backdrop" method="dialog" class="modal-backdrop">
|
<form v-if="backdrop" method="dialog" class="modal-backdrop">
|
||||||
<button @click="() => {
|
<button
|
||||||
onCancel ? onCancel() : cancel();
|
@click="
|
||||||
|
() => {
|
||||||
|
onCancel && onCancel();
|
||||||
}
|
}
|
||||||
">
|
"
|
||||||
|
>
|
||||||
close
|
close
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
.modal-box::before {
|
|
||||||
@apply fixed top-1 right-1 h-5 w-5 rotate-90 bg-no-repeat z-10 bg-[url('../../assets/img/table/content-box-background01.svg')] bg-center;
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-action::after {
|
|
||||||
@apply absolute -bottom-3 -left-4 h-5 w-5 rotate-90 bg-no-repeat z-10 bg-[url('../../assets/img/table/content-box-background05.svg')] bg-center;
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
|
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
|
|
||||||
const forgeDom = ref(null);
|
const forgeDom = ref(null);
|
||||||
let viewer = null;
|
let viewer = null;
|
||||||
|
@ -164,7 +164,7 @@ const initForge = async () => {
|
|||||||
// });
|
// });
|
||||||
// });
|
// });
|
||||||
// });
|
// });
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
const viewer = await initViewer(forgeDom.value)
|
const viewer = await initViewer(forgeDom.value)
|
||||||
const filePath = `${FILE_BASEURL}/upload/forge/0.svf`;
|
const filePath = `${FILE_BASEURL}/upload/forge/0.svf`;
|
||||||
await loadModel(viewer, filePath)
|
await loadModel(viewer, filePath)
|
||||||
|
@ -93,7 +93,7 @@ const createSprites = async (dataVizExtn) => {
|
|||||||
const DataVizCore = Autodesk.DataVisualization.Core;
|
const DataVizCore = Autodesk.DataVisualization.Core;
|
||||||
const viewableType = DataVizCore.ViewableType.SPRITE;
|
const viewableType = DataVizCore.ViewableType.SPRITE;
|
||||||
let spriteColor = new THREE.Color(0xffffff);
|
let spriteColor = new THREE.Color(0xffffff);
|
||||||
const BASEURL = import.meta.env.VITE_FORGE_BASEURL;
|
const BASEURL = window.env?.VITE_FORGE_BASEURL;
|
||||||
const spriteIconUrl = `${BASEURL}/hotspot.svg`;
|
const spriteIconUrl = `${BASEURL}/hotspot.svg`;
|
||||||
const style = new DataVizCore.ViewableStyle(
|
const style = new DataVizCore.ViewableStyle(
|
||||||
viewableType,
|
viewableType,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const BASEURL = import.meta.env.VITE_API_BASEURL;
|
const BASEURL = window.env?.VITE_API_BASEURL;
|
||||||
export const POST_LOGIN = `${BASEURL}/api/Login/`;
|
export const POST_LOGIN = `${BASEURL}/api/Login/`;
|
||||||
export const GET_AUTHPAGE_API = `${BASEURL}/api/GetUsrFroList`;
|
export const GET_AUTHPAGE_API = `${BASEURL}/api/GetUsrFroList`;
|
||||||
export const GET_SUBAUTHPAGE_API = `${BASEURL}/api/Device/GetMainSub`;
|
export const GET_SUBAUTHPAGE_API = `${BASEURL}/api/Device/GetMainSub`;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const BASEURL = import.meta.env.VITE_API_BASEURL;
|
const BASEURL = window.env?.VITE_API_BASEURL;
|
||||||
export const GET_FORGETOKEN_API = `${BASEURL}/api/forge/oauth/token`;
|
export const GET_FORGETOKEN_API = `${BASEURL}/api/forge/oauth/token`;
|
||||||
|
|
||||||
export const GET_FORGEURN_API = `${BASEURL}/api/Device/GetBuild`;
|
export const GET_FORGEURN_API = `${BASEURL}/api/Device/GetBuild`;
|
||||||
|
@ -1,64 +1,97 @@
|
|||||||
const moveModal = (elmnt) => {
|
// src/directives/draggable.js
|
||||||
console.log(elmnt);
|
// 用法:在 Modal 外層加 v-draggable="{ handle: '[data-drag-handle]', target: '.modal-box' }"
|
||||||
var pos1 = 0,
|
// - handle:只在這個節點按下才可拖動
|
||||||
pos2 = 0,
|
// - target:實際被拖動的 DOM(預設用 el 本身)
|
||||||
pos3 = 0,
|
|
||||||
pos4 = 0;
|
|
||||||
document.body.addEventListener("mousedown", dragMouseDown, {
|
|
||||||
passive: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
function dragMouseDown(e) {
|
function createDraggable(el, options = {}) {
|
||||||
console.log("dragMouseDown", e);
|
const cancelSelector =
|
||||||
e = e || window.event;
|
'input, textarea, select, button, [contenteditable="true"], [data-drag-cancel]';
|
||||||
|
|
||||||
|
const target =
|
||||||
|
(options.target && el.querySelector(options.target)) ||
|
||||||
|
el.querySelector('.modal-box') ||
|
||||||
|
el;
|
||||||
|
|
||||||
|
const handle =
|
||||||
|
(options.handle && el.querySelector(options.handle)) || target;
|
||||||
|
|
||||||
|
let startX = 0,
|
||||||
|
startY = 0,
|
||||||
|
originLeft = 0,
|
||||||
|
originTop = 0,
|
||||||
|
dragging = false;
|
||||||
|
|
||||||
|
// 只在「左鍵」且「不是表單控制項」時啟動拖曳
|
||||||
|
function onMouseDown(e) {
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
|
||||||
|
// 點到可互動元件就不要拖(也不要 preventDefault,讓它能聚焦)
|
||||||
|
if (e.target.matches(cancelSelector) || e.target.closest(cancelSelector)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 這裡才開始準備拖曳
|
||||||
|
dragging = true;
|
||||||
|
const rect = target.getBoundingClientRect();
|
||||||
|
originLeft = rect.left;
|
||||||
|
originTop = rect.top;
|
||||||
|
startX = e.clientX;
|
||||||
|
startY = e.clientY;
|
||||||
|
|
||||||
|
// 只有在真的要拖時才阻止預設,避免選字/圖片拖移
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// get the mouse cursor position at startup:
|
|
||||||
pos3 = e.clientX;
|
// 以視窗為座標系比較直觀
|
||||||
pos4 = e.clientY;
|
target.style.position = 'fixed';
|
||||||
document.body.addEventListener("mouseup", closeDragElement, {
|
target.style.left = '0px';
|
||||||
passive: false,
|
target.style.top = '0px';
|
||||||
});
|
target.style.transform = `translate(${originLeft}px, ${originTop}px)`;
|
||||||
// call a function whenever the cursor moves:
|
|
||||||
document.body.addEventListener("mousemove", elementDrag, {
|
window.addEventListener('mousemove', onMouseMove, { passive: false });
|
||||||
passive: false,
|
window.addEventListener('mouseup', onMouseUp, { passive: true });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function elementDrag(e) {
|
function onMouseMove(e) {
|
||||||
e = e || window.event;
|
if (!dragging) return;
|
||||||
e.preventDefault();
|
const dx = e.clientX - startX;
|
||||||
// calculate the new cursor position:
|
const dy = e.clientY - startY;
|
||||||
pos1 = pos3 - e.clientX;
|
target.style.transform = `translate(${originLeft + dx}px, ${originTop + dy}px)`;
|
||||||
pos2 = pos4 - e.clientY;
|
|
||||||
pos3 = e.clientX;
|
|
||||||
pos4 = e.clientY;
|
|
||||||
// set the element's new position:
|
|
||||||
elmnt.style.top = elmnt.offsetTop - pos2 + "px";
|
|
||||||
elmnt.style.left = elmnt.offsetLeft - pos1 + "px";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeDragElement() {
|
function onMouseUp() {
|
||||||
// stop moving when mouse button is released:
|
dragging = false;
|
||||||
document.body.removeEventListener("mouseup", closeDragElement);
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
document.body.removeEventListener("mousemove", elementDrag);
|
window.removeEventListener('mouseup', onMouseUp);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
handle.style.cursor = 'move';
|
||||||
|
handle.addEventListener('mousedown', onMouseDown, { passive: false });
|
||||||
|
|
||||||
|
// 提供清理函式給 unmounted 用
|
||||||
|
return () => {
|
||||||
|
handle.removeEventListener('mousedown', onMouseDown);
|
||||||
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
|
window.removeEventListener('mouseup', onMouseUp);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const draggable = {
|
export const draggable = {
|
||||||
install(app) {
|
install(app) {
|
||||||
app.directive("draggable", {
|
app.directive('draggable', {
|
||||||
mounted: (el, binding, vnode, prevVnode) => {
|
mounted(el, binding) {
|
||||||
console.log("draggable", $(`#${el.id}`).draggable);
|
// 允許 v-draggable 或 v-draggable="true" 或 v-draggable="{ handle, target }"
|
||||||
if (binding.value) {
|
const enabled =
|
||||||
if ($(`#${el.id}`).draggable) {
|
binding.value === '' || binding.value === true || typeof binding.value === 'object';
|
||||||
$(`#${el.id}`).draggable({
|
if (!enabled) return;
|
||||||
cursor: "move",
|
|
||||||
scroll: true,
|
// 以前用 jQuery UI 的判斷會在沒有 $ 或 .draggable 時噴錯,直接移除
|
||||||
container: ".app-container",
|
const options = typeof binding.value === 'object' ? binding.value : {};
|
||||||
});
|
el.__dragCleanup__ = createDraggable(el, options);
|
||||||
} else {
|
},
|
||||||
moveModal(el);
|
unmounted(el) {
|
||||||
}
|
if (el.__dragCleanup__) {
|
||||||
|
el.__dragCleanup__();
|
||||||
|
delete el.__dragCleanup__;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -72,7 +72,7 @@ export default function useForgeSprite() {
|
|||||||
const DataVizCore = Autodesk.DataVisualization.Core;
|
const DataVizCore = Autodesk.DataVisualization.Core;
|
||||||
const viewableType = DataVizCore.ViewableType.SPRITE;
|
const viewableType = DataVizCore.ViewableType.SPRITE;
|
||||||
let spriteColor = new THREE.Color(0xffffff);
|
let spriteColor = new THREE.Color(0xffffff);
|
||||||
const BASEURL = import.meta.env.VITE_FORGE_BASEURL;
|
const BASEURL = window.env?.VITE_FORGE_BASEURL;
|
||||||
const spriteIconUrl = `${BASEURL}/hotspot.svg`;
|
const spriteIconUrl = `${BASEURL}/hotspot.svg`;
|
||||||
const style = new DataVizCore.ViewableStyle(
|
const style = new DataVizCore.ViewableStyle(
|
||||||
viewableType,
|
viewableType,
|
||||||
|
Binary file not shown.
@ -5,7 +5,7 @@ import System from "@/views/system/System.vue";
|
|||||||
import SystemFloor from "@/views/system/SystemFloor.vue";
|
import SystemFloor from "@/views/system/SystemFloor.vue";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
history: createWebHashHistory(window.env?.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: "/login",
|
path: "/login",
|
||||||
|
@ -11,7 +11,7 @@ const useHeatmapBarStore = defineStore("heatmap", () => {
|
|||||||
|
|
||||||
const getConfig = async () => {
|
const getConfig = async () => {
|
||||||
const api =
|
const api =
|
||||||
import.meta.env.MODE === "production"
|
window.env?.MODE === "production"
|
||||||
? "/dist/config.json"
|
? "/dist/config.json"
|
||||||
: "/config.json";
|
: "/config.json";
|
||||||
const res = await axios.get(api);
|
const res = await axios.get(api);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const BASEURL = import.meta.env.VITE_API_BASEURL;
|
const BASEURL = window.env?.VITE_API_BASEURL;
|
||||||
|
|
||||||
export default function downloadExcel(res) {
|
export default function downloadExcel(res) {
|
||||||
let disposition = res.headers.get("Content-Disposition");
|
let disposition = res.headers.get("Content-Disposition");
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import useGetCookie from "@/hooks/useGetCookie";
|
import useGetCookie from "@/hooks/useGetCookie";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
const BASEURL = import.meta.env.VITE_API_BASEURL;
|
const BASEURL = window.env?.VITE_API_BASEURL;
|
||||||
|
|
||||||
const instance = axios.create({
|
const instance = axios.create({
|
||||||
baseURL: BASEURL,
|
baseURL: BASEURL,
|
||||||
|
@ -10,7 +10,7 @@ import { useI18n } from "vue-i18n";
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||||
const { companyOptions, departmentList, floors } = inject("asset_modal_options");
|
const { companyOptions, departmentList, floors } = inject("asset_modal_options");
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
const { searchParams, changeParams } = useSearchParam();
|
const { searchParams, changeParams } = useSearchParam();
|
||||||
|
|
||||||
const totalCoordinates = ref({});
|
const totalCoordinates = ref({});
|
||||||
|
@ -10,7 +10,7 @@ import useUserInfoStore from "@/stores/useUserInfoStore";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
const { searchParams, changeParams } = useSearchParam();
|
const { searchParams, changeParams } = useSearchParam();
|
||||||
const { updateLeftFields, formErrorMsg, formState } = inject(
|
const { updateLeftFields, formErrorMsg, formState } = inject(
|
||||||
"asset_table_modal_form"
|
"asset_table_modal_form"
|
||||||
|
@ -4,7 +4,7 @@ import { ref, computed, inject, watch, onMounted } from "vue";
|
|||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import Menu from "@/components/customUI/Menu.vue";
|
import Menu from "@/components/customUI/Menu.vue";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
const { formState } = inject("asset_table_modal_form");
|
const { formState } = inject("asset_table_modal_form");
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,7 @@ import { postMqttTopic, postMqttTopicStop } from "@/apis/system";
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||||
const { formState } = inject("asset_table_modal_form");
|
const { formState } = inject("asset_table_modal_form");
|
||||||
const BASEURL = import.meta.env.VITE_MQTT_BASEURL;
|
const BASEURL = window.env?.VITE_MQTT_BASEURL;
|
||||||
const iotSchemaTag = inject("iotSchemaTag");
|
const iotSchemaTag = inject("iotSchemaTag");
|
||||||
|
|
||||||
// MQTT相關
|
// MQTT相關
|
||||||
|
@ -7,7 +7,7 @@ import { twMerge } from "tailwind-merge";
|
|||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
const { totalCoordinates } = inject("asset_table_data");
|
const { totalCoordinates } = inject("asset_table_data");
|
||||||
const { floors } = inject("asset_modal_options");
|
const { floors } = inject("asset_modal_options");
|
||||||
const { updateRightFields, formErrorMsg, formState } = inject(
|
const { updateRightFields, formErrorMsg, formState } = inject(
|
||||||
|
@ -15,7 +15,7 @@ import "yup-phone-lite";
|
|||||||
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
editRecord: Object,
|
editRecord: Object,
|
||||||
|
@ -14,7 +14,7 @@ import { computed, inject, ref, watch, onMounted, onUnmounted } from "vue";
|
|||||||
import useBuildingStore from "@/stores/useBuildingStore";
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
import { getSystemDevices, getSystemRealTime } from "@/apis/system";
|
import { getSystemDevices, getSystemRealTime } from "@/apis/system";
|
||||||
import DashboardRefrig from "./components/DashboardRefrig.vue";
|
import DashboardRefrig from "./components/DashboardRefrig.vue";
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
const buildingStore = useBuildingStore();
|
const buildingStore = useBuildingStore();
|
||||||
|
|
||||||
const subscribeData = ref([]);
|
const subscribeData = ref([]);
|
||||||
|
@ -10,7 +10,7 @@ import { useI18n } from "vue-i18n";
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { searchParams, changeParams } = useSearchParam();
|
const { searchParams, changeParams } = useSearchParam();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import useSearchParam from "@/hooks/useSearchParam";
|
import useSearchParam from "@/hooks/useSearchParam";
|
||||||
const { searchParams, changeParams } = useSearchParam();
|
const { searchParams, changeParams } = useSearchParam();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from "@/apis/graph";
|
} from "@/apis/graph";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
updateEditRecord: Function,
|
updateEditRecord: Function,
|
||||||
|
@ -8,7 +8,7 @@ import { useI18n } from "vue-i18n";
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||||
const { sidebar_data } = inject("current_dir");
|
const { sidebar_data } = inject("current_dir");
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,7 @@ import useSearchParam from "@/hooks/useSearchParam";
|
|||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
const { searchParams } = useSearchParam();
|
const { searchParams } = useSearchParam();
|
||||||
|
|
||||||
const { dataSource, openModal, updateEditRecord, search, tableLoading } =
|
const { dataSource, openModal, updateEditRecord, search, tableLoading } =
|
||||||
|
@ -12,7 +12,7 @@ import Select from "@/components/customUI/Select.vue";
|
|||||||
import SearchSelect from "@/components/customUI/SearchSelect.vue";
|
import SearchSelect from "@/components/customUI/SearchSelect.vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
editRecord: Object,
|
editRecord: Object,
|
||||||
|
@ -70,7 +70,7 @@ import { getSystemDevices } from "@/apis/system";
|
|||||||
import { setRtspEnable } from "@/apis/rtsp"; // 已移除 setSambaDirectory
|
import { setRtspEnable } from "@/apis/rtsp"; // 已移除 setSambaDirectory
|
||||||
import useActiveBtn from "@/hooks/useActiveBtn";
|
import useActiveBtn from "@/hooks/useActiveBtn";
|
||||||
|
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
const DEFAULT_MONITOR_URL =
|
const DEFAULT_MONITOR_URL =
|
||||||
"http://192.168.0.219:8026/?url=rtsp://admin02:mjmAdmin_99@192.168.0.200:554/stream1?tcp";
|
"http://192.168.0.219:8026/?url=rtsp://admin02:mjmAdmin_99@192.168.0.200:554/stream1?tcp";
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { useI18n } from "vue-i18n";
|
|||||||
import useBuildingStore from "@/stores/useBuildingStore";
|
import useBuildingStore from "@/stores/useBuildingStore";
|
||||||
|
|
||||||
const storeBuild = useBuildingStore();
|
const storeBuild = useBuildingStore();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||||
|
|
||||||
|
@ -1,38 +1,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Table from "@/components/customUI/Table.vue";
|
import Table from "@/components/customUI/Table.vue";
|
||||||
import VendorModal from "./VendorModal.vue";
|
import VendorModal from "./VendorModal.vue";
|
||||||
import { getOperationCompanyList,deleteOperationCompany } from "@/apis/operation";
|
import {
|
||||||
|
getOperationCompanyList,
|
||||||
|
deleteOperationCompany,
|
||||||
|
} from "@/apis/operation";
|
||||||
import { onMounted, ref, inject, computed } from "vue";
|
import { onMounted, ref, inject, computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openToast, cancelToastOpen } = inject("app_toast");
|
const { openToast, cancelToastOpen } = inject("app_toast");
|
||||||
|
|
||||||
const columns = computed(() => [
|
const columns = computed(() => [
|
||||||
{
|
{ title: t("operation.vendor"), key: "name" },
|
||||||
title: t("operation.vendor"),
|
{ title: t("operation.contact_person"), key: "contact_person" },
|
||||||
key: "name",
|
{ title: t("operation.phone"), key: "phone" },
|
||||||
},
|
{ title: t("operation.email"), key: "email" },
|
||||||
{
|
{ title: t("operation.created_at"), key: "created_at" },
|
||||||
title: t("operation.contact_person"),
|
{ title: t("operation.operation"), key: "operation", width: 200 },
|
||||||
key: "contact_person",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("operation.phone"),
|
|
||||||
key: "phone",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("operation.email"),
|
|
||||||
key: "email",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("operation.created_at"),
|
|
||||||
key: "created_at",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("operation.operation"),
|
|
||||||
key: "operation",
|
|
||||||
width: 200,
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const dataSource = ref([]);
|
const dataSource = ref([]);
|
||||||
@ -49,7 +33,9 @@ onMounted(() => {
|
|||||||
getDataSource();
|
getDataSource();
|
||||||
});
|
});
|
||||||
|
|
||||||
const formState = ref({
|
// ====== Modal 狀態與開關 ======
|
||||||
|
const MODAL_ID = "company_modal";
|
||||||
|
const emptyForm = () => ({
|
||||||
contact_person: "",
|
contact_person: "",
|
||||||
email: "",
|
email: "",
|
||||||
name: "",
|
name: "",
|
||||||
@ -60,22 +46,36 @@ const formState = ref({
|
|||||||
address: "",
|
address: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const openModal = (record) => {
|
const formState = ref(emptyForm());
|
||||||
if (record.id) {
|
|
||||||
formState.value = { ...record };
|
// 同一函式同時處理:開啟(新增/編輯) 與 關閉
|
||||||
} else {
|
const openModal = (payload) => {
|
||||||
formState.value = {
|
const el = document.getElementById(MODAL_ID);
|
||||||
contact_person: "",
|
|
||||||
email: "",
|
// 關閉
|
||||||
name: "",
|
if (payload === false) {
|
||||||
phone: "",
|
try {
|
||||||
remark: "",
|
el?.close?.();
|
||||||
tax_id_number: "",
|
} catch {}
|
||||||
city: "",
|
return;
|
||||||
address: "",
|
}
|
||||||
};
|
|
||||||
|
// 開啟(無參數或 true → 新增)
|
||||||
|
if (payload === true || payload == null) {
|
||||||
|
formState.value = emptyForm();
|
||||||
|
try {
|
||||||
|
el?.showModal?.();
|
||||||
|
} catch {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 開啟(有帶 record → 編輯)
|
||||||
|
if (payload && typeof payload === "object") {
|
||||||
|
formState.value = { ...emptyForm(), ...payload }; // 合併保險
|
||||||
|
try {
|
||||||
|
el?.showModal?.();
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
company_modal.showModal();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const remove = async (id) => {
|
const remove = async (id) => {
|
||||||
@ -95,15 +95,19 @@ const remove = async (id) => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex justify-start items-center mt-10 mb-5">
|
<div class="flex justify-start items-center mt-10 mb-5">
|
||||||
<h3 class="text-xl mr-5">{{ $t("assetManagement.company") }}</h3>
|
<h3 class="text-xl mr-5">{{ $t("assetManagement.company") }}</h3>
|
||||||
|
|
||||||
|
<!-- 子層會渲染「新增」按鈕,照你的原樣 -->
|
||||||
<VendorModal
|
<VendorModal
|
||||||
:formState="formState"
|
:formState="formState"
|
||||||
:getData="getDataSource"
|
:getData="getDataSource"
|
||||||
:openModal="openModal"
|
:openModal="openModal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table :columns="columns" :dataSource="dataSource" :loading="loading">
|
<Table :columns="columns" :dataSource="dataSource" :loading="loading">
|
||||||
<template #bodyCell="{ record, column, index }">
|
<template #bodyCell="{ record, column, index }">
|
||||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||||
|
|
||||||
<template v-else-if="column.key === 'operation'">
|
<template v-else-if="column.key === 'operation'">
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-success text-white mr-2"
|
class="btn btn-sm btn-success text-white mr-2"
|
||||||
@ -118,6 +122,7 @@ const remove = async (id) => {
|
|||||||
{{ $t("button.delete") }}
|
{{ $t("button.delete") }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ record[column.key] }}
|
{{ record[column.key] }}
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,23 +1,21 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, defineProps, inject, watch } from "vue";
|
import { ref, inject } from "vue";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import "yup-phone-lite";
|
import "yup-phone-lite";
|
||||||
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
|
||||||
import {
|
import { postOperationCompany, updateOperationCompany } from "@/apis/operation";
|
||||||
postOperationCompany,
|
|
||||||
updateOperationCompany,
|
|
||||||
} from "@/apis/operation";
|
|
||||||
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 props = defineProps({
|
const props = defineProps({
|
||||||
formState: Object,
|
formState: Object,
|
||||||
getData: Function,
|
getData: Function,
|
||||||
openModal: Function
|
openModal: Function, // 父層傳入:開/關 modal
|
||||||
});
|
});
|
||||||
|
|
||||||
const deptScheme = yup.object({
|
const schema = yup.object({
|
||||||
name: yup.string().required(t("button.required")),
|
name: yup.string().required(t("button.required")),
|
||||||
contact_person: yup.string().nullable(true),
|
contact_person: yup.string().nullable(true),
|
||||||
email: yup.string().email().nullable(true),
|
email: yup.string().email().nullable(true),
|
||||||
@ -28,34 +26,104 @@ const deptScheme = yup.object({
|
|||||||
remark: yup.string().nullable(true),
|
remark: yup.string().nullable(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { formErrorMsg, handleSubmit, handleErrorReset, updateScheme } =
|
const { formErrorMsg, handleSubmit, handleErrorReset } =
|
||||||
useFormErrorMessage(deptScheme);
|
useFormErrorMessage(schema);
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const MODAL_ID = "company_modal";
|
||||||
|
|
||||||
|
/** ====== 關閉 Modal:多重保險 ====== */
|
||||||
|
const closeModal = () => {
|
||||||
|
// 1) 父層 boolean 控制
|
||||||
|
if (typeof props.openModal === "function") {
|
||||||
|
try {
|
||||||
|
if (props.openModal.length >= 1) {
|
||||||
|
// 偏好:openModal(false)
|
||||||
|
props.openModal(false);
|
||||||
|
} else {
|
||||||
|
// 兼容:無參數 toggler → 先嘗試傳 false,不行再當作切換
|
||||||
|
try {
|
||||||
|
props.openModal(false);
|
||||||
|
} catch {
|
||||||
|
props.openModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 原生 <dialog> 或可 close() 的自製 Modal
|
||||||
|
const el = document.getElementById(MODAL_ID);
|
||||||
|
if (el?.close) {
|
||||||
|
try {
|
||||||
|
el.close();
|
||||||
|
return;
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 發送一個常見的關閉事件給自製 Modal(若有監聽)
|
||||||
|
try {
|
||||||
|
el?.dispatchEvent?.(new CustomEvent("close", { bubbles: true }));
|
||||||
|
} catch (_) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** ====== 開啟 Modal:呼叫父層 ====== */
|
||||||
|
const openModal = () => {
|
||||||
|
if (typeof props.openModal === "function") {
|
||||||
|
try {
|
||||||
|
if (props.openModal.length >= 1) props.openModal(true);
|
||||||
|
else props.openModal();
|
||||||
|
} catch {
|
||||||
|
// 忽略
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
handleErrorReset();
|
// 只做「關閉」;不要清空內容,避免你說的「取消→內容被清空但沒關」情況
|
||||||
company_modal.close();
|
// 若要清錯誤訊息,可在下次打開前再清
|
||||||
|
closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOk = async () => {
|
const onOk = async () => {
|
||||||
const value = await handleSubmit(deptScheme, props.formState);
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
const value = await handleSubmit(schema, props.formState);
|
||||||
|
|
||||||
|
let res;
|
||||||
if (props.formState?.id) {
|
if (props.formState?.id) {
|
||||||
|
// 若你的 API 需要 id 參數,改成:updateOperationCompany(props.formState.id, value)
|
||||||
res = await updateOperationCompany(value);
|
res = await updateOperationCompany(value);
|
||||||
} else {
|
} else {
|
||||||
res = await postOperationCompany(value);
|
res = await postOperationCompany(value);
|
||||||
}
|
}
|
||||||
if (res.isSuccess) {
|
|
||||||
props.getData();
|
if (res?.isSuccess) {
|
||||||
onCancel();
|
await props.getData?.();
|
||||||
|
props.openModal(false);
|
||||||
|
// 成功後關閉
|
||||||
|
closeModal();
|
||||||
|
// 下次打開前再清錯誤
|
||||||
|
handleErrorReset();
|
||||||
|
props.openModal(false);
|
||||||
|
openToast?.("success", t("common.success"), `#${MODAL_ID}`);
|
||||||
} else {
|
} else {
|
||||||
openToast("error", res.msg, "#company_modal");
|
openToast?.("error", res?.msg ?? t("common.failed"), `#${MODAL_ID}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
openToast?.("error", err?.message ?? t("common.failed"), `#${MODAL_ID}`);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button class="btn btn-sm btn-add " @click.stop.prevent="props.openModal">
|
<button class="btn btn-sm btn-add" @click.stop.prevent="openModal">
|
||||||
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
|
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
id="company_modal"
|
id="company_modal"
|
||||||
:title="props.formState?.id ? t('button.edit') : t('button.add')"
|
:title="props.formState?.id ? t('button.edit') : t('button.add')"
|
||||||
@ -63,87 +131,79 @@ const onOk = async () => {
|
|||||||
width="710"
|
width="710"
|
||||||
>
|
>
|
||||||
<template #modalContent>
|
<template #modalContent>
|
||||||
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
|
<form class="mt-5 w-full flex flex-wrap justify-between">
|
||||||
<Input :value="formState" class="my-2" name="name">
|
<Input :value="props.formState" class="my-2" name="name">
|
||||||
<template #topLeft>{{ $t("operation.name") }}</template>
|
<template #topLeft>{{ $t("operation.name") }}</template>
|
||||||
<template #bottomLeft
|
<template #bottomLeft>
|
||||||
><span class="text-error text-base">
|
<span class="text-error text-base">{{ formErrorMsg.name }}</span>
|
||||||
{{ formErrorMsg.name }}
|
</template>
|
||||||
</span></template
|
</Input>
|
||||||
></Input
|
<Input :value="props.formState" class="my-2" name="contact_person">
|
||||||
>
|
|
||||||
<Input :value="formState" class="my-2" name="contact_person">
|
|
||||||
<template #topLeft>{{ $t("operation.contact_person") }}</template>
|
<template #topLeft>{{ $t("operation.contact_person") }}</template>
|
||||||
<template #bottomLeft
|
<template #bottomLeft>
|
||||||
><span class="text-error text-base">
|
<span class="text-error text-base">{{
|
||||||
{{ formErrorMsg.contact_person }}
|
formErrorMsg.contact_person
|
||||||
</span></template
|
}}</span>
|
||||||
></Input
|
</template>
|
||||||
>
|
</Input>
|
||||||
<Input :value="formState" class="my-2" name="phone">
|
<Input :value="props.formState" class="my-2" name="phone">
|
||||||
<template #topLeft>{{ $t("operation.phone") }}</template>
|
<template #topLeft>{{ $t("operation.phone") }}</template>
|
||||||
<template #bottomLeft
|
<template #bottomLeft>
|
||||||
><span class="text-error text-base">
|
<span class="text-error text-base">{{ formErrorMsg.phone }}</span>
|
||||||
{{ formErrorMsg.phone }}
|
</template>
|
||||||
</span></template
|
</Input>
|
||||||
></Input
|
<Input :value="props.formState" class="my-2" name="email">
|
||||||
>
|
|
||||||
<Input :value="formState" class="my-2" name="email">
|
|
||||||
<template #topLeft>{{ $t("operation.email") }}</template>
|
<template #topLeft>{{ $t("operation.email") }}</template>
|
||||||
<template #bottomLeft
|
<template #bottomLeft>
|
||||||
><span class="text-error text-base">
|
<span class="text-error text-base">{{ formErrorMsg.email }}</span>
|
||||||
{{ formErrorMsg.email }}
|
</template>
|
||||||
</span></template
|
</Input>
|
||||||
></Input
|
<Input :value="props.formState" class="my-2" name="city">
|
||||||
>
|
|
||||||
<Input :value="formState" class="my-2" name="city">
|
|
||||||
<template #topLeft>{{ $t("operation.city") }}</template>
|
<template #topLeft>{{ $t("operation.city") }}</template>
|
||||||
<template #bottomLeft
|
<template #bottomLeft>
|
||||||
><span class="text-error text-base">
|
<span class="text-error text-base">{{ formErrorMsg.city }}</span>
|
||||||
{{ formErrorMsg.city }}
|
</template>
|
||||||
</span></template
|
</Input>
|
||||||
></Input
|
<Input :value="props.formState" class="my-2" name="address">
|
||||||
>
|
|
||||||
<Input :value="formState" class="my-2" name="address">
|
|
||||||
<template #topLeft>{{ $t("operation.address") }}</template>
|
<template #topLeft>{{ $t("operation.address") }}</template>
|
||||||
<template #bottomLeft
|
<template #bottomLeft>
|
||||||
><span class="text-error text-base">
|
<span class="text-error text-base">{{ formErrorMsg.address }}</span>
|
||||||
{{ formErrorMsg.address }}
|
</template>
|
||||||
</span></template
|
</Input>
|
||||||
></Input
|
<Input :value="props.formState" class="my-2" name="tax_id_number">
|
||||||
>
|
|
||||||
<Input :value="formState" class="my-2" name="tax_id_number">
|
|
||||||
<template #topLeft>{{ $t("operation.tax_id_number") }}</template>
|
<template #topLeft>{{ $t("operation.tax_id_number") }}</template>
|
||||||
<template #bottomLeft
|
<template #bottomLeft>
|
||||||
><span class="text-error text-base">
|
<span class="text-error text-base">{{
|
||||||
{{ formErrorMsg.tax_id_number }}
|
formErrorMsg.tax_id_number
|
||||||
</span></template
|
}}</span>
|
||||||
></Input
|
</template>
|
||||||
>
|
</Input>
|
||||||
<Input :value="formState" class="my-2" name="remark">
|
<Input :value="props.formState" class="my-2" name="remark">
|
||||||
<template #topLeft>{{ $t("operation.remark") }}</template>
|
<template #topLeft>{{ $t("operation.remark") }}</template>
|
||||||
<template #bottomLeft
|
<template #bottomLeft>
|
||||||
><span class="text-error text-base">
|
<span class="text-error text-base">{{ formErrorMsg.remark }}</span>
|
||||||
{{ formErrorMsg.remark }}
|
</template>
|
||||||
</span></template
|
</Input>
|
||||||
></Input
|
|
||||||
>
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modalAction>
|
<template #modalAction>
|
||||||
<button
|
<button
|
||||||
type="reset"
|
type="button"
|
||||||
class="btn btn-outline-success mr-2"
|
class="btn btn-outline-success mr-2"
|
||||||
@click.prevent="onCancel"
|
@click.prevent="onCancel"
|
||||||
|
:disabled="loading"
|
||||||
>
|
>
|
||||||
{{ $t("button.cancel") }}
|
{{ $t("button.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="button"
|
||||||
class="btn btn-outline-success"
|
class="btn btn-outline-success"
|
||||||
@click.stop.prevent="onOk"
|
@click.stop.prevent="onOk"
|
||||||
|
:disabled="loading"
|
||||||
>
|
>
|
||||||
|
<span v-if="loading" class="loading loading-spinner loading-xs mr-2" />
|
||||||
{{ $t("button.submit") }}
|
{{ $t("button.submit") }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -11,7 +11,7 @@ const { currentFloor, subscribeData } = inject("system_deviceList");
|
|||||||
const { getCurrentInfoModalData, selected_dbid } = inject(
|
const { getCurrentInfoModalData, selected_dbid } = inject(
|
||||||
"system_selectedDevice"
|
"system_selectedDevice"
|
||||||
);
|
);
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
|
|
||||||
const asset_floor_chart = ref(null);
|
const asset_floor_chart = ref(null);
|
||||||
const sameOption = {
|
const sameOption = {
|
||||||
|
@ -8,7 +8,7 @@ const { getCurrentInfoModalData, selected_dbid } = inject(
|
|||||||
const { subscribeData } = inject("system_deviceList");
|
const { subscribeData } = inject("system_deviceList");
|
||||||
|
|
||||||
const { showData } = useSystemShowData();
|
const { showData } = useSystemShowData();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
|
|
||||||
const fitToView = (forge_dbid, spriteDbId) => {
|
const fitToView = (forge_dbid, spriteDbId) => {
|
||||||
selected_dbid.value = [forge_dbid, spriteDbId];
|
selected_dbid.value = [forge_dbid, spriteDbId];
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { computed, inject, watch, ref } from "vue";
|
import { computed, inject, watch, ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
|
const FILE_BASEURL = window.env?.VITE_FILE_API_BASEURL;
|
||||||
const { selectedDeviceCog } = inject("system_selectedDevice");
|
const { selectedDeviceCog } = inject("system_selectedDevice");
|
||||||
const imgData = ref([]);
|
const imgData = ref([]);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user