語言包與首頁介面

This commit is contained in:
koko 2024-10-11 17:10:47 +08:00
parent f5c7a27cd6
commit 706c536782
144 changed files with 3209 additions and 2453 deletions

View File

@ -9,7 +9,7 @@
href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.css"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Marketing Dashboard - Application Intel - SmartAdmin v4.5.1</title>
<title>瀚荃監控系統</title>
<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 type="text/javascript" src="/requirejs/config.js"></script>

72
package-lock.json generated
View File

@ -20,10 +20,12 @@
"date-fns": "^3.3.1",
"dayjs": "^1.11.10",
"echarts": "^5.4.3",
"flag-icons": "^7.2.3",
"pinia": "^2.1.7",
"requirejs": "^2.3.6",
"tailwind-merge": "^2.2.1",
"vue": "^3.3.4",
"vue-i18n": "^10.0.4",
"vue-router": "^4.2.5",
"yup": "^1.4.0",
"yup-phone-lite": "^2.0.1"
@ -545,6 +547,50 @@
"vue": ">= 3.0.0 < 4"
}
},
"node_modules/@intlify/core-base": {
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-10.0.4.tgz",
"integrity": "sha512-GG428DkrrWCMhxRMRQZjuS7zmSUzarYcaHJqG9VB8dXAxw4iQDoKVQ7ChJRB6ZtsCsX3Jse1PEUlHrJiyQrOTg==",
"license": "MIT",
"dependencies": {
"@intlify/message-compiler": "10.0.4",
"@intlify/shared": "10.0.4"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-10.0.4.tgz",
"integrity": "sha512-AFbhEo10DP095/45EauinQJ5hJ3rJUmuuqltGguvc3WsvezZN+g8qNHLGWKu60FHQVizMrQY7VJ+zVlBXlQQkQ==",
"license": "MIT",
"dependencies": {
"@intlify/shared": "10.0.4",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-10.0.4.tgz",
"integrity": "sha512-ukFn0I01HsSgr3VYhYcvkTCLS7rGa0gw4A4AMpcy/A9xx/zRJy7PS2BElMXLwUazVFMAr5zuiTk3MQeoeGXaJg==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@ -2334,6 +2380,12 @@
"node": ">=8"
}
},
"node_modules/flag-icons": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.2.3.tgz",
"integrity": "sha512-X2gUdteNuqdNqob2KKTJTS+ZCvyWeLCtDz9Ty8uJP17Y4o82Y+U/Vd4JNrdwTAjagYsRznOn9DZ+E/Q52qbmqg==",
"license": "MIT"
},
"node_modules/follow-redirects": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
@ -5378,6 +5430,26 @@
}
}
},
"node_modules/vue-i18n": {
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-10.0.4.tgz",
"integrity": "sha512-1xkzVxqBLk2ZFOmeI+B5r1J7aD/WtNJ4j9k2mcFcQo5BnOmHBmD7z4/oZohh96AAaRZ4Q7mNQvxc9h+aT+Md3w==",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "10.0.4",
"@intlify/shared": "10.0.4",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-router": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz",

View File

@ -21,10 +21,12 @@
"date-fns": "^3.3.1",
"dayjs": "^1.11.10",
"echarts": "^5.4.3",
"flag-icons": "^7.2.3",
"pinia": "^2.1.7",
"requirejs": "^2.3.6",
"tailwind-merge": "^2.2.1",
"vue": "^3.3.4",
"vue-i18n": "^10.0.4",
"vue-router": "^4.2.5",
"yup": "^1.4.0",
"yup-phone-lite": "^2.0.1"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 122 KiB

View File

@ -39,11 +39,11 @@ provide("app_toast", { openToast });
/>
<div v-if="store.user.token" class="min-h-screen">
<Navbar />
<div class="mt-6 px-8 w-full relative app-container">
<div class="px-8 w-full relative app-container">
<RouterView />
</div>
</div>
<div v-else class="h-screen"><RouterView /></div>
<div v-else class="min-h-screen"><RouterView /></div>
</template>
<style scoped>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.cls-1{fill:#dff3fa;}.cls-2{fill:#74b6ed;}</style></defs><path class="cls-1" d="M2.79,24.12a2.59,2.59,0,0,0,2.58-2.58V7.1A5,5,0,0,0,7.11,5.4H21.23a2.59,2.59,0,0,0,2.58-2.58.12.12,0,0,0-.12-.12.12.12,0,0,0-.12.12,2.34,2.34,0,0,1-2.34,2.34H7l0,.06A4.86,4.86,0,0,1,5.19,6.93L5.13,7V21.54a2.34,2.34,0,0,1-2.34,2.34.12.12,0,0,0-.12.12A.12.12,0,0,0,2.79,24.12Z"/><path class="cls-2" d="M2,18.05a.6.6,0,0,1-.6-.59V4.14L1,3.86A2.09,2.09,0,0,1,2.09,0,2.05,2.05,0,0,1,3.88,1l.28.46H17a.6.6,0,0,1,.6.6.59.59,0,0,1-.6.59H4.16l-.28.47a2.08,2.08,0,0,1-.77.76l-.49.27V17.46A.59.59,0,0,1,2,18.05Z"/></svg>

After

Width:  |  Height:  |  Size: 697 B

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.cls-1{fill:#dff3fa;}.cls-2{fill:#74b6ed;}</style></defs><path class="cls-1" d="M-.15,21.18A2.58,2.58,0,0,1,2.43,18.6H16.87a4.88,4.88,0,0,1,1.7-1.74V2.74A2.59,2.59,0,0,1,21.15.15a.12.12,0,0,1,.12.12.12.12,0,0,1-.12.12,2.35,2.35,0,0,0-2.34,2.35V17l-.06,0A4.63,4.63,0,0,0,17,18.78l0,.06H2.43A2.35,2.35,0,0,0,.08,21.18.12.12,0,0,1,0,21.3.11.11,0,0,1-.15,21.18Z"/><path class="cls-2" d="M5.92,21.94a.59.59,0,0,0,.59.6H19.83l.28.44a2.07,2.07,0,0,0,1.77,1,2.09,2.09,0,0,0,1.06-3.89l-.47-.27V7a.59.59,0,0,0-.59-.6.6.6,0,0,0-.6.6V19.81l-.46.27a2.11,2.11,0,0,0-.76.78l-.28.48H6.51A.59.59,0,0,0,5.92,21.94Z"/></svg>

After

Width:  |  Height:  |  Size: 713 B

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><defs><style>.cls-1{fill:#0ca9d4;}</style></defs><polygon id="_13" data-name="13" class="cls-1" points="3.96 0 4.48 3.43 7.91 3.96 4.48 4.48 3.96 7.91 3.43 4.48 0 3.96 3.43 3.43 3.96 0"/></svg>

After

Width:  |  Height:  |  Size: 286 B

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 124.4 12" style="enable-background:new 0 0 124.4 12;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0CA9D4;}
.st1{opacity:0.3;fill:#969696;}
.st2{opacity:0.8;fill:#0CA9D4;}
</style>
<g>
<polygon class="st0" points="92.7,2.9 31.5,2.9 28.8,0 95.6,0 "/>
<path class="st1" d="M124.4,0.1v11.7c0,0.1-0.3,0.1-0.7,0.1H97.8c-0.2,0-0.4,0-0.5-0.1l-1.8-0.4c-0.1,0-0.3-0.1-0.5-0.1H29.8
c-0.2,0-0.4,0-0.5,0.1L27.5,12C27.4,12,27.2,12,27,12H0.7C0.3,12,0,11.9,0,11.9V0.1C0,0.1,0.3,0,0.7,0l26.2,0.1
c0.2,0,0.4,0,0.5,0.1L29,2.2c0.1,0,0.3,0.1,0.5,0.1h65.3c0.2,0,0.4,0,0.5-0.1l1.9-2.1c0.1,0,0.3-0.1,0.5-0.1l26-0.1
C124.1,0,124.4,0.1,124.4,0.1z"/>
<path class="st2" d="M122.9,12V2.1c0-0.5-0.4-0.9-0.9-0.9H96.4c-0.3,0-0.5,0.1-0.7,0.3l-1.8,1.6c-0.1,0.1-0.2,0.1-0.3,0.1H30.7
c-0.1,0-0.2,0-0.3-0.1l-1.6-1.6c-0.2-0.2-0.4-0.3-0.7-0.3H2.4c-0.5,0-0.9,0.4-0.9,0.9V12 M1.7,12V2.1c0-0.4,0.3-0.6,0.6-0.6h25.8
c0.2,0,0.4,0.1,0.5,0.2l1.6,1.6c0.1,0.1,0.3,0.2,0.5,0.2h62.9c0.2,0,0.4-0.1,0.5-0.2l1.8-1.6c0.1-0.1,0.3-0.2,0.5-0.2H122
c0.4,0,0.6,0.3,0.6,0.6V12"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 124.4 12.01"><defs><style>.cls-1,.cls-3{fill:#ffe422;}.cls-2{fill:#969696;opacity:0.3;}.cls-3{opacity:0.8;}</style></defs><polygon class="cls-1" points="92.68 2.87 31.5 2.87 28.8 0 95.59 0 92.68 2.87"/><path class="cls-2" d="M124.4.14V11.86c0,.08-.3.15-.66.15H97.84a2.27,2.27,0,0,1-.5,0l-1.76-.45a2.34,2.34,0,0,0-.5,0H29.76a2.2,2.2,0,0,0-.5,0L27.5,12a2.34,2.34,0,0,1-.5,0H.66C.3,12,0,11.94,0,11.86V.14C0,.06.3,0,.66,0L26.87.11a1.79,1.79,0,0,1,.5.05L29,2.25a2.27,2.27,0,0,0,.5,0H94.86a2.34,2.34,0,0,0,.5,0L97.25.16a1.83,1.83,0,0,1,.5,0l26-.11C124.1,0,124.4.06,124.4.14Z"/><path class="cls-3" d="M122.92,12V2.12a.89.89,0,0,0-.89-.89H96.41a.9.9,0,0,0-.67.31l-1.83,1.6a.36.36,0,0,1-.29.13H30.73a.36.36,0,0,1-.29-.13l-1.6-1.6a.9.9,0,0,0-.67-.31H2.37a.89.89,0,0,0-.89.89V12m.25,0V2.12a.64.64,0,0,1,.64-.64h25.8a.65.65,0,0,1,.48.23l1.6,1.59a.64.64,0,0,0,.48.22H93.62a.62.62,0,0,0,.48-.22l1.83-1.59a.65.65,0,0,1,.48-.23H122a.64.64,0,0,1,.64.64V12"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2.5 21" style="enable-background:new 0 0 2.5 21;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.8;}
.st1{fill:#FDE9F2;}
</style>
<g id="_x37_0_00000047024518298343511510000016821505459452511412_" class="st0">
<rect x="1.2" y="1" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -1.5991 1.79)" class="st1" width="0.3" height="3.6"/>
<rect x="1.2" y="-0.4" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -0.5638 1.3612)" class="st1" width="0.3" height="3.6"/>
<rect x="1.2" y="5.2" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -4.5762 3.0232)" class="st1" width="0.3" height="3.6"/>
<rect x="1.2" y="3.8" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -3.541 2.5943)" class="st1" width="0.3" height="3.6"/>
<rect x="1.2" y="9.5" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -7.5533 4.2563)" class="st1" width="0.3" height="3.6"/>
<rect x="1.2" y="8" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -6.5181 3.8275)" class="st1" width="0.3" height="3.6"/>
<rect x="1.2" y="13.7" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -10.5305 5.4895)" class="st1" width="0.3" height="3.6"/>
<rect x="1.2" y="12.2" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -9.4952 5.0607)" class="st1" width="0.3" height="3.6"/>
<rect x="1.2" y="17.9" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -13.5076 6.7227)" class="st1" width="0.3" height="3.6"/>
<rect x="1.2" y="16.4" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -12.4724 6.2938)" class="st1" width="0.3" height="3.6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 151 38"><defs><style>.cls-1,.cls-5{fill:#b3e8f4;}.cls-1{opacity:0;}.cls-2{fill:#83beed;}.cls-3{fill:#dff3fa;}.cls-4{fill:#fcfefd;}.cls-6{fill:#fde9f2;}</style></defs><path class="cls-1" d="M140.66,2H4.17A4.17,4.17,0,0,0,0,6.15v21.5l10.6,10.6H147.09a4.17,4.17,0,0,0,4.17-4.17V12.59Z"/><path class="cls-2" d="M147.09,36.29H11.41L2,26.84V6.15A2.21,2.21,0,0,1,4.17,3.94H139.85l9.45,9.46V34.08A2.21,2.21,0,0,1,147.09,36.29ZM11.52,36H147.09a2,2,0,0,0,2-2V13.5l-9.31-9.31H4.17a2,2,0,0,0-2,2V26.73Z"/><polygon class="cls-3" points="24.78 5.81 8.01 5.81 11.16 2.33 27.93 2.33 24.78 5.81"/><polygon class="cls-4" points="137.13 36.16 127.56 36.16 129.36 34.17 138.93 34.17 137.13 36.16"/><polygon class="cls-4" points="114.9 36.16 105.33 36.16 107.13 34.17 116.7 34.17 114.9 36.16"/><polygon class="cls-4" points="92.67 36.16 83.11 36.16 84.91 34.17 94.47 34.17 92.67 36.16"/><polygon class="cls-5" points="123.2 6.35 92.68 6.35 98.42 0 128.94 0 123.2 6.35"/><rect class="cls-4" x="77.43" y="33.96" width="71.74" height="0.25"/><polygon id="_13" data-name="13" class="cls-6" points="138.62 8.13 139.09 11.2 142.16 11.67 139.09 12.14 138.62 15.21 138.15 12.14 135.08 11.67 138.15 11.2 138.62 8.13"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 151 38"><defs><style>.cls-1{fill:#fff;opacity:0.05;}.cls-2{fill:#af2334;opacity:0.75;}.cls-3{fill:#30c4c4;}.cls-4{fill:#dff3fa;}.cls-5{fill:#fcfefd;}</style></defs><path class="cls-1" d="M140.18,2H3.68A4.17,4.17,0,0,0-.48,6.15v21.5l10.6,10.6H146.61a4.17,4.17,0,0,0,4.17-4.17V12.59Z"/><path class="cls-2" d="M140.18,2H3.68A4.17,4.17,0,0,0-.48,6.15v21.5l10.6,10.6H146.61a4.17,4.17,0,0,0,4.17-4.17V12.59Z"/><path class="cls-3" d="M146.61,36.29H10.93L1.47,26.84V6.15A2.22,2.22,0,0,1,3.68,3.94H139.36l9.46,9.46V34.08A2.21,2.21,0,0,1,146.61,36.29ZM11,36H146.61a2,2,0,0,0,2-2V13.5l-9.31-9.31H3.68a2,2,0,0,0-2,2V26.73Z"/><polygon class="cls-4" points="24.3 5.81 7.53 5.81 10.68 2.33 27.45 2.33 24.3 5.81"/><polygon class="cls-5" points="136.65 36.16 127.08 36.16 128.88 34.17 138.45 34.17 136.65 36.16"/><polygon class="cls-5" points="114.42 36.16 104.85 36.16 106.65 34.17 116.22 34.17 114.42 36.16"/><polygon class="cls-5" points="92.19 36.16 82.62 36.16 84.42 34.17 93.99 34.17 92.19 36.16"/><polygon class="cls-4" points="122.72 6.35 92.2 6.35 97.94 0 128.45 0 122.72 6.35"/><rect class="cls-5" x="76.95" y="33.96" width="71.74" height="0.25"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/assets/img/icon/C.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/assets/img/icon/E1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/assets/img/icon/E2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
src/assets/img/icon/E3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
src/assets/img/icon/EL.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/img/icon/F1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
src/assets/img/icon/L1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/assets/img/icon/L2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/assets/img/icon/M1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
src/assets/img/icon/M10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/img/icon/M12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
src/assets/img/icon/M8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
src/assets/img/icon/P.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
src/assets/img/icon/P1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
src/assets/img/icon/PSC.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/assets/img/icon/R.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/img/icon/W1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/assets/img/icon/W2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
src/assets/img/icon/W3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 14 55"><defs><style>.cls-1{isolation:isolate;}.cls-2{mix-blend-mode:screen;opacity:0.75;}.cls-3{fill:#58bfe8;}</style></defs><g class="cls-1"><g id="圖層_1" data-name="圖層 1"><image class="cls-2" width="14" height="55" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAA3CAYAAAA8PXu0AAAACXBIWXMAAAsSAAALEgHS3X78AAAA8ElEQVRIS+2WTQrCMBBGX4roQhDcieAZvP9VPICIKxGyUKtx4Zc6TXVQF67yQZjm52Xarl5IKfFLRsU8FNUm2WrBRvOJqoUTcAHOqimDQYfnwBKYai1ptMAR2KtebMeJoLXA7nbgJChqtGXHqUYEdqo3nh0jcMW8aoYDj047YKPDN/rf2MLwr+bviYIOAvNeV0swbyQBeQzSvFr8JBV0UkEnFXRSQScVdFJBJxV08n/wla5kUWroX+x6jlWzmdZKs+rZIzzFaAwstFa63MAerYpFHh1XfGCPaHOr53e+2tljME7e8IUhh0Lmf3LywaaXO0TIazwsXqhCAAAAAElFTkSuQmCC"/><polygon class="cls-3" points="10.59 51.05 3.9 51.05 3.86 3.77 10.55 3.77 10.55 4.77 4.86 4.77 4.9 50.05 10.59 50.05 10.59 51.05"/></g></g></svg>

After

Width:  |  Height:  |  Size: 926 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 14 55"><defs><style>.cls-1{isolation:isolate;}.cls-2{mix-blend-mode:screen;opacity:0.75;}.cls-3{fill:#23c8cc;}</style></defs><g class="cls-1"><g id="圖層_1" data-name="圖層 1"><image class="cls-2" width="14" height="55" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAA3CAYAAAA8PXu0AAAACXBIWXMAAAsSAAALEgHS3X78AAAA8ElEQVRIS+2WTQrCMBBGX4roQhDcieAZvP9VPICIKxGyUKtx4Zc6TXVQF67yQZjm52Xarl5IKfFLRsU8FNUm2WrBRvOJqoUTcAHOqimDQYfnwBKYai1ptMAR2KtebMeJoLXA7nbgJChqtGXHqUYEdqo3nh0jcMW8aoYDj047YKPDN/rf2MLwr+bviYIOAvNeV0swbyQBeQzSvFr8JBV0UkEnFXRSQScVdFJBJxV08n/wla5kUWroX+x6jlWzmdZKs+rZIzzFaAwstFa63MAerYpFHh1XfGCPaHOr53e+2tljME7e8IUhh0Lmf3LywaaXO0TIazwsXqhCAAAAAElFTkSuQmCC"/><polygon class="cls-3" points="10.59 51.05 3.9 51.05 3.86 3.77 10.55 3.77 10.55 4.77 4.86 4.77 4.9 50.05 10.59 50.05 10.59 51.05"/></g></g></svg>

After

Width:  |  Height:  |  Size: 926 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 14 55"><defs><style>.cls-1{isolation:isolate;}.cls-2{mix-blend-mode:screen;opacity:0.75;}.cls-3{fill:#58bfe8;}</style></defs><g class="cls-1"><g id="圖層_1" data-name="圖層 1"><image class="cls-2" width="14" height="55" transform="matrix(-1, 0, 0, 1, 14, 0)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAA3CAYAAAA8PXu0AAAACXBIWXMAAAsSAAALEgHS3X78AAAA8ElEQVRIS+2WTQrCMBBGX4roQhDcieAZvP9VPICIKxGyUKtx4Zc6TXVQF67yQZjm52Xarl5IKfFLRsU8FNUm2WrBRvOJqoUTcAHOqimDQYfnwBKYai1ptMAR2KtebMeJoLXA7nbgJChqtGXHqUYEdqo3nh0jcMW8aoYDj047YKPDN/rf2MLwr+bviYIOAvNeV0swbyQBeQzSvFr8JBV0UkEnFXRSQScVdFJBJxV08n/wla5kUWroX+x6jlWzmdZKs+rZIzzFaAwstFa63MAerYpFHh1XfGCPaHOr53e+2tljME7e8IUhh0Lmf3LywaaXO0TIazwsXqhCAAAAAElFTkSuQmCC"/><polygon class="cls-3" points="3.41 51.05 10.1 51.05 10.14 3.77 3.44 3.77 3.44 4.77 9.14 4.77 9.1 50.05 3.41 50.05 3.41 51.05"/></g></g></svg>

After

Width:  |  Height:  |  Size: 962 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 14 55"><defs><style>.cls-1{isolation:isolate;}.cls-2{mix-blend-mode:screen;opacity:0.75;}.cls-3{fill:#23c8cc;}</style></defs><g class="cls-1"><g id="圖層_1" data-name="圖層 1"><image class="cls-2" width="14" height="55" transform="matrix(-1, 0, 0, 1, 14, 0)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAA3CAYAAAA8PXu0AAAACXBIWXMAAAsSAAALEgHS3X78AAAA8ElEQVRIS+2WTQrCMBBGX4roQhDcieAZvP9VPICIKxGyUKtx4Zc6TXVQF67yQZjm52Xarl5IKfFLRsU8FNUm2WrBRvOJqoUTcAHOqimDQYfnwBKYai1ptMAR2KtebMeJoLXA7nbgJChqtGXHqUYEdqo3nh0jcMW8aoYDj047YKPDN/rf2MLwr+bviYIOAvNeV0swbyQBeQzSvFr8JBV0UkEnFXRSQScVdFJBJxV08n/wla5kUWroX+x6jlWzmdZKs+rZIzzFaAwstFa63MAerYpFHh1XfGCPaHOr53e+2tljME7e8IUhh0Lmf3LywaaXO0TIazwsXqhCAAAAAElFTkSuQmCC"/><polygon class="cls-3" points="3.41 51.05 10.1 51.05 10.14 3.77 3.44 3.77 3.44 4.77 9.14 4.77 9.1 50.05 3.41 50.05 3.41 51.05"/></g></g></svg>

After

Width:  |  Height:  |  Size: 962 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><defs><style>.cls-1{fill:#ffe422;}</style></defs><polygon id="_13" data-name="13" class="cls-1" points="4 0.19 4.5 3.5 7.81 4 4.5 4.5 4 7.81 3.5 4.5 0.19 4 3.5 3.5 4 0.19"/></svg>

After

Width:  |  Height:  |  Size: 272 B

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 9"><defs><style>.cls-1{fill:#30c4c4;}</style></defs><path id="_12" data-name="12" class="cls-1" d="M22,4.44C21.92,4.4,8.63,4,7.59,3.11A11.13,11.13,0,0,1,4.82.19h-1A9.65,9.65,0,0,1,0,4V5a9.65,9.65,0,0,1,3.8,3.8h1a11.12,11.12,0,0,1,2.7-3.1C8.17,5.17,21.94,4.59,22,4.56Z"/></svg>

After

Width:  |  Height:  |  Size: 367 B

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4.91 37.93"><defs><style>.cls-1{opacity:0.8;}.cls-2{fill:#ffe422;}</style></defs><g id="_70" data-name="70" class="cls-1"><rect class="cls-2" x="2.21" y="29.6" width="0.49" height="6.45" transform="translate(-22.5 11.35) rotate(-45)"/><rect class="cls-2" x="2.21" y="32.24" width="0.49" height="6.45" transform="translate(-24.36 12.13) rotate(-45)"/><rect class="cls-2" x="2.21" y="22.01" width="0.49" height="6.45" transform="translate(-17.13 9.13) rotate(-45)"/><rect class="cls-2" x="2.21" y="24.65" width="0.49" height="6.45" transform="translate(-18.99 9.9) rotate(-45)"/><rect class="cls-2" x="2.21" y="14.42" width="0.49" height="6.45" transform="translate(-11.76 6.9) rotate(-45)"/><rect class="cls-2" x="2.21" y="17.06" width="0.49" height="6.45" transform="translate(-13.62 7.68) rotate(-45)"/><rect class="cls-2" x="2.21" y="6.82" width="0.49" height="6.45" transform="translate(-6.39 4.68) rotate(-45)"/><rect class="cls-2" x="2.21" y="9.46" width="0.49" height="6.45" transform="translate(-8.25 5.45) rotate(-45)"/><rect class="cls-2" x="2.21" y="-0.77" width="0.49" height="6.45" transform="translate(-1.02 2.46) rotate(-45)"/><rect class="cls-2" x="2.21" y="1.87" width="0.49" height="6.45" transform="translate(-2.88 3.23) rotate(-45)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 7.13 78.94"><defs><style>.cls-1{fill:#fde9f2;}</style></defs><g id="_67" data-name="67"><rect class="cls-1" y="75.13" width="3.57" height="3.57"/><rect class="cls-1" y="68" width="3.57" height="3.57"/><rect class="cls-1" y="60.87" width="3.57" height="3.57"/><rect class="cls-1" y="53.73" width="3.57" height="3.57"/><rect class="cls-1" y="46.6" width="3.57" height="3.57"/><rect class="cls-1" y="39.47" width="3.57" height="3.57"/><rect class="cls-1" y="32.34" width="3.57" height="3.57"/><rect class="cls-1" y="25.2" width="3.57" height="3.57"/><rect class="cls-1" y="18.07" width="3.57" height="3.57"/><rect class="cls-1" y="10.94" width="3.57" height="3.57"/><rect class="cls-1" y="3.81" width="3.57" height="3.57"/><rect class="cls-1" x="3.57" y="71.56" width="3.57" height="3.57"/><rect class="cls-1" x="3.57" y="64.43" width="3.57" height="3.57"/><rect class="cls-1" x="3.57" y="57.3" width="3.57" height="3.57"/><rect class="cls-1" x="3.57" y="50.17" width="3.57" height="3.57"/><rect class="cls-1" x="3.57" y="43.03" width="3.57" height="3.57"/><rect class="cls-1" x="3.57" y="35.9" width="3.57" height="3.57"/><rect class="cls-1" x="3.57" y="28.77" width="3.57" height="3.57"/><rect class="cls-1" x="3.57" y="21.64" width="3.57" height="3.57"/><rect class="cls-1" x="3.57" y="14.51" width="3.57" height="3.57"/><rect class="cls-1" x="3.57" y="7.37" width="3.57" height="3.57"/><rect class="cls-1" x="3.57" y="0.24" width="3.57" height="3.57"/></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 9"><defs><style>.cls-1{fill:#fff;}</style></defs><path class="cls-1" d="M22.56,4.36h-8a3,3,0,0,0-2.87-2.88V.4h-.29V1.48A3,3,0,0,0,8.49,4.36h-8v.28H8.49a3,3,0,0,0,2.87,2.88V8.6h.29V7.52a3,3,0,0,0,2.87-2.88h8ZM13.44,2.57a2.73,2.73,0,0,1,.8,1.79H12.38a.89.89,0,0,0-.73-.74V1.77A2.7,2.7,0,0,1,13.44,2.57Zm-3.87,0a2.7,2.7,0,0,1,1.79-.8V3.62a.88.88,0,0,0-.73.74H8.77A2.73,2.73,0,0,1,9.57,2.57Zm0,3.87a2.76,2.76,0,0,1-.8-1.8h1.86a.88.88,0,0,0,.73.74V7.23A2.74,2.74,0,0,1,9.57,6.44Zm3.87,0a2.74,2.74,0,0,1-1.79.79V5.38a.89.89,0,0,0,.73-.74h1.86A2.76,2.76,0,0,1,13.44,6.44Z"/></svg>

After

Width:  |  Height:  |  Size: 664 B

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 110.8 43.5" style="enable-background:new 0 0 110.8 43.5;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.5;fill:url(#SVGID_1_);}
.st1{fill:#FFFFFF;}
.st2{opacity:0.7;}
.st3{fill:#79AECC;}
.st4{fill:#DFF3FA;}
</style>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="6.0326" y1="21.8344" x2="106.0948" y2="21.8344">
<stop offset="0" style="stop-color:#0CA9D4;stop-opacity:0.7"/>
<stop offset="2.671185e-02" style="stop-color:#27ABD7;stop-opacity:0.6626"/>
<stop offset="6.063062e-02" style="stop-color:#42AEDA;stop-opacity:0.6151"/>
<stop offset="9.965897e-02" style="stop-color:#58B0DC;stop-opacity:0.5605"/>
<stop offset="0.1452" style="stop-color:#69B1DE;stop-opacity:0.4967"/>
<stop offset="0.2015" style="stop-color:#74B2DF;stop-opacity:0.4178"/>
<stop offset="0.2806" style="stop-color:#7BB3E0;stop-opacity:0.3072"/>
<stop offset="0.5" style="stop-color:#7DB3E0;stop-opacity:0"/>
<stop offset="0.7194" style="stop-color:#7BB3E0;stop-opacity:0.3072"/>
<stop offset="0.7985" style="stop-color:#74B2DF;stop-opacity:0.4178"/>
<stop offset="0.8548" style="stop-color:#69B1DE;stop-opacity:0.4967"/>
<stop offset="0.9003" style="stop-color:#58B0DC;stop-opacity:0.5605"/>
<stop offset="0.9394" style="stop-color:#42AEDA;stop-opacity:0.6151"/>
<stop offset="0.9733" style="stop-color:#27ABD7;stop-opacity:0.6626"/>
<stop offset="1" style="stop-color:#0CA9D4;stop-opacity:0.7"/>
</linearGradient>
<path class="st0" d="M104.6,39.6H7.5c-0.8,0-1.5-0.7-1.5-1.5V5.5c0-0.8,0.7-1.5,1.5-1.5h97.1c0.8,0,1.5,0.7,1.5,1.5v32.6
C106.1,38.9,105.4,39.6,104.6,39.6z"/>
<g>
<path class="st1" d="M102.5,38H9.6c-0.8,0-1.5-0.7-1.5-1.5V7.1c0-0.8,0.7-1.5,1.5-1.5h92.9c0.8,0,1.5,0.7,1.5,1.5v29.5
C104,37.4,103.3,38,102.5,38z M9.6,5.9c-0.7,0-1.2,0.5-1.2,1.2v29.5c0,0.7,0.5,1.2,1.2,1.2h92.9c0.7,0,1.2-0.5,1.2-1.2V7.1
c0-0.7-0.5-1.2-1.2-1.2H9.6z"/>
</g>
<g class="st2">
<g>
<path class="st3" d="M107,43.4H5.1c-2.4,0-4.4-2-4.4-4.4V4.6c0-2.4,2-4.4,4.4-4.4H107c2.4,0,4.4,2,4.4,4.4V39
C111.4,41.5,109.4,43.4,107,43.4z M5.1,2.3c-1.3,0-2.4,1.1-2.4,2.4V39c0,1.3,1.1,2.4,2.4,2.4H107c1.3,0,2.4-1.1,2.4-2.4V4.6
c0-1.3-1.1-2.4-2.4-2.4H5.1z"/>
</g>
</g>
<polygon class="st4" points="66.5,3.1 44,3.1 45.7,0.2 68.1,0.2 "/>
<polygon class="st4" points="66.5,43.4 44,43.4 45.7,40.6 68.1,40.6 "/>
<polygon class="st4" points="4.5,11.4 4.5,33.9 0.7,32.2 0.7,9.8 "/>
<polygon class="st4" points="111.4,11.4 111.4,33.9 107.6,32.2 107.6,9.8 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1 @@
<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 111 7"><defs><style>.cls-1{fill:#5eabea;}.cls-2{fill:#d0032c;}</style></defs><path id="_66" data-name="66" class="cls-1" d="M55.74,7.19A3.7,3.7,0,0,1,59.37,4l51.39-.45L59.37,3.15A3.7,3.7,0,0,1,55.74,0Z"/><path id="_66-2" data-name="66" class="cls-2" d="M55.74,0h0a3.7,3.7,0,0,1-3.63,3.15L0,3.59,52.11,4a3.7,3.7,0,0,1,3.63,3.15Z"/></svg>

After

Width:  |  Height:  |  Size: 424 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 276 B

After

Width:  |  Height:  |  Size: 150 KiB

View File

@ -30,7 +30,7 @@ const ackedAlarm = async (uuid) => {
:icon="['fas', 'exclamation-triangle']"
class="text-warning mr-2"
/>
<span>異常通知</span></span
<span>{{ $t("alarm.notify") }}</span></span
>
<small>
<span class="mr-4"
@ -45,18 +45,18 @@ const ackedAlarm = async (uuid) => {
</p>
<div class="divider my-2"></div>
<div>
<p>異常編號{{ alarm.uuid }}</p>
<p>{{ $t("alarm.number") }}{{ alarm.uuid }}</p>
<!-- <p>異常等級255</p> -->
<p>異常類別{{ alarm.alarmClass }}</p>
<p>設備名稱{{ alarm.full_name }}</p>
<p>異常訊息{{ alarm.msg }}</p>
<p>{{ $t("alarm.category") }}{{ alarm.alarmClass }}</p>
<p>{{ $t("alarm.device_name") }}{{ alarm.full_name }}</p>
<p>{{ $t("alarm.message") }}{{ alarm.msg }}</p>
</div>
<div class="card-actions mt-1 justify-end">
<button
class="btn btn-sm btn-success"
@click="() => ackedAlarm(alarm.uuid)"
>
確認
{{ $t("alarm.confirm") }}
</button>
</div>
</div>

View File

@ -1,6 +1,7 @@
<script setup>
import { onMounted, ref } from "vue";
import AlarmCards from "./AlarmCards.vue";
import { twMerge } from "tailwind-merge";
const showErr = ref(false);
const toggleErrIcon = () => {
@ -31,10 +32,17 @@ const toggleErrIcon = () => {
size="2x"
class="text-white menu-icon"
/>
<span class="text-white"> 顯示警告</span>
<span class="text-white"> {{ $t("alarm.title") }}</span>
</label>
</div>
<div class="drawer-side translate-y-16 max-h-[95vh] overflow-y-scroll overflow-x-hidden">
<div
:class="
twMerge(
'drawer-side translate-y-16 max-h-[95vh] overflow-x-hidden',
showErr ? 'overflow-y-scroll' : ''
)
"
>
<AlarmCards />
</div>
</div>

View File

@ -1,31 +1,57 @@
<script setup>
import * as echarts from "echarts";
import { onMounted, ref, markRaw } from "vue";
import { onMounted, ref, markRaw, watch, onBeforeUnmount } from "vue";
const props = defineProps({
option: Object,
class: String,
id: String,
option: Object, //
class: String, //
id: String, // ID
});
let chart = ref(null);
let chart = ref(null); //
let dom = ref(null); // DOM
//
function init() {
let echart = echarts;
chart.value = markRaw(echart.init(document.getElementById(props.id)));
chart.value.setOption(props.option);
if (dom.value) {
let echart = echarts;
chart.value = markRaw(echart.init(dom.value)); //
chart.value.setOption(props.option); //
}
}
//
onMounted(() => {
init();
if (!chart.value && dom.value) {
init();
}
});
// props.option
watch(
() => props.option,
(newOption) => {
if (chart.value) {
chart.value.setOption(newOption); //
}
},
{ deep: true }
);
//
onBeforeUnmount(() => {
if (chart.value) {
chart.value.dispose(); //
}
});
defineExpose({
chart,
chart, // chart
});
</script>
<template>
<div :id="id" :class="class" class="border p-3"></div>
<div :id="id" :class="class" ref="dom" style="height: 100%; width: 100%;"></div>
</template>
<style lang="scss" scoped></style>

View File

@ -17,7 +17,6 @@ import ForgeInfoModal from "./ForgeInfoModal.vue";
import useAlarmStore from "@/stores/useAlarmStore";
const props = defineProps({
fullScreen: Boolean,
initialData: Object,
cubeStyle: {
type: Object,
@ -116,25 +115,7 @@ const initForge = () => {
);
}
);
// viewer.addEventListener(
// Autodesk.Viewing.SELECTION_CHANGED_EVENT,
// (e) => {
// console.log("selection changed!", e, e.dbIdArray[0]);
// if (e.dbIdArray.length > 0) {
// const pos = getModalPosition(viewer, e.dbIdArray[0]);
// let currentValue = Object.values(subscribeData.value).find(
// ({ forge_dbid }) => forge_dbid === e.dbIdArray[0]
// );
// viewer.getProperties(e.dbIdArray[0], function (e) {
// console.log("Entire object response ", e);
// console.log("Properties ", e.properties);
// });
// console.log(Object.values(subscribeData.value), currentValue);
// // getCurrentInfoModalData(pos, currentValue);
// }
// }
// );
});
});
});
@ -148,11 +129,6 @@ onMounted(() => {
//
const currentInfoModalData = ref(null);
const isMobile = (pointerType) => {
// let flag =
// /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone/gi.test(
// navigator.userAgent
// );
// console.log("isMobile", flag);
return pointerType !== "mouse"; // is desktop
};
const getCurrentInfoModalData = (e, position, value) => {
@ -178,24 +154,10 @@ onUnmounted(() => {
<template>
<ForgeInfoModal :data="currentInfoModalData" />
<div
:class="
twMerge(
fullScreen
? 'absolute top-0 left-0 w-screen h-full z-0'
: 'w-full relative'
)
"
>
<div
id="forge-preview"
ref="forgeDom"
:class="
twMerge(
'relative w-full h-full',
fullScreen ? 'min-h-screen ' : 'min-h-[600px]'
)
"
class="relative w-full h-full min-h-full"
>
<div v-show="heat_bar_isShow" class="absolute z-10 heatbar">
<div class="w-40 flex justify-between text-[10px] mb-1">
@ -253,7 +215,7 @@ onUnmounted(() => {
<span v-else>{{ value.show_value }}</span>
</label>
</div>
</div>
</template>
<style lang="css">

View File

@ -5,6 +5,7 @@ import NavbarBuilding from "./NavbarBuilding.vue";
import Logo from "@/assets/img/logo.svg";
import useGetCookie from "@/hooks/useGetCookie";
import AlarmDrawer from "@/components/alarm/AlarmDrawer.vue";
import NavbarLang from "./NavbarLang.vue";
const user = ref("");
@ -19,7 +20,7 @@ const src = import.meta.env.MODE === "production" ? "./logo.svg" : Logo;
</script>
<template>
<header class="navbar bg-dark text-success py-2 mb-3 w-full relative z-50">
<header class="navbar bg-dark text-success w-full relative z-50">
<div class="navbar-start">
<div class="dropdown">
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
@ -44,8 +45,9 @@ const src = import.meta.env.MODE === "production" ? "./logo.svg" : Logo;
<NavbarItem />
</ul>
</div>
<router-link to="/dashboard" class="rounded-lg pl-14 text-xl">
<img :src="src" alt="logo" class="w-52" />
<router-link to="/dashboard" class="rounded-lg pl-4 text-2xl font-bold text-white flex items-center">
<img :src="src" alt="logo" class="w-8 me-1" />
CviLux Group
</router-link>
<NavbarBuilding />
</div>
@ -55,6 +57,9 @@ const src = import.meta.env.MODE === "production" ? "./logo.svg" : Logo;
</div>
<div class="navbar-end mr-4">
<ul class="menu-box">
<li>
<NavbarLang />
</li>
<li>
<AlarmDrawer />
</li>
@ -80,7 +85,7 @@ const src = import.meta.env.MODE === "production" ? "./logo.svg" : Logo;
<a
href="/logout"
class="flex flex-col justify-center items-center"
>登出</a
>{{ $t("sign_out") }}</a
>
</li>
</ul>
@ -102,17 +107,16 @@ const src = import.meta.env.MODE === "production" ? "./logo.svg" : Logo;
z-index: 0;
}
.menu-box .btn-group {
background: transparent;
width: 95px;
}
.menu-box li {
.menu-box > li {
position: relative;
}
.menu-box li:not(:last-child)::after {
.menu-box > li:not(:last-child)::after {
content: "";
position: absolute;
top: 20px;
@ -122,7 +126,7 @@ const src = import.meta.env.MODE === "production" ? "./logo.svg" : Logo;
display: block;
width: 25px;
height: 2px;
background-color: #7cedc1;
background-color: #93c0dc;
z-index: 0;
}

View File

@ -25,10 +25,10 @@ onMounted(() => {
<div
tabindex="0"
role="button"
class="text-white ml-8 text-2xl font-semiLight"
class="text-white ml-8 text-xl font-semiLight "
>
{{ store.selectedBuilding?.full_name }}
<font-awesome-icon :icon="['fas', 'angle-down']" />
<font-awesome-icon :icon="['fas', 'angle-down']" class="ml-1"/>
</div>
<ul
tabindex="0"

View File

@ -138,6 +138,6 @@ onMounted(() => {
</template>
<style lang="css" scoped>
.router-link-active.router-link-exact-active {
color: #7cedc1;
color: #93c0dc;
}
</style>

View File

@ -0,0 +1,44 @@
<script setup>
import { useI18n } from "vue-i18n";
import { ref, onMounted } from "vue";
const { locale } = useI18n(); // 使 I18n
const language = ref(locale.value)
//
const toggleLanguage = (lang) => {
locale.value = lang;
localStorage.setItem("CviLanguage", lang);
language.value = lang;
};
</script>
<template>
<div class="dropdown dropdown-bottom dropdown-end">
<button
tabindex="0"
type="button"
class="flex flex-col justify-center items-center btn-group"
>
<span
:class="`fi fi-${language} fis text-3xl rounded-full border-1 border-white`"
></span>
<span class="text-white">{{ $t("language") }}</span>
</button>
<ul
tabindex="0"
class="dropdown-content translate-y-2 z-[100] menu py-3 shadow rounded w-36 bg-[#4c625e] border text-center"
>
<li class="text-white" @click="toggleLanguage('tw')">
<span><span class="fi fi-tw"></span>繁體中文</span>
</li>
<li class="text-white" @click="toggleLanguage('cn')">
<span><span class="fi fi-cn"></span>简体中文</span>
</li>
<li class="text-white" @click="toggleLanguage('us')">
<span><span class="fi fi-us"></span>English</span>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped></style>

222
src/config/cn.json Normal file
View File

@ -0,0 +1,222 @@
{
"language": "简体中文",
"sign_out": "登出",
"log_in": "登入",
"account": "帐号",
"password": "密码",
"history": {
"title": "历史资料",
"building_name": "厂区",
"device_name": "设备名称",
"device_category": "设备类别",
"category": "类别",
"value": "数值",
"date": "记录时间",
"point": "点位",
"combinations": "常用组合",
"date_range": "日期区间",
"time_range": "时间区间",
"start_date": "起始日期",
"start_time": "起始时间",
"end_date": "结束日期",
"end_time": "结束时间"
},
"alarm": {
"title": "显示警告",
"notify": "异常通知",
"number": "异常编号",
"category": "异常类别",
"device_name": "设备名称",
"message": "异常讯息",
"confirm": "确认"
},
"alert": {
"query_title": "告警纪录查询",
"setting_title": "告警设定",
"offnormal": "未复归",
"normal": "已复归",
"unacked": "未确认",
"acked": "已确认",
"30days": "近30天",
"start_date": "起始日期",
"end_date": "结束日期",
"building_and_floor": "栋别-楼层",
"uuid": "异常ID",
"alarmClass": "异常类别",
"device_name": "设备名称",
"device_number": "设备编号",
"date": "发生日期",
"time": "发生时间",
"error_msg": "异常原因",
"ack_state": "Ack 确认",
"repair_order_number": "派工 / 维运单号",
"repair_order": "维修单",
"form_number": "表单编号",
"start_time": "预计开始时间",
"item": "项目",
"maintainance": "保养",
"repair": "维修",
"repair_item": "维修项目",
"repair_item_code": "维修项目代码(设备编号)",
"responsible_vendor": "负责厂商",
"status": "状态",
"not_completed": "未完成",
"completed": "已完成",
"worker_id": "工作人员编号",
"notice": "注意事项",
"result_description": "结果描述",
"upload_file": "上传文件",
"enable": "启用",
"not_enabled": "不启用",
"qualifications": "限定条件",
"upper_limit": "上限",
"lower_limit": "下限",
"highDelay": "上限持续秒数",
"lowDelay": "下限持续秒数",
"warning_method": "警示方式",
"warning_time": "警示时间",
"operation": "功能",
"alarm_settings": "异常设定",
"time_setting": "时间设定",
"yes": "是",
"no": "否",
"no_notify": "无通知",
"notify_name": "姓名",
"notify_phone": "手机号码",
"notify_email": "email",
"notify_items": "通知项目",
"notify_list": "通知名单",
"choose": "选择"
},
"operation": {
"title": "运维管理",
"project": "项目",
"location": "位置",
"uuid": "异常ID",
"form_number": "表单编号",
"device_name": "设备名称",
"status": "狀態",
"staff": "处理人员",
"start_time": "预计开始时间",
"upload": "档案上传",
"finish_time": "完成时间",
"operation": "功能",
"vendor": "厂商",
"contact_person": "联络人",
"phone": "电话",
"email": "email",
"created_at": "建立日期",
"maintainance": "保养",
"repair": "维修",
"company_info": "厂商资料",
"repair_item": "维修项目",
"repair_item_code": "维修项目代码(设备编号)",
"responsible_vendor": "负责厂商",
"not_completed": "未完成",
"completed": "已完成",
"worker_id": "工作人员编号",
"notice": "注意事项",
"result_description": "结果描述",
"upload_file": "上传文件",
"name": "姓名",
"city": "城市",
"address": "地址",
"tax_id_number": "统一编号",
"remark": "备注",
"date": "日期",
"serial": "单号",
"today": "今天",
"yesterday": "昨天",
"start_created_at": "起始日期",
"end_created_at": "结束日期",
"enter_text": "请输入文字",
"enter_serial": "请输入单号"
},
"graphManagement": {
"title": "图资管理",
"category": "图资类别",
"new_category": "新类别",
"index": "编号",
"oriOrgName": "档案",
"operation": "功能"
},
"assetManagement": {
"title": "资产管理",
"add_category": "新增类别",
"system_name": "名称",
"system_value": "代号",
"system_parent": "所属大类",
"device_number": "设备编号",
"device_name": "设备名称",
"asset_number": "资产编号",
"floor": "设备位置",
"add_floor": "新增楼层",
"add_floor_text": "须先上传楼层地图",
"device_coordinate": "图面标识",
"brand_and_modal": "品牌 / 型号",
"brand": "品牌",
"modal": "型号",
"company_and_contact": "厂商 / 联络人",
"company": "负责厂商",
"buying_date": "建置时间",
"oriFile": "档案上传",
"created_at": "建立时间",
"operation": "功能",
"device_list": "设备列表",
"edit_device": "编辑设备",
"add_device": "新增设备",
"operate_text": "显示名称",
"fill_text": "请由系统人员填写",
"equipment_point": "设备点位",
"add_sensor": "新增感测器",
"associated_device": "关联设备",
"choose": "选择",
"index": "编号",
"floor_plan": "平面图"
},
"accountManagement": {
"account_title": "帐号管理",
"role_title": "角色管理",
"index": "编号",
"name": "姓名",
"account": "帐号",
"password": "密码",
"role": "角色",
"role_name": "角色名称",
"role_permissions": "角色权限",
"role_permissions_setting": "角色权限设定",
"permission_name": "权限名称",
"basic_permissions": "基础权限",
"production_permissions": "生产设定权限",
"email": "email",
"phone": "手机",
"created_at": "建立时间",
"operation": "功能",
"name_placeholder": "请输入使用者名称",
"role_placeholder": "请输入角色名称",
"change_password": "变更密码",
"choose": "选择"
},
"button": {
"add": "新增",
"cancel": "取消",
"query": "查询",
"search": "搜索",
"view": "查看",
"reset": "重置",
"export": "导出",
"enter_text": "输入文字后按下 Enter",
"required": "必填",
"submit": "确定",
"edit": "修改",
"delete": "删除",
"deselect_all": "取消全选",
"select_all": "全选",
"phone_format": "请输入正确电话号码格式",
"email_format": "请输入正确 Email 地址",
"password_format": "密码长度至少8码必须包含英文及数字",
"start_time_placeholder": "请输入预计开始日期",
"rename": "重新命名",
"download": "下载"
}
}

222
src/config/tw.json Normal file
View File

@ -0,0 +1,222 @@
{
"language": "繁體中文",
"sign_out": "登出",
"log_in": "登入",
"account": "帳號",
"password": "密碼",
"history": {
"title": "歷史資料",
"building_name": "廠區",
"device_name": "設備名稱",
"device_category": "設備類別",
"category": "類別",
"value": "數值",
"date": "記錄時間",
"point": "點位",
"combinations": "常用組合",
"date_range": "日期區間",
"time_range": "時間區間",
"start_date": "起始日期",
"start_time": "起始時間",
"end_date": "結束日期",
"end_time": "結束時間"
},
"alarm": {
"title": "顯示警告",
"notify": "異常通知",
"number": "異常編號",
"category": "異常類別",
"device_name": "設備名稱",
"message": "異常訊息",
"confirm": "確認"
},
"alert": {
"query_title": "告警紀錄查詢",
"setting_title": "告警設定",
"offnormal": "未復歸",
"normal": "已復歸",
"unacked": "未確認",
"acked": "已確認",
"30days": "近30天",
"start_date": "起始日期",
"end_date": "結束日期",
"building_and_floor": "棟別-樓層",
"uuid": "異常ID",
"alarmClass": "異常類別",
"device_name": "設備名稱",
"device_number": "設備編號",
"date": "發生日期",
"time": "發生時間",
"error_msg": "異常原因",
"ack_state": "Ack 確認",
"repair_order_number": "派工 / 維運單號",
"repair_order": "維修單",
"form_number": "表單編號",
"start_time": "預計開始時間",
"item": "項目",
"maintainance": "保養",
"repair": "維修",
"repair_item": "維修項目",
"repair_item_code": "維修項目代碼(設備編號)",
"responsible_vendor": "負責廠商",
"status": "狀態",
"not_completed": "未完成",
"completed": "已完成",
"worker_id": "工作人員編號",
"notice": "注意事項",
"result_description": "結果描述",
"upload_file": "上傳檔案",
"enable": "啟用",
"not_enabled": "不啟用",
"qualifications": "限定條件",
"upper_limit": "上限",
"lower_limit": "下限",
"highDelay": "上限持續秒數",
"lowDelay": "下限持續秒數",
"warning_method": "警示方式",
"warning_time": "警示時間",
"operation": "功能",
"alarm_settings": "異常設定",
"time_setting": "時間設定",
"yes": "是",
"no": "否",
"no_notify": "無通知",
"notify_name": "姓名",
"notify_phone": "手機號碼",
"notify_email": "email",
"notify_items": "通知項目",
"notify_list": "通知名單",
"choose": "選擇"
},
"operation": {
"title": "運維管理",
"project": "項目",
"location": "位置",
"uuid": "異常ID",
"form_number": "表單編號",
"device_name": "設備名稱",
"status": "狀態",
"staff": "處理人員",
"start_time": "預計開始時間",
"upload": "檔案上傳",
"finish_time": "完成時間",
"operation": "功能",
"vendor": "廠商",
"contact_person": "聯絡人",
"phone": "電話",
"email": "email",
"created_at": "建立日期",
"maintainance": "保養",
"repair": "維修",
"company_info": "廠商資料",
"repair_item": "維修項目",
"repair_item_code": "維修項目代碼(設備編號)",
"responsible_vendor": "負責廠商",
"not_completed": "未完成",
"completed": "已完成",
"worker_id": "工作人員編號",
"notice": "注意事項",
"result_description": "結果描述",
"upload_file": "上傳檔案",
"name": "姓名",
"city": "城市",
"address": "地址",
"tax_id_number": "統一編號",
"remark": "備注",
"date": "日期",
"serial": "單號",
"today": "今天",
"yesterday": "昨天",
"start_created_at": "起始日期",
"end_created_at": "結束日期",
"enter_text": "請輸入文字",
"enter_serial": "請輸入單號"
},
"graphManagement": {
"title": "圖資管理",
"category": "圖資類別",
"new_category": "新類別",
"index": "編號",
"oriOrgName": "檔案",
"operation": "功能"
},
"assetManagement": {
"title": "資產管理",
"add_category": "新增類別",
"system_name": "名稱",
"system_value": "代號",
"system_parent": "所屬大類",
"device_number": "設備編號",
"device_name": "設備名稱",
"asset_number": "資產編號",
"floor": "設備位置",
"add_floor": "新增樓層",
"add_floor_text": "須先上傳樓層地圖",
"device_coordinate": "圖面標識",
"brand_and_modal": "品牌 / 型號",
"brand": "品牌",
"modal": "型號",
"company_and_contact": "廠商 / 聯絡人",
"company": "負責廠商",
"buying_date": "購買日期",
"oriFile": "檔案上傳",
"created_at": "建立時間",
"operation": "功能",
"device_list": "設備列表",
"edit_device": "編輯設備",
"add_device": "新增設備",
"operate_text": "顯示名稱",
"fill_text": "請由系統人員填寫",
"equipment_point": "設備點位",
"add_sensor": "新增感測器",
"associated_device": "關聯設備",
"choose": "選擇",
"index": "編號",
"floor_plan": "平面圖"
},
"accountManagement": {
"account_title": "帳號管理",
"role_title": "角色管理",
"index": "編號",
"name": "姓名",
"account": "帳號",
"password": "密碼",
"role": "角色",
"role_name": "角色名稱",
"role_permissions": "角色權限",
"role_permissions_setting": "角色權限設定",
"permission_name": "權限名稱",
"basic_permissions": "基礎權限",
"production_permissions": "生產設定權限",
"email": "email",
"phone": "手機",
"created_at": "建立時間",
"operation": "功能",
"name_placeholder": "請輸入使用者名稱",
"role_placeholder": "請輸入角色名稱",
"change_password": "變更密碼",
"choose": "選擇"
},
"button": {
"add": "新增",
"cancel": "取消",
"query": "查詢",
"search": "搜尋",
"view": "查看",
"reset": "重置",
"export": "匯出",
"enter_text": "輸入文字後按下 Enter",
"required": "必填",
"submit": "確定",
"edit": "修改",
"delete": "刪除",
"deselect_all": "取消全選",
"select_all": "全選",
"phone_format": "請輸入正確電話號碼格式",
"email_format": "請輸入正確的 Email 地址",
"password_format": "密碼長度至少8碼必須包含英文及數字",
"start_time_placeholder": "請輸入預計開始日期",
"rename": "重新命名",
"download": "下載"
}
}

222
src/config/us.json Normal file
View File

@ -0,0 +1,222 @@
{
"language": "English",
"sign_out": "Sign out",
"log_in": "Log in",
"account": "Account",
"password": "Password",
"history": {
"title": "Historical Data",
"building_name": "Building",
"device_name": "Device Name",
"device_category": "Device Category",
"category": "Category",
"value": "Value",
"date": "Record Time",
"point": "Point",
"combinations": "Common combinations",
"date_range": "Date range",
"time_range": "Time interval",
"start_date": "Start date",
"start_time": "Start time",
"end_date": "End date",
"end_time": "End time"
},
"alarm": {
"title": "Warning",
"notify": "Notification",
"number": "ID",
"category": "Category",
"device_name": "Device name",
"message": "Message",
"confirm": "Confirm"
},
"alert": {
"query_title": "Alarm record query",
"setting_title": "Alarm settings",
"offnormal": "off normal",
"normal": "normal",
"unacked": "unacked",
"acked": "acked",
"30days": "Last 30 days",
"start_date": "Start date",
"end_date": "End date",
"building_and_floor": "Building - floor",
"uuid": "Exception ID",
"alarmClass": "Exception Category",
"device_name": "Device name",
"device_number": "Device number",
"date": "Occurrence date",
"time": "Occurrence time",
"error_msg": "Abnormal cause",
"ack_state": "Ack Confirm",
"repair_order_number": "Repair order number",
"repair_order": "Repair order",
"form_number": "Form Number",
"start_time": "Estimated Start Time",
"item": "Item",
"maintainance": "Maintainance",
"repair": "Repair",
"repair_item": "Repair Item",
"repair_item_code": "Repair Item Code (Device Number)",
"responsible_vendor": "Responsible Vendor",
"status": "Status",
"not_completed": "Not completed",
"completed": "Completed",
"worker_id": "Worker ID",
"notice": "Notice",
"result_description": "Result Description",
"upload_file": "Upload File",
"enable": "Enable",
"not_enabled": "Not enabled",
"qualifications": "Qualifications",
"upper_limit": "Upper limit",
"lower_limit": "Lower limit",
"highDelay": "Max duration (s)",
"lowDelay": "Min duration (s)",
"warning_method": "Warning method",
"warning_time": "Warning time",
"operation": "Function",
"alarm_settings": "Abnormal alarm settings",
"time_setting": "Time setting",
"yes": "yes",
"no": "no",
"no_notify": "No notification",
"notify_name": "Name",
"notify_phone": "Phone number",
"notify_email": "email",
"notify_items": "Notification items",
"notify_list": "Notification list",
"choose": "choose"
},
"operation": {
"title": "Operation and maintenance management",
"project": "Project",
"location": "Location",
"uuid": "Exception ID",
"form_number": "Form Number",
"device_name": "Device name",
"status": "Status",
"staff": "Staff",
"start_time": "Estimated start time",
"upload": "File upload",
"finish_time": "Completion time",
"operation": "Function",
"vendor": "company",
"contact_person": "Contact person",
"phone": "Phone",
"email": "email",
"created_at": "Creation date",
"maintainance": "Maintainance",
"repair": "Repair",
"company_info": "Company Info",
"repair_item": "Repair Item",
"repair_item_code": "Repair Item Code (Device Number)",
"responsible_vendor": "Responsible Vendor",
"not_completed": "Not completed",
"completed": "Completed",
"worker_id": "Worker ID",
"notice": "Notice",
"result_description": "Result Description",
"upload_file": "Upload File",
"name": "Name",
"city": "City",
"address": "Address",
"tax_id_number": "tax ID number",
"remark": "Remark",
"date": "Date",
"serial": "Order number",
"today": "Today",
"yesterday": "Yesterday",
"start_created_at": "Start date",
"end_created_at": "End date",
"enter_text": "Please enter text",
"enter_serial": "Please enter the order number"
},
"graphManagement": {
"title": "Data and Publication Management",
"category": "Category",
"new_category": "New category",
"index": "serial number",
"oriOrgName": "file",
"operation": "Function"
},
"assetManagement": {
"title": "Asset Management",
"add_category": "Add category",
"system_name": "Name",
"system_value": "Code",
"system_parent": "Category",
"device_number": "Device number",
"device_name": "Device name",
"asset_number": "Asset number",
"floor": "Location",
"add_floor": "Add floor",
"add_floor_text": "Floor map must be uploaded first",
"device_coordinate": "Coordinate",
"brand_and_modal": "Brand/Model",
"brand": "Brand",
"modal": "Model",
"company_and_contact": "Company/Contact Person",
"company": "Company",
"buying_date": "Purchase time",
"oriFile": "File upload",
"created_at": "Creation time",
"operation": "Function",
"device_list": "Device list",
"edit_device": "Edit device",
"add_device": "Add device",
"operate_text": "Display name",
"fill_text": "Please fill it in by system personnel",
"equipment_point": "Equipment point",
"add_sensor": "Add new sensor",
"associated_device": "Associated devices",
"choose": "Choose",
"index": "serial number",
"floor_plan": "Floor plan"
},
"accountManagement": {
"account_title": "Account Management",
"role_title": "Role Management",
"index": "serial number",
"name": "Name",
"account": "Account",
"password": "Password",
"role": "Role",
"role_name": "Role name",
"role_permissions": "Role permissions",
"role_permissions_setting": "Role permissions settings",
"permission_name": "Permission name",
"basic_permissions": "Basic permissions",
"production_permissions": "Production setting permissions",
"email": "email",
"phone": "Phone",
"created_at": "Created time",
"operation": "Function",
"name_placeholder": "Please enter user name",
"role_placeholder": "Please enter the role name",
"change_password": "Change password",
"choose": "Choose"
},
"button": {
"add": "Add",
"cancel": "Cancel",
"query": "Query",
"search": "Search",
"view": "View",
"reset": "Reset",
"export": "Export",
"enter_text": "After entering text, press Enter",
"required": "Required",
"submit": "Submit",
"edit": "Edit",
"delete": "Delete",
"deselect_all": "Deselect all",
"select_all": "Select all",
"phone_format": "Please enter the correct phone number format",
"email_format": "Please enter correct email address",
"password_format": "The password must be at least 8 characters long and must contain English and numbers.",
"start_time_placeholder": "Please enter expected start date",
"rename": "Rename",
"download": "Download"
}
}

View File

@ -5,6 +5,10 @@ import "./assets/btn.css";
import "./assets/pagination.css";
import { createApp } from "vue";
import { createI18n } from "vue-i18n";
import tw from "./config/tw.json";
import cn from "./config/cn.json";
import us from "./config/us.json";
import Antd from "ant-design-vue";
import { createPinia } from "pinia";
import App from "./App.vue";
@ -13,17 +17,32 @@ import "virtual:svg-icons-register";
// 引入项目中的全部全局组件
import SvgIcon from "@/components/svgIcon.vue";
import library from "./fontawsomeIconRegister";
import "flag-icons/css/flag-icons.min.css";
/* import font awesome icon component */
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { focusPlugin } from "@/directives/focusPlugin";
import { draggable } from "@/directives/draggable";
const messages = {
tw,
cn,
us,
};
const storedLanguage = localStorage.getItem("CviLanguage") || "us";
const i18n = createI18n({
legacy: false,
locale: storedLanguage,
fallbackLocale: 'us',
messages,
});
const app = createApp(App);
app.use(createPinia());
app.use(router);
app.use(Antd);
app.use(i18n);
// 组装成一个对象
const allGlobalComponents = { SvgIcon, FontAwesomeIcon };

View File

@ -7,6 +7,7 @@ import AccountManagement from "@/views/accountManagement/AccountManagement.vue";
import AssetManagement from "@/views/AssetManagement/AssetManagement.vue";
import AlertManagement from "@/views/alert/AlertManagement.vue";
import ProductSetting from "@/views/productSetting/ProductSetting.vue";
import EnergyManagement from "@/views/energyManagement/EnergyManagement.vue";
import Login from "@/views/login/Login.vue";
import useUserInfoStore from "@/stores/useUserInfoStore";
import useGetCookie from "@/hooks/useGetCookie";
@ -63,6 +64,11 @@ const router = createRouter({
name: "productSetting",
component: ProductSetting,
},
{
path: "/energyManagement",
name: "energyManagement",
component: EnergyManagement,
},
{
path: "/mytestfile/mjm",
name: "mytestfile",

View File

@ -4,7 +4,9 @@ import AssetTable from "./components/AssetTable.vue";
</script>
<template>
<h1 class="text-2xl font-extrabold mb-2">資產管理</h1>
<h1 class="text-2xl font-extrabold mb-2">
{{ $t("assetManagement.title") }}
</h1>
<AssetSubList />
<AssetTable />
</template>

View File

@ -52,12 +52,12 @@ const deleteItem = async (id) => {
<template>
<div class="mt-4">
<AssetSubListAddModal
<!-- <AssetSubListAddModal
:openModal="openModal"
:onCancel="onCancel"
:getData="getSubList"
:editRecord="editRecord"
/>
/> -->
<ButtonConnectedGroup
:items="items"
:onclick="

View File

@ -3,7 +3,8 @@ import { ref, defineProps, onMounted, watch } from "vue";
import * as yup from "yup";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import { getAssetMainList, postAssetSubList } from "@/apis/asset";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps({
openModal: Function,
onCancel: Function,
@ -18,9 +19,9 @@ const getMainSystems = async () => {
};
let subSysSchema = yup.object({
system_key: yup.string().required("必填"), //
system_value: yup.string().required("必填"), //
system_parent_id: yup.number().required("必填"), // id
system_key: yup.string().required(t("button.required")), //
system_value: yup.string().required(t("button.required")), //
system_parent_id: yup.number().required(t("button.required")), // id
});
const { formErrorMsg, handleSubmit, handleErrorReset } =
@ -73,11 +74,11 @@ const onOk = async () => {
<template>
<button class="btn btn-sm btn-success mr-3" @click.stop.prevent="openModal">
<font-awesome-icon :icon="['fas', 'plus']" />新增
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
</button>
<Modal
id="asset_add_sub_item"
title="新增類別"
:title="t('assetManagement.add_category')"
:open="open"
:onCancel="onCancel"
width="300"
@ -85,7 +86,7 @@ const onOk = async () => {
<template #modalContent>
<form ref="form" class="mt-5 flex flex-col items-center">
<Input name="system_key" :value="formState">
<template #topLeft>名稱</template>
<template #topLeft>{{ $t("assetManagement.system_name") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.system_key }}
@ -93,7 +94,7 @@ const onOk = async () => {
>
</Input>
<Input name="system_value" :value="formState">
<template #topLeft>代號</template>
<template #topLeft>{{ $t("assetManagement.system_value") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.system_value }}
@ -108,7 +109,9 @@ const onOk = async () => {
:value="formState"
selectClass="border-info focus-within:border-info"
>
<template #topLeft>所屬大類</template>
<template #topLeft>{{
$t("assetManagement.system_parent")
}}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.system_parent_id }}
@ -123,14 +126,14 @@ const onOk = async () => {
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
取消
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.prevent="onOk"
>
確定
{{ $t("button.submit") }}
</button>
</template>
</Modal>

View File

@ -1,12 +1,13 @@
<script setup>
import { getAssetList, getAssetSingle, deleteAssetItem } from "@/apis/asset";
import { onMounted, ref, watch, inject } from "vue";
import { onMounted, ref, watch, inject, computed } from "vue";
import useSearchParam from "@/hooks/useSearchParam";
import AssetTableAddModal from "./AssetTableAddModal.vue";
import { getOperationCompanyList } from "@/apis/operation";
import { getAssetFloorList } from "@/apis/asset";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
@ -50,58 +51,60 @@ onMounted(async () => {
getAssetData();
});
const columns = [
const columns = computed(() => [
{
title: "設備編號",
title: t("assetManagement.device_number"),
key: "device_number",
class: "break-all",
},
{
title: "設備名稱",
title: t("assetManagement.device_name"),
key: "full_name",
filter: true,
class: "break-all",
},
{
title: "資產編號",
title: t("assetManagement.asset_number"),
key: "asset_number",
},
{
title: "設備位置",
title: t("assetManagement.floor"),
key: "floor",
filter: true,
sort: true,
},
{
title: "圖面標識",
title: t("assetManagement.device_coordinate"),
key: "device_coordinate",
},
{
title: "品牌 / 型號",
title: t("assetManagement.brand_and_modal"),
key: "brandAndModal",
},
{
title: "廠商 / 聯絡人",
title: t("assetManagement.company_and_contact"),
key: "companyAndContact",
},
{
title: "建置時間",
title: t("assetManagement.buying_date"),
key: "buying_date",
sort: true,
},
{
title: "檔案上傳",
title: t("assetManagement.oriFile"),
key: "oriFile",
},
{
title: "建立時間",
title: t("assetManagement.created_at"),
key: "created_at",
sort: true,
},
{
title: "功能",
title: t("assetManagement.operation"),
key: "operation",
width: 200,
},
];
]);
watch(
() => searchParams,
@ -156,7 +159,7 @@ const remove = async (id) => {
<template>
<div class="flex justify-start items-center mt-10">
<h3 class="text-xl mr-5">設備列表</h3>
<h3 class="text-xl mr-5">{{ $t("assetManagement.device_list") }}</h3>
<AssetTableAddModal
:openModal="openModal"
:onCancel="onCancel"
@ -208,13 +211,13 @@ const remove = async (id) => {
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => edit(record.main_id)"
>
修改
{{ $t("button.edit") }}
</button>
<button
class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => remove(record.main_id)"
>
刪除
{{ $t("button.delete") }}
</button>
</template>
<template v-else>

View File

@ -103,11 +103,11 @@ const closeModal = () => {
<template>
<button class="btn btn-sm btn-success mr-3" @click.stop.prevent="openModal">
<font-awesome-icon :icon="['fas', 'plus']" />新增
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
</button>
<Modal
id="asset_add_table_item"
:title="editRecord?.main_id ? '編輯設備' : '新增設備'"
:title="editRecord?.main_id ? $t('assetManagement.edit_device') : $t('assetManagement.add_device')"
:open="open"
:onCancel="closeModal"
width="1600"
@ -130,14 +130,14 @@ const closeModal = () => {
class="btn btn-outline-success mr-2"
@click.prevent="closeModal"
>
取消
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.prevent="onOk"
>
確定
{{ $t("button.submit") }}
</button>
</template>
</Modal>

View File

@ -7,6 +7,8 @@ import { getOperationCompanyList } from "@/apis/operation";
import useSearchParam from "@/hooks/useSearchParam";
import OperationTableModal from "@/views/operation/components/OperationTableModal.vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const { searchParams, changeParams } = useSearchParam();
@ -51,7 +53,7 @@ const buying_date = ref([
key: "buying_date",
value: "",
dateFormat: "yyyy-MM-dd",
placeholder: "購買日期",
placeholder: t("assetManagement.buying_date"),
},
]);
@ -99,7 +101,7 @@ const openCompanyAddModal = () => {
<template>
<!-- information -->
<Input :value="formState" width="270" name="full_name">
<template #topLeft>設備名稱</template>
<template #topLeft>{{ $t("assetManagement.device_name") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.full_name }}
@ -107,7 +109,7 @@ const openCompanyAddModal = () => {
></Input
>
<Input :value="formState" width="270" name="operate_text">
<template #topLeft>顯示名稱</template>
<template #topLeft>{{ $t("assetManagement.operate_text") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.operate_text }}
@ -115,7 +117,9 @@ const openCompanyAddModal = () => {
></Input
>
<Input :value="formState" width="270" name="device_number">
<template #topLeft>Tag_Name (請由系統人員填寫)</template>
<template #topLeft
>Tag_Name ({{ $t("assetManagement.fill_text") }})</template
>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.device_number }}
@ -136,7 +140,7 @@ const openCompanyAddModal = () => {
name="device_coordinate"
:disabled="true"
>
<template #topLeft>平面圖座標</template>
<template #topLeft>{{ $t("assetManagement.device_coordinate") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.device_coordinate }}
@ -144,7 +148,7 @@ const openCompanyAddModal = () => {
></Input
>
<Input :value="formState" width="270" name="asset_number">
<template #topLeft>固定資產編號</template>
<template #topLeft>{{ $t("assetManagement.asset_number") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.asset_number }}
@ -152,7 +156,7 @@ const openCompanyAddModal = () => {
></Input
>
<DateGroup :items="buying_date" width="270" :withLine="false">
<template #topLeft>購買日期</template>
<template #topLeft>{{ $t("assetManagement.buying_date") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.buying_date }}
@ -160,7 +164,7 @@ const openCompanyAddModal = () => {
>
</DateGroup>
<Input :value="formState" width="270" name="brand">
<template #topLeft>品牌</template>
<template #topLeft>{{ $t("assetManagement.brand") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.brand }}
@ -168,7 +172,7 @@ const openCompanyAddModal = () => {
></Input
>
<Input :value="formState" width="270" name="device_model">
<template #topLeft>型號</template>
<template #topLeft>{{ $t("assetManagement.modal") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.device_model }}
@ -184,7 +188,7 @@ const openCompanyAddModal = () => {
:options="companyOptions"
:required="true"
>
<template #topLeft>負責廠商</template>
<template #topLeft>{{ $t("assetManagement.company") }}</template>
</Select>
<OperationTableModal type="asset" />
<button
@ -192,7 +196,7 @@ const openCompanyAddModal = () => {
class="btn btn-success btn-sm ml-2 mt-7"
@click="openCompanyAddModal"
>
新增
{{ $t("button.add") }}
</button>
</div>
<Upload
@ -203,7 +207,7 @@ const openCompanyAddModal = () => {
class="col-span-2"
:baseUrl="FILE_BASEURL"
>
<template #topLeft>上傳檔案</template>
<template #topLeft>{{ $t("assetManagement.oriFile") }}</template>
</Upload>
<AssetTableModalLeftInfoIoT />
</template>

View File

@ -1,11 +1,12 @@
<script setup>
import { getAssetIOTList } from "@/apis/asset";
import { ref, onMounted, inject, watch } from "vue";
import { ref, computed, inject, watch } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { formState } = inject("asset_table_modal_form");
const tableColumns = [
const tableColumns = computed(() => [
{
title: "編號",
title: t("assetManagement.index"),
key: "index",
},
{
@ -13,7 +14,7 @@ const tableColumns = [
key: "tag",
},
{
title: "名稱",
title: t("assetManagement.system_name"),
key: "full_name",
},
// {
@ -21,15 +22,15 @@ const tableColumns = [
// key: "status",
// },
{
title: "功能",
title: t("assetManagement.operation"),
key: "operation",
width: 100,
},
];
]);
const modalColumns = [
const modalColumns = computed(() => [
{
title: "勾選",
title: t("assetManagement.choose"),
key: "check",
},
{
@ -37,10 +38,10 @@ const modalColumns = [
key: "tag",
},
{
title: "名稱",
title: t("assetManagement.system_name"),
key: "full_name",
},
];
]);
// TODO:
const iotData = ref([]);
@ -111,13 +112,15 @@ const deleteItem = (id) => {
<!-- IoT -->
<div class="flex flex-col mt-8 col-span-2">
<div class="flex justify-between items-center">
<span class="label-text-alt text-lg">設備點位</span>
<span class="label-text-alt text-lg">
{{ $t("assetManagement.equipment_point") }}
</span>
<button
type="button"
class="btn btn-sm btn-success"
@click.stop.prevent="openModal"
>
新增感測器
{{ $t("assetManagement.add_sensor") }}
</button>
</div>
<Table
@ -147,7 +150,7 @@ const deleteItem = (id) => {
</div>
<Modal
id="asset_add_IoT_item"
title="關聯設備"
:title="t('assetManagement.associated_device')"
:onCancel="onCancel"
width="900"
>
@ -175,14 +178,14 @@ const deleteItem = (id) => {
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
取消
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.prevent="onOk"
>
確定
{{ $t("button.submit") }}
</button>
</template></Modal
>

View File

@ -5,7 +5,8 @@ import { getAssetFloorList, postAssetFloor } from "@/apis/asset";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import * as yup from "yup";
import { twMerge } from "tailwind-merge";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const { updateRightFields, formErrorMsg, formState } = inject(
@ -109,8 +110,8 @@ const FloorFormState = ref({
});
const floorScheme = yup.object({
building_tag: yup.string().required("必填"),
full_name: yup.string().required("必填"),
building_tag: yup.string().required(t("button.required")),
full_name: yup.string().required(t("button.required")),
floorFile: yup.array(),
});
@ -166,14 +167,14 @@ const onCancel = () => {
:options="floors"
:isBottomLabelExist="false"
>
<template #topLeft>樓層</template>
<template #topLeft>{{ t("assetManagement.floor") }}</template>
</Select>
<button
type="button"
class="btn btn-success mt-10 ml-5"
@click="openModal"
>
新增樓層
{{ t("assetManagement.add_floor") }}
</button>
</div>
<!-- <button type="button" class="btn btn-success mt-10">確認座標</button> -->
@ -194,14 +195,19 @@ const onCancel = () => {
v-if="!currentFloor?.floor_map_url"
class="absolute top-0 left-0 flex justify-center items-center min-h-[500px] w-full border border-stone-900 shadow-lg bg-sub-success bg-opacity-25 rounded-md"
>
<p class="text-2xl">須先上傳樓層地圖</p>
<p class="text-2xl">{{ t("assetManagement.add_floor_text") }}</p>
</div>
</div>
<Modal id="asset_add_floor" title="平面圖" :onCancel="onCancel" width="400">
<Modal
id="asset_add_floor"
:title="t('assetManagement.floor_plan')"
:onCancel="onCancel"
width="400"
>
<template #modalContent>
<form ref="form">
<Input :value="FloorFormState" width="270" name="full_name">
<template #topLeft>名稱</template>
<template #topLeft>{{ $t("assetManagement.system_name") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ floorFormErrorMsg.full_name }}
@ -215,7 +221,7 @@ const onCancel = () => {
:multiple="false"
class="col-span-2"
>
<template #topLeft>上傳檔案</template>
<template #topLeft>{{ $t("assetManagement.oriFile") }}</template>
</Upload>
</form>
</template>
@ -225,10 +231,10 @@ const onCancel = () => {
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
取消
{{ $t("button.cancel") }}
</button>
<button type="submit" class="btn btn-outline-success" @click="onOk">
確定
{{ $t("button.submit") }}
</button>
</template></Modal
>

View File

@ -2,36 +2,39 @@
import ButtonGroup from "@/components/customUI/ButtonGroup.vue";
import Account from "./components/Account.vue";
import Role from "./components/Role.vue";
import { computed, onMounted, ref, provide, onBeforeMount } from "vue";
import { computed, watch, onBeforeMount } from "vue";
import useActiveBtn from "@/hooks/useActiveBtn";
import { useI18n } from "vue-i18n";
const { t, locale } = useI18n();
const changeComponent = (e, item) => {
changeActiveBtn(item);
};
const { items, changeActiveBtn, setItems } = useActiveBtn();
onBeforeMount(() => {
const initializeItems = () => {
setItems([
{
title: "帳號管理",
title: t("accountManagement.account_title"),
key: "account",
active: true,
component: Account,
},
{
title: "角色管理",
title: t("accountManagement.role_title"),
key: "role",
active: false,
component: Role,
},
// {
// title: "",
// key: "auth",
// active: false,
// component: Auth,
// },
]);
};
onBeforeMount(() => {
initializeItems();
});
watch(locale, () => {
initializeItems();
});
const activeTab = computed(() => {
@ -40,7 +43,9 @@ const activeTab = computed(() => {
</script>
<template>
<h1 class="text-2xl font-extrabold mb-2">帳號管理</h1>
<h1 class="text-2xl font-extrabold mb-2">
{{ $t("accountManagement.account_title") }}
</h1>
<ButtonGroup
:items="items"
:withLine="true"

View File

@ -6,46 +6,47 @@ import {
getAccountOneUser,
delAccount,
} from "@/apis/account";
import { onMounted, ref, inject } from "vue";
import { onMounted, ref, inject, computed } from "vue";
import AccountPasswordModal from "./AccountPasswordModal.vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const columns = [
const columns = computed(() => [
{
title: "編號",
title: t("accountManagement.index"),
key: "index",
},
{
title: "姓名",
title: t("accountManagement.name"),
key: "full_name",
filter: true,
},
{
title: "帳號",
title: t("accountManagement.account"),
key: "account",
},
{
title: "角色",
title: t("accountManagement.role"),
key: "role_full_name",
},
{
title: "email",
title: t("accountManagement.email"),
key: "email",
},
{
title: "手機",
title: t("accountManagement.phone"),
key: "phone",
},
{
title: "建立時間",
title: t("accountManagement.created_at"),
key: "created_at",
},
{
title: "操作",
title: t("accountManagement.operation"),
key: "operation",
},
];
]);
const dataSource = ref([]);
const loading = ref(false);
@ -145,17 +146,18 @@ const removeAccount = async (id) => {
class="btn btn-success mr-3"
@click.stop.prevent="() => openModal(null)"
>
<font-awesome-icon :icon="['fas', 'plus']" />新增
<font-awesome-icon :icon="['fas', 'plus']" />
{{ $t("button.add") }}
</button>
<div class="flex items-center mb-8">
<Input
placeholder="請輸入使用者名稱"
:placeholder="t('accountManagement.name_placeholder')"
name="Full_name"
:value="searchData"
class="mr-3"
/>
<Input
placeholder="請輸入角色名稱"
:placeholder="t('accountManagement.role_placeholder')"
name="Role_full_name"
:value="searchData"
/>
@ -163,13 +165,13 @@ const removeAccount = async (id) => {
class="btn btn-outline-success ml-5"
@click.stop.prevent="onSearch"
>
搜尋
{{ $t("button.search") }}
</button>
<button
class="btn btn-outline-success mx-4"
@click.stop.prevent="onReset"
>
重置
{{ $t("button.reset") }}
</button>
</div>
</template>
@ -180,7 +182,7 @@ const removeAccount = async (id) => {
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => getUser(record.userinfo_guid)"
>
修改
{{ $t("button.edit") }}
</button>
<button
class="btn btn-sm btn-info text-white mr-2"
@ -192,13 +194,13 @@ const removeAccount = async (id) => {
})
"
>
變更密碼
{{ $t("accountManagement.change_password") }}
</button>
<button
class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => removeAccount(record.userinfo_guid)"
>
刪除
{{ $t("button.delete") }}
</button>
</template>
<template v-else>

View File

@ -4,7 +4,8 @@ import * as yup from "yup";
import "yup-phone-lite";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import { getAccountRoleList, postAccountUser } from "@/apis/account";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const props = defineProps({
@ -15,18 +16,21 @@ const props = defineProps({
let userSchema = ref(
yup.object({
Account: yup.string().required("必填"),
Name: yup.string().required("必填"),
Email: yup.string().email("請輸入正確的 Email 地址").required("必填"),
Phone: yup.string().phone("TW", "請輸入正確電話號碼格式").required("必填"),
RoleId: yup.string().required("必填"),
Account: yup.string().required(t("button.required")),
Name: yup.string().required(t("button.required")),
Email: yup
.string()
.email(t("button.email_format"))
.required(t("button.required")),
Phone: yup
.string()
.phone("TW", t("button.phone_format"))
.required(t("button.required")),
RoleId: yup.string().required(t("button.required")),
Password: yup
.string()
.required("必填")
.matches(
/^(?=.{8})(?=.*[A-Za-z])(?=.*\d)/g,
"密碼長度至少8碼必須包含英文及數字"
),
.required(t("button.required"))
.matches(/^(?=.{8})(?=.*[A-Za-z])(?=.*\d)/g, t("button.password_format")),
})
);
@ -40,32 +44,38 @@ watch(
let newSchema;
if (Boolean(newValue?.Id)) {
newSchema = yup.object({
Account: yup.string().required("必填"),
Name: yup.string().required("必填"),
Email: yup.string().email("請輸入正確的 Email 地址").required("必填"),
Account: yup.string().required(t("button.required")),
Name: yup.string().required(t("button.required")),
Email: yup
.string()
.email(t("button.email_format"))
.required(t("button.required")),
Phone: yup
.string()
.phone("TW", "請輸入正確電話號碼格式")
.required("必填"),
RoleId: yup.string().required("必填"),
.phone("TW", t("button.phone_format"))
.required(t("button.required")),
RoleId: yup.string().required(t("button.required")),
Password: yup.string(),
});
} else {
newSchema = yup.object({
Account: yup.string().required("必填"),
Name: yup.string().required("必填"),
Email: yup.string().email("請輸入正確的 Email 地址").required("必填"),
Account: yup.string().required(t("button.required")),
Name: yup.string().required(t("button.required")),
Email: yup
.string()
.email(t("button.email_format"))
.required(t("button.required")),
Phone: yup
.string()
.phone("TW", "請輸入正確電話號碼格式")
.required("必填"),
RoleId: yup.string().required("必填"),
.phone("TW", t("button.phone_format"))
.required(t("button.required")),
RoleId: yup.string().required(t("button.required")),
Password: yup
.string()
.required("必填")
.required(t("button.required"))
.matches(
/^(?=.{8})(?=.*[A-Za-z])(?=.*\d)/g,
"密碼長度至少8碼必須包含英文及數字"
t("button.password_format")
),
});
}
@ -111,7 +121,7 @@ const onOk = async () => {
<template>
<Modal
id="account_user_modal"
:title="formState?.Id ? '編輯' : '新增'"
:title="formState?.Id ? t('button.edit') : t('button.add')"
:onCancel="onCancel"
width="710"
>
@ -123,7 +133,7 @@ const onOk = async () => {
name="Account"
:disabled="Boolean(formState?.Id)"
>
<template #topLeft>帳號</template>
<template #topLeft>{{ $t("accountManagement.account") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.Account }}
@ -137,7 +147,7 @@ const onOk = async () => {
name="Password"
type="password"
>
<template #topLeft>密碼</template>
<template #topLeft>{{ $t("accountManagement.password") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.Password }}
@ -145,7 +155,7 @@ const onOk = async () => {
></Input
>
<Input :value="formState" class="my-2" name="Name">
<template #topLeft>姓名</template>
<template #topLeft>{{ $t("accountManagement.name") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.Name }}
@ -154,7 +164,7 @@ const onOk = async () => {
</Input>
<Input class="my-2" :value="formState" name="Email">
<template #topLeft>Email</template>
<template #topLeft>{{ $t("accountManagement.email") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.Email }}
@ -162,7 +172,7 @@ const onOk = async () => {
>
</Input>
<Input class="my-2" :value="formState" name="Phone">
<template #topLeft>電話</template>
<template #topLeft>{{ $t("accountManagement.phone") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.Phone }}
@ -178,7 +188,9 @@ const onOk = async () => {
Attribute="full_name"
:options="roleList"
>
<template #topLeft>角色權限</template>
<template #topLeft>{{
$t("accountManagement.role_permissions")
}}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.RoleId }}
@ -193,14 +205,14 @@ const onOk = async () => {
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
取消
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.stop.prevent="onOk"
>
確定
{{ $t("button.submit") }}
</button>
</template>
</Modal>

View File

@ -4,7 +4,8 @@ import * as yup from "yup";
import "yup-phone-lite";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import { changePassword } from "@/apis/account";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const props = defineProps({
@ -16,7 +17,7 @@ const formState = ref({
});
let userSchema = yup.object({
Password: yup.string().required("必填"),
Password: yup.string().required(t("button.required")),
});
const { formErrorMsg, handleSubmit, handleErrorReset } =
@ -41,7 +42,7 @@ const onOk = async () => {
<template>
<Modal
id="account_user_password_modal"
title="變更密碼"
:title="t('accountManagement.change_password')"
:onCancel="onCancel"
width="710"
>
@ -49,7 +50,9 @@ const onOk = async () => {
<p class="mt-10 text-3xl">{{ account.Name }}</p>
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
<Input :value="formState" class="my-2" name="Password" type="password">
<template #topLeft>變更密碼</template>
<template #topLeft>{{
$t("accountManagement.change_password")
}}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.Password }}
@ -64,14 +67,14 @@ const onOk = async () => {
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
取消
{{ $t("button.cancel") }}
</button>
<button
type="submit"
class="btn btn-outline-success"
@click.stop.prevent="onOk"
>
確定
{{ $t("button.submit") }}
</button>
</template>
</Modal>

View File

@ -3,27 +3,28 @@ import Table from "@/components/customUI/Table.vue";
import Input from "@/components/customUI/Input.vue";
import { getAccountRoleList, delRole } from "@/apis/account";
import RoleAuthModal from "./RoleAuthModal.vue";
import { ref, onMounted } from "vue";
const columns = [
import { ref, onMounted, computed } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const columns = computed(() => [
{
title: "編號",
title: t("accountManagement.index"),
key: "index",
},
{
title: "名稱",
title: t("accountManagement.role_name"),
key: "full_name",
},
{
title: "建立時間",
title: t("accountManagement.created_at"),
key: "created_at",
},
{
title: "操作",
title: t("accountManagement.operation"),
key: "operation",
width: 400,
},
];
]);
const dataSource = ref([]);
const loading = ref(false);
@ -97,7 +98,7 @@ const onSearch = () => {
<template #beforeTable>
<div class="flex items-center mb-8">
<Input
placeholder="請輸入角色名稱"
:placeholder="t('accountManagement.role_placeholder')"
name="Full_name"
:value="searchRole"
/>
@ -105,10 +106,10 @@ const onSearch = () => {
class="btn btn-outline-info mx-3"
@click.stop.prevent="onSearch"
>
搜尋
{{ $t("button.search") }}
</button>
<button class="btn btn-success ml-10" @click.stop.prevent="add">
<font-awesome-icon :icon="['fas', 'plus']" />新增
<font-awesome-icon :icon="['fas', 'plus']" /> {{ $t("button.add") }}
</button>
</div>
</template>
@ -119,19 +120,19 @@ const onSearch = () => {
class="btn btn-sm btn-info text-white mr-2"
@click.stop.prevent="() => view(record)"
>
查看
{{ $t("button.view") }}
</button>
<button
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => edit(record)"
>
修改
{{ $t("button.edit") }}
</button>
<button
class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => remove(record.role_guid)"
>
刪除
{{ $t("button.delete") }}
</button>
</template>
<template v-else>

View File

@ -8,9 +8,10 @@ import {
getAccountRoleAuthList,
postAccountRole,
} from "@/apis/account";
import { defineProps, onMounted, ref, watch } from "vue";
import { defineProps, onMounted, ref, watch, computed } from "vue";
import useActiveBtn from "@/hooks/useActiveBtn";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps({
selectedRole: String,
cancelModal: Function,
@ -20,20 +21,20 @@ const props = defineProps({
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
const columns = [
const columns = computed(() => [
{
title: "選擇",
title: t("accountManagement.choose"),
key: "SaveCheckAuth",
},
{
title: "編號",
title: t("accountManagement.index"),
key: "index",
},
{
title: "權限名稱",
title: t("accountManagement.index"),
key: "subName",
},
];
]);
const rawData = ref([]);
const dataSource = ref([]);
@ -58,13 +59,13 @@ watch(SaveCheckAuth, (newValue, oldValue) => {
if (newValue.includes("PF10") && !oldValue.includes("PF10")) {
setItems([
{
title: "基礎權限",
title: t("accountManagement.basic_permissions"),
key: "PF",
active: selectedBtn.value.authCode === "PF",
authCode: "PF",
},
{
title: "生產設定權限",
title: t("accountManagement.production_permissions"),
key: "PS",
active: selectedBtn.value.authCode === "PS",
authCode: "PS",
@ -77,7 +78,7 @@ watch(SaveCheckAuth, (newValue, oldValue) => {
);
setItems([
{
title: "基礎權限",
title: t("accountManagement.basic_permissions"),
key: "PF",
active: true,
authCode: "PF",
@ -133,7 +134,7 @@ watch(
onMounted(() => {
setItems([
{
title: "基礎權限",
title: t("accountManagement.basic_permissions"),
key: "PF",
active: true,
authCode: "PF",
@ -144,13 +145,17 @@ onMounted(() => {
</script>
<template>
<Modal id="role_auth_modal" title="角色權限設定" :onCancel="onCancel">
<Modal
id="role_auth_modal"
:title="t('accountManagement.role_permissions_setting')"
:onCancel="onCancel"
>
<template #modalContent>
<form class="modal_form" ref="form">
<Input
v-if="!disabled"
label="角色名稱"
placeholder="請輸入角色名稱"
:label="t('accountManagement.role_name')"
:placeholder="t('accountManagement.role_placeholder')"
class="mt-5"
name="Name"
:value="selectedRole"
@ -189,18 +194,11 @@ onMounted(() => {
</form>
</template>
<template #modalAction>
<button
class="btn btn-sm bg-dark btn-neutral text-white mr-2"
@click="onCancel"
>
關閉
<button class="btn btn-outline-success mr-2" @click="onCancel">
{{ $t("button.cancel") }}
</button>
<button
v-if="!disabled"
class="btn btn-sm btn-success text-white"
@click="onOk"
>
確認
<button v-if="!disabled" class="btn btn-outline-success" @click="onOk">
{{ $t("button.submit") }}
</button>
</template>
</Modal>

View File

@ -2,30 +2,39 @@
import ButtonGroup from "@/components/customUI/ButtonGroup.vue";
import AlertQuery from "./components/AlertQuery/AlertQuery.vue";
import AlertSetting from "./components/AlertSetting/AlertSetting.vue";
import { computed, onMounted, ref, provide, onBeforeMount } from "vue";
import { computed, watch, onBeforeMount } from "vue";
import useActiveBtn from "@/hooks/useActiveBtn";
import { useI18n } from "vue-i18n";
const { t, locale } = useI18n();
const changeComponent = (e, item) => {
changeActiveBtn(item);
};
const { items, changeActiveBtn, setItems } = useActiveBtn();
onBeforeMount(() => {
const initializeItems = () => {
setItems([
{
title: "告警記錄查詢",
title: t("alert.query_title"),
key: "Query",
active: true,
component: AlertQuery,
},
{
title: "告警設定",
title: t("alert.setting_title"),
key: "Setting",
active: false,
component: AlertSetting,
},
]);
};
onBeforeMount(() => {
initializeItems();
});
watch(locale, () => {
initializeItems();
});
const activeTab = computed(() => {

View File

@ -1,41 +0,0 @@
<script setup>
import { defineProps, ref, watch, inject, computed } from "vue";
import useSearchParam from "@/hooks/useSearchParam";
const { searchParams } = useSearchParam();
const isSearchDisabled = ref(true);
const search = async () => {
};
const exportFile = async () => {
};
const submitBtns = computed(() => [
{
title: "查詢",
key: "submit",
active: false,
onClick: search,
disabled: isSearchDisabled.value,
},
{
title: "匯出",
key: "export",
active: false,
onClick: exportFile,
disabled: isSearchDisabled.value,
},
]);
</script>
<template>
<ButtonGroup :items="submitBtns" :withLine="false" class="ml-2 mr-8 xl:mr-10" />
</template>
<style lang="scss" scoped></style>

View File

@ -5,9 +5,7 @@ import AlertTableModal from "./AlertTableModal.vue";
import { ref, provide, onMounted } from "vue";
import useSearchParam from "@/hooks/useSearchParam";
import useAlarmData from "@/hooks/baja/useAlarmData";
import {
getAlertFormId,
} from "@/apis/alert";
import { getAlertFormId } from "@/apis/alert";
import {
getOperationDeviceList,
getOperationCompanyList,
@ -55,7 +53,9 @@ const getFormId = async (uuid) => {
};
const getModalDevList = async () => {
const sub_system_tags = searchParams.value.system_tag.map(tag => tag.split('_')[1]);
const sub_system_tags = searchParams.value.system_tag.map(
(tag) => tag.split("_")[1]
);
const res = await getOperationDeviceList({
list_sub_system_tag: sub_system_tags,
device_building_tag: store.buildings[0].building_tag,
@ -102,18 +102,20 @@ const search = async () => {
async (result) => {
alarmData.value = result.data;
// alarm formId
alarmData.value = alarmData.value.map(alarm => ({
alarmData.value = alarmData.value.map((alarm) => ({
...alarm,
formId: null // formId null
formId: null, // formId null
}));
const uuids = alarmData.value.map(alarm => ({ uuid: alarm.uuid }));
const uuids = alarmData.value.map((alarm) => ({ uuid: alarm.uuid }));
const formIds = await getFormId(uuids);
if (Array.isArray(formIds)) {
formIds.forEach((form) => {
if (form && form.uuid) {
const index = alarmData.value.findIndex(alarm => alarm.uuid === form.uuid);
const index = alarmData.value.findIndex(
(alarm) => alarm.uuid === form.uuid
);
if (index !== -1) {
alarmData.value[index].formId = form.formId || null;
}
@ -150,11 +152,17 @@ onMounted(() => {
});
provide("alert_modal", { model_data, search, updateEditRecord });
provide("alert_table", { openModal, updateEditRecord, dataSource, search, tableLoading });
provide("alert_table", {
openModal,
updateEditRecord,
dataSource,
search,
tableLoading,
});
</script>
<template>
<h1 class="text-2xl font-extrabold mb-2">告警紀錄查詢</h1>
<h1 class="text-2xl font-extrabold mb-2">{{ $t("alert.query_title") }}</h1>
<AlertTableModal :editRecord="editRecord" />
<div>
<AlertSearch />

View File

@ -15,7 +15,7 @@ const { search } = inject("alert_table");
<AlertSearchNormalBtns />
<AlertSearchAckBtns />
<AlertSearchTimeRange />
<button class="btn btn-success ml-8" @click.stop.prevent="search">查詢</button>
<button class="btn btn-success ml-8" @click.stop.prevent="search">{{ $t("button.query")}}</button>
</div>
<div class="w-full flex flex-wrap items-center justify-start">
<AlertSearchTypesButton />

View File

@ -2,7 +2,8 @@
import { onMounted, watch } from "vue";
import useActiveBtn from "@/hooks/useActiveBtn";
import useSearchParam from "@/hooks/useSearchParam";
import { useI18n } from "vue-i18n";
const { t, locale } = useI18n();
const { searchParams, changeParams } = useSearchParam();
const {
items,
@ -11,19 +12,27 @@ const {
selectedBtn
} = useActiveBtn();
onMounted(() => {
const initializeItems = () => {
setItems([
{
title: "未確認",
title: t("alert.unacked"),
key: "unacked",
active: true,
},
{
title: "已確認",
title: t("alert.acked"),
key: "acked",
active: false,
},
]);
};
onMounted(() => {
initializeItems();
});
watch(locale, () => {
initializeItems();
});
watch(

View File

@ -2,25 +2,32 @@
import useActiveBtn from "@/hooks/useActiveBtn";
import { onMounted, watch } from "vue";
import useSearchParam from "@/hooks/useSearchParam";
import { useI18n } from "vue-i18n";
const { t, locale } = useI18n();
const { searchParams, changeParams } = useSearchParam();
const { items, changeActiveBtn, setItems, selectedBtn } = useActiveBtn();
onMounted(() => {
const initializeItems = () => {
setItems([
{
title: "未復歸",
title: t("alert.offnormal"),
key: "offnormal",
active: true,
},
{
title: "已賦歸",
title: t("alert.normal"),
key: "normal",
active: false,
},
]);
};
onMounted(() => {
initializeItems();
});
watch(locale, () => {
initializeItems();
});
//

View File

@ -2,21 +2,26 @@
import { ref, onMounted, watch } from "vue";
import dayjs from "dayjs";
import useSearchParam from "@/hooks/useSearchParam";
import { useI18n } from "vue-i18n";
const { t, locale } = useI18n();
const { searchParams, changeParams } = useSearchParam();
const dateRange = ref([
{
key: "start_at",
value: searchParams.value.start_created_at ? dayjs(searchParams.value.start_created_at).valueOf() : dayjs().subtract(30, 'day').valueOf(),
value: searchParams.value.start_created_at
? dayjs(searchParams.value.start_created_at).valueOf()
: dayjs().subtract(30, "day").valueOf(),
dateFormat: "yyyy-MM-dd",
placeholder: "起始日期",
placeholder: t("alert.start_date"),
},
{
key: "end_at",
value: searchParams.value.end_created_at ? dayjs(searchParams.value.end_created_at).valueOf() : dayjs().valueOf(),
value: searchParams.value.end_created_at
? dayjs(searchParams.value.end_created_at).valueOf()
: dayjs().valueOf(),
dateFormat: "yyyy-MM-dd",
placeholder: "結束日期",
placeholder: t("alert.end_date"),
},
]);
@ -24,15 +29,15 @@ const changeTimeRange = () => {
const newRange = [
{
key: "start_at",
value: dayjs().subtract(30, 'day').startOf('day').valueOf(), // 3000:00:00.000
value: dayjs().subtract(30, "day").startOf("day").valueOf(), // 3000:00:00.000
dateFormat: "yyyy-MM-dd",
placeholder: "起始日期",
placeholder: t("alert.start_date"),
},
{
key: "end_at",
value: dayjs().endOf('day').valueOf(), // 23:59:59.999
value: dayjs().endOf("day").valueOf(), // 23:59:59.999
dateFormat: "yyyy-MM-dd",
placeholder: "結束日期",
placeholder: t("alert.end_date"),
},
];
@ -47,7 +52,10 @@ const changeTimeRange = () => {
onMounted(() => {
//
if (!searchParams.value.start_created_at || !searchParams.value.end_created_at) {
if (
!searchParams.value.start_created_at ||
!searchParams.value.end_created_at
) {
changeTimeRange();
}
});
@ -58,18 +66,29 @@ watch(
() => {
changeParams({
...searchParams.value,
start_created_at: dayjs(dateRange.value[0].value).startOf('day').valueOf(),
end_created_at: dayjs(dateRange.value[1].value).endOf('day').valueOf(),
start_created_at: dayjs(dateRange.value[0].value)
.startOf("day")
.valueOf(),
end_created_at: dayjs(dateRange.value[1].value).endOf("day").valueOf(),
});
},
{ deep: true } //
);
watch(locale, () => {
dateRange.value[0].placeholder = t("alert.start_date");
dateRange.value[1].placeholder = t("alert.end_date");
});
</script>
<template>
<div class="flex items-center">
<button type="button" class="btn btn-outline-success mr-3" @click="changeTimeRange">
近30天
<button
type="button"
class="btn btn-outline-success mr-3"
@click="changeTimeRange"
>
{{ $t("alert.30days") }}
</button>
<DateGroup :items="dateRange" :withLine="true" />
</div>

View File

@ -3,7 +3,8 @@ import useBuildingStore from "@/stores/useBuildingStore";
import Checkbox from "@/components/customUI/Checkbox.vue";
import { computed, onMounted, ref, watch } from "vue";
import useSearchParam from "@/hooks/useSearchParam";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { searchParams, changeParams } = useSearchParam();
const store = useBuildingStore();
@ -65,7 +66,7 @@ watch(searchParams, (newValue) => {
class="btn btn-success mr-4"
@click.stop.prevent="changeCheckedItem"
>
{{ checkedItem.length === store.subSys.length ? "取消全選" : "全選" }}
{{ checkedItem.length === store.subSys.length ? t("button.deselect_all") : t("button.select_all") }}
</button>
<Checkbox
v-for="sub in store.subSys"

View File

@ -1,53 +1,53 @@
<script setup>
import { inject } from "vue";
import {
postChgAck,
} from "@/apis/alert";
import { inject, computed } from "vue";
import { postChgAck } from "@/apis/alert";
import { Button } from "ant-design-vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { dataSource, openModal, search, tableLoading } = inject("alert_table");
const columns = [
const columns = computed(() => [
{
key: "building_tag",
title: "棟別-樓層",
title: t("alert.building_and_floor"),
},
{
key: "uuid",
title: "異常ID",
title: t("alert.uuid"),
},
{
key: "alarmClass",
title: "異常類別",
title: t("alert.alarmClass"),
},
{
key: "full_name",
title: "設備名稱",
title: t("alert.device_name"),
},
{
key: "device_number",
title: "設備編號",
title: t("alert.device_number"),
},
{
key: "timestamp_date",
title: "發生日期",
title: t("alert.date"),
},
{
key: "timestamp_time",
title: "發生時間",
title: t("alert.time"),
},
{
key: "msg",
title: "異常原因",
title: t("alert.error_msg"),
},
{
key: "ackState",
title: "Ack 確認",
title: t("alert.ack_state"),
},
{
key: "repairOrder",
title: "派工 / 維運單號",
title: t("alert.repair_order_number"),
},
];
]);
const chgAck = async (devUuid) => {
const res = await postChgAck(devUuid);
@ -55,7 +55,6 @@ const chgAck = async (devUuid) => {
search?.();
}
};
</script>
<template>
@ -63,8 +62,11 @@ const chgAck = async (devUuid) => {
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'ackState'">
<template v-if="record.ackState === 'Unacked'">
<button class="btn btn-sm btn-success text-white whitespace-nowrap mr-2" @click.stop.prevent="() => chgAck(record.uuid)">
未確認
<button
class="btn btn-sm btn-success text-white whitespace-nowrap mr-2"
@click.stop.prevent="() => chgAck(record.uuid)"
>
{{ $t("alert.unacked") }}
</button>
</template>
<template v-else>
@ -72,11 +74,15 @@ const chgAck = async (devUuid) => {
</template>
</template>
<template v-if="column.key === 'repairOrder'">
<button class="btn btn-sm btn-success text-white whitespace-nowrap mr-2"
@click.stop.prevent="() => openModal(record)">
<button
class="btn btn-sm btn-success text-white whitespace-nowrap mr-2"
@click.stop.prevent="() => openModal(record)"
>
<span v-if="record.formId">{{ record.formId }}</span>
<span v-else>
<font-awesome-icon :icon="['fas', 'plus']" />維修單
<font-awesome-icon :icon="['fas', 'plus']" />{{
$t("alert.repair_order_number")
}}
</span>
</button>
</template>

View File

@ -1,13 +1,12 @@
<script setup>
import { ref, defineProps, watch, inject } from "vue";
import dayjs from "dayjs";
import {
postOperationRecord,
} from "@/apis/alert";
import { postOperationRecord } from "@/apis/alert";
import * as yup from "yup";
import "yup-phone-lite";
import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const FILE_BASEURL = import.meta.env.VITE_FILE_API_BASEURL;
const props = defineProps({
@ -22,26 +21,24 @@ const dateItem = ref([
name: "start_time",
value: dayjs(),
dateFormat: "yyyy-MM-dd",
placeholder: "請輸入預計開始日期",
placeholder: t("alert.start_time"),
},
]);
const formState = ref(
{
formId: null,
uuid: "",
work_type: 2,
fix_do: "",
fix_do_code: "",
fix_firm: "",
status: 0,
work_person_id: "",
start_time: dayjs().format("YYYY-MM-DD"),
notice: "",
description: "",
lorf: [],
}
);
const formState = ref({
formId: null,
uuid: "",
work_type: 2,
fix_do: "",
fix_do_code: "",
fix_firm: "",
status: 0,
work_person_id: "",
start_time: dayjs().format("YYYY-MM-DD"),
notice: "",
description: "",
lorf: [],
});
const { model_data, updateEditRecord, search } = inject("alert_modal") || {
model_data: [],
@ -50,12 +47,12 @@ const { model_data, updateEditRecord, search } = inject("alert_modal") || {
};
let alertSchema = yup.object({
start_time: yup.date().required("必填"),
fix_do: yup.string().required("必填"),
fix_do_code: yup.string().required("必填"),
fix_firm: yup.string().required("必填"),
status: yup.number().required("必填"),
work_person_id: yup.string().required("必填"),
start_time: yup.date().required(t("button.required")),
fix_do: yup.string().required(t("button.required")),
fix_do_code: yup.string().required(t("button.required")),
fix_firm: yup.string().required(t("button.required")),
status: yup.number().required(t("button.required")),
work_person_id: yup.string().required(t("button.required")),
notice: yup.string().nullable(true),
description: yup.string().nullable(true),
});
@ -71,14 +68,15 @@ const onOk = async () => {
const formData = new FormData(form.value);
formData.delete("oriFile");
formState.value?.lorf.forEach(
(file, index) => {
formData.append(`lorf[${index}].id`, file.id ? file.id : "");
formData.append(`lorf[${index}].file`, file.id ? null : file);
formData.append(`lorf[${index}].save_file_name`,file.id ? file.save_file_name : "");
formData.append(`lorf[${index}].ori_file_name`, file.name);
}
);
formState.value?.lorf.forEach((file, index) => {
formData.append(`lorf[${index}].id`, file.id ? file.id : "");
formData.append(`lorf[${index}].file`, file.id ? null : file);
formData.append(
`lorf[${index}].save_file_name`,
file.id ? file.save_file_name : ""
);
formData.append(`lorf[${index}].ori_file_name`, file.name);
});
formData.append(
"start_time",
@ -140,7 +138,9 @@ watch(
}
// start_time
if (key === "start_time") {
formState.value.start_time = value ? dayjs(value).format("YYYY-MM-DD") : dayjs().format("YYYY-MM-DD");
formState.value.start_time = value
? dayjs(value).format("YYYY-MM-DD")
: dayjs().format("YYYY-MM-DD");
dateItem.value[0].value = value;
}
//
@ -159,104 +159,171 @@ watch(
</script>
<template>
<Modal id="alert_action_item" title="維修單" :onCancel="onCancel" width="710">
<Modal
id="alert_action_item"
:title="t('alert.repair_order')"
:onCancel="onCancel"
width="710"
>
<template #modalContent>
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
<Input v-if="formState.value && formState.value.formId" class="my-2" :value="formState" name="formId" readonly>
<template #topLeft>表單編號</template>
<Input
v-if="formState.value && formState.value.formId"
class="my-2"
:value="formState"
name="formId"
readonly
>
<template #topLeft>{{ $t("alert.form_number") }}</template>
</Input>
<Input :value="formState" class="my-2" name="uuid" readonly>
<template #topLeft>異常編號</template>
<template #topLeft>{{ $t("alert.uuid") }}</template>
</Input>
<DateGroup class="my-2" :items="dateItem" inputClass="w-full shadow-none" :required="true">
<template #topLeft>預計開始時間</template>
<DateGroup
class="my-2"
:items="dateItem"
inputClass="w-full shadow-none"
:required="true"
>
<template #topLeft>{{ $t("alert.start_time") }}</template>
<template #bottomLeft>
<span class="text-error text-base">
{{ formErrorMsg.start_time }}
</span>
</template>
</DateGroup>
<Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="work_type"
Attribute="title" :options="[
<Select
:value="formState"
class="my-2"
selectClass="border-info focus-within:border-info"
name="work_type"
Attribute="title"
:options="[
{
key: 1,
value: 1,
title: '保養',
title: $t('alert.maintenance'),
},
{
key: 2,
value: 2,
title: '維修',
title: $t('alert.repair'),
},
]" :required="true" :disabled="true">
<template #topLeft>項目</template>
]"
:required="true"
:disabled="true"
>
<template #topLeft>{{ $t("alert.item") }}</template>
</Select>
<Input class="my-2" :value="formState" name="fix_do" :required="true">
<template #topLeft>維修項目</template>
<template #bottomLeft><span class="text-error text-base">
{{ formErrorMsg.fix_do }}
</span>
</template>
<template #topLeft>{{ $t("alert.repair_item") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.fix_do }}
</span>
</template>
</Input>
<Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="fix_do_code"
Attribute="device_name" :options="model_data.model_devList" :required="true" :disabled="true">
<template #topLeft>維修項目代碼(設備編號)</template>
<template #bottomLeft><span class="text-error text-base">
<Select
:value="formState"
class="my-2"
selectClass="border-info focus-within:border-info"
name="fix_do_code"
Attribute="device_name"
:options="model_data.model_devList"
:required="true"
:disabled="true"
>
<template #topLeft>{{ $t("repair_item_code") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.fix_do_code }}
</span></template>
</span></template
>
</Select>
<Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="fix_firm"
Attribute="name" :options="model_data.model_companyList" :required="true">
<template #topLeft>負責廠商</template>
<template #bottomLeft><span class="text-error text-base">
<Select
:value="formState"
class="my-2"
selectClass="border-info focus-within:border-info"
name="fix_firm"
Attribute="name"
:options="model_data.model_companyList"
:required="true"
>
<template #topLeft>{{ $t("responsible_vendor") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.fix_firm }}
</span></template>
</span></template
>
</Select>
<RadioGroup class="my-2" name="status" :value="formState" :items="[
{
key: 0,
value: 0,
title: '未完成',
},
{
key: 1,
value: 1,
title: '已完成',
},
]" :required="true">
<template #topLeft>狀態</template>
<RadioGroup
class="my-2"
name="status"
:value="formState"
:items="[
{
key: 0,
value: 0,
title: $t('alert.not_completed'),
},
{
key: 1,
value: 1,
title: $t('alert.completed'),
},
]"
:required="true"
>
<template #topLeft>{{ $t("alert.status") }}</template>
</RadioGroup>
<Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="work_person_id"
Attribute="full_name" :options="model_data.model_userList" :required="true">
<template #topLeft>工作人員編號</template>
<template #bottomLeft><span class="text-error text-base">
<Select
:value="formState"
class="my-2"
selectClass="border-info focus-within:border-info"
name="work_person_id"
Attribute="full_name"
:options="model_data.model_userList"
:required="true"
>
<template #topLeft>{{ $t("alert.worker_id") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.work_person_id }}
</span></template>
</span></template
>
</Select>
<Textarea :value="formState" name="notice" class="w-full my-2">
<template #topLeft>注意事項</template>
</Textarea>
<template #topLeft>{{ $t("alert.notice") }}</template>
</Textarea>
<Textarea :value="formState" name="description" class="w-full my-2">
<template #topLeft>結果描述</template>
</Textarea>
<template #topLeft>{{ $t("alert.result_description") }}</template>
</Textarea>
<Upload
class="my-2"
name="oriFile"
:fileList="formState?.lorf"
:getFileList="updateFileList"
:multiple="true"
:baseUrl="`${FILE_BASEURL}/upload/operation`"
class="my-2"
name="oriFile"
:fileList="formState?.lorf"
:getFileList="updateFileList"
:multiple="true"
:baseUrl="`${FILE_BASEURL}/upload/operation`"
>
<template #topLeft>上傳檔案</template>
<template #topLeft>{{ $t("alert.upload_file") }}</template>
</Upload>
</form>
</template>
<template #modalAction>
<button type="reset" class="btn btn-outline-success mr-2" @click.prevent="onCancel">
取消
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="onCancel"
>
{{ $t("button.cancel") }}
</button>
<button type="submit" class="btn btn-outline-success" @click.stop.prevent="onOk">
確定
<button
type="submit"
class="btn btn-outline-success"
@click.stop.prevent="onOk"
>
{{ $t("button.submit")}}
</button>
</template>
</Modal>

View File

@ -1,23 +1,24 @@
<script setup>
import { inject, defineProps, watch, ref } from "vue";
import { defineProps, computed } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps({
SaveCheckAuth: Object,
NoticeData: Object,
onChange: Function,
});
const columns = [
const columns = computed(() => [
{
title: "選擇",
title: t('alert.choose'),
key: "SaveCheckAuth",
width: 150,
},
{
title: "通知項目",
title: t('alert.notify_items'),
key: "system_key",
},
];
]);
</script>

View File

@ -1,60 +1,64 @@
<script setup>
import { ref, inject, watch } from "vue";
import { ref, inject, watch, computed } from "vue";
import { getAlarmMemberList, deleteAlarmMember } from "@/apis/alert";
import AlertNotifyTableAddModal from "./AlertNotifyTableAddModal.vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const tableData = ref([]);
const editRecord = ref(null);
const { noticeList } = inject("notify_table");
const fetchTableData = async () => {
const noticeMap = noticeList.value.reduce((acc, notice) => {
acc[notice.system_value] = notice.system_key;
return acc;
}, {});
const res = await getAlarmMemberList();
tableData.value = res.data.map(member => ({
tableData.value = res.data.map((member) => ({
...member,
notify_items: member.notices
.filter(n => noticeMap[n])
.map(n => noticeMap[n])
.join('\n') || '無通知',
notify_items:
member.notices
.filter((n) => noticeMap[n])
.map((n) => noticeMap[n])
.join("\n") || t("alert.no_notify"),
}));
};
watch(noticeList, (newVal) => {
if (newVal) {
fetchTableData();
}
}, { immediate: true });
watch(
noticeList,
(newVal) => {
if (newVal) {
fetchTableData();
}
},
{ immediate: true }
);
const columns = [
const columns = computed(() => [
{
title: "姓名",
title: t("alert.notify_name"),
key: "name",
},
{
title: "手機號碼",
title: t("alert.notify_phone"),
key: "phone",
},
{
title: "email",
title: t("alert.notify_email"),
key: "email",
},
{
title: "通知項目",
title: t("alert.notify_items"),
key: "notify_items",
},
{
title: "功能",
title: t("alert.operation"),
key: "operation",
width: 200,
},
];
]);
const openModal = (record) => {
if (record) {
@ -78,23 +82,32 @@ const remove = async (id) => {
openToast("error", res.msg);
}
};
</script>
<template>
<div class="flex justify-start items-center mt-10">
<h3 class="text-xl mr-5">通知名單</h3>
<AlertNotifyTableAddModal :openModal="openModal" :onCancel="onCancel" :editRecord="editRecord"
:fetchTableData="fetchTableData" />
<h3 class="text-xl mr-5">{{ $t("alert.notify_list") }}</h3>
<AlertNotifyTableAddModal
:openModal="openModal"
:onCancel="onCancel"
:editRecord="editRecord"
:fetchTableData="fetchTableData"
/>
</div>
<Table :columns="columns" :dataSource="tableData" class="w-3/4 mt-3">
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'operation'">
<button class="btn btn-sm btn-success text-white mr-2" @click.stop.prevent="() => openModal(record)">
修改
<button
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => openModal(record)"
>
{{ $t("button.edit") }}
</button>
<button class="btn btn-sm btn-error text-white" @click.stop.prevent="() => remove(record.id)">
刪除
<button
class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => remove(record.id)"
>
{{ $t("button.delete") }}
</button>
</template>
<template v-else>

View File

@ -4,7 +4,8 @@ import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import AlertNoticesTable from "./AlertNoticesTable.vue";
import { postAlertMember } from "@/apis/alert";
import * as yup from "yup";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const { noticeList } = inject("notify_table");
const props = defineProps({
@ -15,9 +16,15 @@ const props = defineProps({
});
let scheme = yup.object({
name: yup.string().required("必填"),
phone: yup.string().phone("TW", "請輸入正確電話號碼格式").required("必填"),
email: yup.string().email("請輸入正確的 Email 地址").required("必填"),
name: yup.string().required(t("button.required")),
phone: yup
.string()
.phone("TW", t("button.phone_format"))
.required(t("button.required")),
email: yup
.string()
.email(t("button.email_format"))
.required(t("button.required")),
});
const form = ref(null);
@ -31,8 +38,9 @@ const formState = ref({
const SaveCheckAuth = ref([]);
const { formErrorMsg, handleSubmit, handleErrorReset } =
useFormErrorMessage(scheme.value);
const { formErrorMsg, handleSubmit, handleErrorReset } = useFormErrorMessage(
scheme.value
);
watch(
() => props.editRecord,
@ -60,11 +68,10 @@ const onChange = (value, checked) => {
const onOk = async () => {
const values = await handleSubmit(scheme, formState.value);
const res = await postAlertMember(
{
...values,
notices: SaveCheckAuth.value ? SaveCheckAuth.value : [],
});
const res = await postAlertMember({
...values,
notices: SaveCheckAuth.value ? SaveCheckAuth.value : [],
});
if (res.isSuccess) {
props.fetchTableData();
closeModal();
@ -82,40 +89,48 @@ const closeModal = () => {
<template>
<button class="btn btn-sm btn-success mr-3" @click.stop.prevent="openModal">
<font-awesome-icon :icon="['fas', 'plus']" />新增
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
</button>
<Modal id="notify_add_table_item" title="通知名單" :open="open" :onCancel="closeModal" width="300">
<Modal
id="notify_add_table_item"
:title="t('alert.notify_list')"
:open="open"
:onCancel="closeModal"
width="300"
>
<template #modalContent>
<form ref="form" class="mt-5 flex flex-col items-center">
<Input :value="formState" class="w-full" name="name">
<template #topLeft>姓名</template>
<template #bottomLeft>
<span class="text-error text-base">
{{ formErrorMsg.name }}
</span>
</template>
<template #topLeft>{{ $t("alert.notify_name") }}</template>
<template #bottomLeft>
<span class="text-error text-base">
{{ formErrorMsg.name }}
</span>
</template>
</Input>
<Input class="w-full" :value="formState" name="phone">
<template #topLeft>電話</template>
<template #bottomLeft>
<span class="text-error text-base">
{{ formErrorMsg.phone }}
</span>
</template>
<template #topLeft>{{ $t("alert.notify_phone") }}</template>
<template #bottomLeft>
<span class="text-error text-base">
{{ formErrorMsg.phone }}
</span>
</template>
</Input>
<Input class="w-full" :value="formState" name="email">
<template #topLeft>Email</template>
<template #bottomLeft>
<span class="text-error text-base">
{{ formErrorMsg.email }}
</span>
</template>
<template #topLeft>{{ $t("alert.notify_email") }}</template>
<template #bottomLeft>
<span class="text-error text-base">
{{ formErrorMsg.email }}
</span>
</template>
</Input>
<div class="w-5/6 mt-5">
<p class="text-light text-base">
通知項目
</p>
<AlertNoticesTable :SaveCheckAuth="SaveCheckAuth" :NoticeData="[noticeList[2]]" :onChange="onChange"/>
<p class="text-light text-base">{{ $t("alert.notify_items") }}</p>
<AlertNoticesTable
:SaveCheckAuth="SaveCheckAuth"
:NoticeData="[noticeList[2]]"
:onChange="onChange"
/>
<span class="text-error text-base">
{{ formErrorMsg.notices }}
</span>
@ -123,11 +138,19 @@ const closeModal = () => {
</form>
</template>
<template #modalAction>
<button type="reset" class="btn btn-outline-success mr-2" @click.prevent="closeModal">
取消
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="closeModal"
>
{{ $t("button.cancel") }}
</button>
<button type="submit" class="btn btn-outline-success" @click.prevent="onOk">
確定
<button
type="submit"
class="btn btn-outline-success"
@click.prevent="onOk"
>
{{ $t("button.submit") }}
</button>
</template>
</Modal>

View File

@ -1,10 +1,16 @@
<script setup>
import { onMounted, ref, watch, inject } from "vue";
import { getOutliersList, getOutliersDevList, getOutliersPoints } from "@/apis/alert";
import { onMounted, ref, watch, inject, computed } from "vue";
import {
getOutliersList,
getOutliersDevList,
getOutliersPoints,
} from "@/apis/alert";
import useSearchParam from "@/hooks/useSearchParam";
import AlertOutliersTableAddModal from "./AlertOutliersTableAddModal.vue";
import AlertOutliersTimeModal from "./AlertOutliersTimeModal.vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { noticeList } = inject("notify_table");
const { searchParams, changeParams } = useSearchParam();
@ -15,110 +21,120 @@ const dev_data = ref({
});
const editRecord = ref(null);
const columns = [
const columns = computed(() => [
{
title: "設備編號",
title: t("alert.device_number"),
key: "device_number",
class: "break-all",
},
{
title: "設備名稱",
title: t("alert.device_name"),
key: "device_name",
},
{
title: "項目",
title: t("alert.item"),
key: "points",
},
{
title: "啟用",
title: t("alert.enable"),
key: "enable",
},
{
title: "限定條件",
title: t("alert.qualifications"),
key: "factor_name",
},
{
title: "上限(>=)",
title: `${t("alert.upper_limit")} (>=)`,
key: "highLimit",
},
{
title: "下限(<=)",
title: `${t("alert.lower_limit")} (<=)`,
key: "lowLimit",
},
{
title: "上限持續秒數",
title: t("alert.highDelay"),
key: "highDelay",
},
{
title: "下限持續秒數",
title: t("alert.lowDelay"),
key: "lowDelay",
},
{
title: "警示方式",
title: t("alert.warning_method"),
key: "warning_method",
},
{
title: "警示時間",
title: t("alert.warning_time"),
key: "warning_time",
width: 150,
},
{
title: "功能",
title: t("alert.operation"),
key: "operation",
width: 200,
width: 150,
},
];
]);
const getDevList = async () => {
const res = await getOutliersDevList({
device_name_tag: searchParams.value?.subSys_id
device_name_tag: searchParams.value?.subSys_id,
});
return res.data.map((d) => ({
...d,
key: d.device_number
key: d.device_number,
}));
};
const getAlarmPoints = async () => {
const res = await getOutliersPoints({
device_name_tag: searchParams.value?.subSys_id
device_name_tag: searchParams.value?.subSys_id,
});
return res.data.map((d) => ({
...d,
key: d.points
key: d.points,
}));
};
const getOutliersData = async () => {
const res = await getOutliersList({
device_name_tag: searchParams.value?.subSys_id
device_name_tag: searchParams.value?.subSys_id,
});
if (res.isSuccess) {
tableData.value = res.data.map(item => {
const matchedDevice = dev_data.value.devList.find(dev => dev.device_number === item.device_number);
const matchedPoints = dev_data.value.alarmPoints.find(p => p.points === item.points);
const matchedFactor = matchedPoints?.factor && item.factor ? matchedPoints?.factor?.find(f => f.id === item.factor) : null;
const warningMethodKeys = item.notices?.map(noticeValue => {
const matchedNotice = noticeList.value.find(n => n.system_value === noticeValue);
return matchedNotice ? matchedNotice.system_key : '';
}).filter(key => key !== '').join('\n');
tableData.value = res.data.map((item) => {
const matchedDevice = dev_data.value.devList.find(
(dev) => dev.device_number === item.device_number
);
const matchedPoints = dev_data.value.alarmPoints.find(
(p) => p.points === item.points
);
const matchedFactor =
matchedPoints?.factor && item.factor
? matchedPoints?.factor?.find((f) => f.id === item.factor)
: null;
const warningMethodKeys = item.notices
?.map((noticeValue) => {
const matchedNotice = noticeList.value.find(
(n) => n.system_value === noticeValue
);
return matchedNotice ? matchedNotice.system_key : "";
})
.filter((key) => key !== "")
.join("\n");
return {
...item,
device_name: matchedDevice ? matchedDevice.device_name : '',
points: matchedPoints ? matchedPoints.full_name : '',
device_name: matchedDevice ? matchedDevice.device_name : "",
points: matchedPoints ? matchedPoints.full_name : "",
is_bool: matchedPoints ? matchedPoints.is_bool : 1,
factor_name: matchedFactor ? matchedFactor.full_name : '',
warning_method: warningMethodKeys
factor_name: matchedFactor ? matchedFactor.full_name : "",
warning_method: warningMethodKeys,
};
});
}
};
const getAllOptions = async () => {
const [devices, points] = await Promise.all([
getDevList(),
getAlarmPoints(),
]);
const [devices, points] = await Promise.all([getDevList(), getAlarmPoints()]);
dev_data.value.devList = devices;
dev_data.value.alarmPoints = points;
};
@ -155,23 +171,22 @@ const openTimeModal = () => {
const onTimeCancel = () => {
outliers_time_item.close();
};
</script>
<template>
<div class="flex justify-start items-center mt-10">
<h3 class="text-xl mr-5">異常設定</h3>
<h3 class="text-xl mr-5">{{ $t("alert.alarm_settings") }}</h3>
<AlertOutliersTableAddModal
:openModal="openModal"
:onCancel="onCancel"
:editRecord="editRecord"
:getData="getOutliersData"
:OptionsData="dev_data"
:openModal="openModal"
:onCancel="onCancel"
:editRecord="editRecord"
:getData="getOutliersData"
:OptionsData="dev_data"
/>
<AlertOutliersTimeModal
:openModal="openTimeModal"
:onCancel="onTimeCancel"
/>
<!-- <AlertOutliersTimeModal
:openModal="openTimeModal"
:onCancel="onTimeCancel"
/> -->
</div>
<Table :columns="columns" :dataSource="tableData" class="mt-3">
<template #bodyCell="{ record, column, index }">
@ -180,19 +195,22 @@ const onTimeCancel = () => {
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => openModal(record)"
>
修改
{{ $t("button.edit") }}
</button>
</template>
<template v-else-if="column.key === 'enable'">
{{ record.enable === 1 ? '是' : '否' }}
{{ record.enable === 1 ? t("alert.yes") : t("alert.no") }}
</template>
<template v-else-if="column.key === 'warning_method'">
<span class="whitespace-pre">{{ record.warning_method }}</span>
</template>
<template v-else-if="column.key === 'warning_time'">
<button class="btn btn-sm btn-success text-white pb-3"
<button
class="btn btn-sm btn-success text-white pb-3"
:disabled="!record.enable"
@click.stop.prevent="() => openTimeModal(record)"
>
限制告警時間
{{ $t("alert.time_setting") }}
</button>
</template>
<template v-else>

View File

@ -5,7 +5,8 @@ import useFormErrorMessage from "@/hooks/useFormErrorMessage";
import AlertNoticesTable from "./AlertNoticesTable.vue";
import { postOutliersSetting } from "@/apis/alert";
import * as yup from "yup";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { openToast } = inject("app_toast");
const { noticeList } = inject("notify_table");
const { searchParams, changeParams } = useSearchParam();
@ -20,23 +21,23 @@ const props = defineProps({
const form = ref(null);
const formState = ref({
"id": 0,
"device_number": "",
"device_name_tag": searchParams.value?.subSys_id,
"points": "",
"enable": 0,
"is_bool": 1,
"factor": null,
"highLimit": null,
"lowLimit": null,
"highDelay": null,
"lowDelay": null,
"notices": []
id: 0,
device_number: "",
device_name_tag: searchParams.value?.subSys_id,
points: "",
enable: 0,
is_bool: 1,
factor: null,
highLimit: null,
lowLimit: null,
highDelay: null,
lowDelay: null,
notices: [],
});
let scheme = yup.object({
device_number: yup.string().required("必填"),
points: yup.string().required("必填"),
device_number: yup.string().required(t("button.required")),
points: yup.string().required(t("button.required")),
factor: yup.number().nullable(),
enable: yup.number().required(),
highLimit: yup.number().nullable(),
@ -45,8 +46,9 @@ let scheme = yup.object({
lowDelay: yup.number().nullable(),
});
const { formErrorMsg, handleSubmit, handleErrorReset } =
useFormErrorMessage(scheme.value);
const { formErrorMsg, handleSubmit, handleErrorReset } = useFormErrorMessage(
scheme.value
);
const SaveCheckAuth = ref([]);
const isBool = ref(1);
@ -69,15 +71,17 @@ watch(
);
const onPointsChange = (selectedPoint) => {
const pointData = props.OptionsData.alarmPoints.find(p => p.points === selectedPoint);
const pointData = props.OptionsData.alarmPoints.find(
(p) => p.points === selectedPoint
);
if (pointData) {
isBool.value = pointData.is_bool;
formState.value.is_bool = pointData.is_bool;
if (pointData.factor && Array.isArray(pointData.factor)) {
factorData.value = pointData.factor.map((d) => ({
...d,
key: d.id
}));;
key: d.id,
}));
} else {
factorData.value = [];
formState.value.factor = 0;
@ -120,74 +124,141 @@ const closeModal = () => {
factorData.value = [];
isBool.value = 1;
};
</script>
<template>
<button class="btn btn-sm btn-success mr-3" @click.stop.prevent="openModal">
<font-awesome-icon :icon="['fas', 'plus']" />新增
<font-awesome-icon :icon="['fas', 'plus']" />{{ $t("button.add") }}
</button>
<Modal id="outliers_add_table_item" title="異常設定" :open="open" :onCancel="closeModal" width="710">
<Modal
id="outliers_add_table_item"
:title="t('alert.alarm_settings')"
:open="open"
:onCancel="closeModal"
width="710"
>
<template #modalContent>
<form ref="form" class="mt-5 w-full flex flex-wrap justify-between">
<Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="device_number"
Attribute="device_name" :options="OptionsData.devList">
<template #topLeft>設備名稱</template>
<template #bottomLeft><span class="text-error text-base">
<Select
:value="formState"
class="my-2"
selectClass="border-info focus-within:border-info"
name="device_number"
Attribute="device_name"
:options="OptionsData.devList"
>
<template #topLeft>{{ $t("alert.device_name") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.device_number }}
</span></template>
</span></template
>
</Select>
<Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="points"
Attribute="full_name" :options="OptionsData.alarmPoints" :onChange="onPointsChange">
<template #topLeft>項目</template>
<template #bottomLeft><span class="text-error text-base">
<Select
:value="formState"
class="my-2"
selectClass="border-info focus-within:border-info"
name="points"
Attribute="full_name"
:options="OptionsData.alarmPoints"
:onChange="onPointsChange"
>
<template #topLeft>{{ $t("alert.item") }}</template>
<template #bottomLeft
><span class="text-error text-base">
{{ formErrorMsg.points }}
</span></template>
</span></template
>
</Select>
<RadioGroup class="my-2" name="enable" :value="formState" :items="[
{
key: 1,
value: 1,
title: '啟用',
},
{
key: 0,
value: 0,
title: '不啟用',
},
]" :required="true">
<template #topLeft>狀態</template>
<RadioGroup
class="my-2"
name="enable"
:value="formState"
:items="[
{
key: 1,
value: 1,
title: $t('alert.enable'),
},
{
key: 0,
value: 0,
title: $t('alert.not_enabled'),
},
]"
:required="true"
>
<template #topLeft>{{ $t("alert.status") }}</template>
</RadioGroup>
<Select :value="formState" class="my-2" selectClass="border-info focus-within:border-info" name="factor"
Attribute="full_name" :options="factorData" v-if="factorData.length !== 0">
<template #topLeft>限定條件</template>
<Select
:value="formState"
class="my-2"
selectClass="border-info focus-within:border-info"
name="factor"
Attribute="full_name"
:options="factorData"
v-if="factorData.length !== 0"
>
<template #topLeft>{{ $t("alert.qualifications") }}</template>
</Select>
<InputNumber :value="formState" class="my-2" name="highLimit" v-if="!isBool">
<template #topLeft>上限(>=)</template>
<InputNumber
:value="formState"
class="my-2"
name="highLimit"
v-if="!isBool"
>
<template #topLeft>{{ $t("alert.upper_limit") }}(>=)</template>
</InputNumber>
<InputNumber :value="formState" class="my-2" name="lowLimit" v-if="!isBool">
<template #topLeft>下限(&lt;=)</template>
<InputNumber
:value="formState"
class="my-2"
name="lowLimit"
v-if="!isBool"
>
<template #topLeft>{{ $t("alert.lower_limit") }}(&lt;=)</template>
</InputNumber>
<InputNumber :value="formState" class="my-2" name="highDelay" v-if="!isBool">
<template #topLeft>上限持續秒數</template>
<InputNumber
:value="formState"
class="my-2"
name="highDelay"
v-if="!isBool"
>
<template #topLeft>{{ $t("alert.highDelay") }}</template>
</InputNumber>
<InputNumber :value="formState" class="my-2" name="lowDelay" v-if="!isBool">
<template #topLeft>下限持續秒數</template>
<InputNumber
:value="formState"
class="my-2"
name="lowDelay"
v-if="!isBool"
>
<template #topLeft>{{ $t("alert.lowDelay") }}</template>
</InputNumber>
<div class="w-full mt-5">
<p class="text-light text-lg ml-1">
警示方式
{{ $t("alert.warning_method") }}
</p>
<AlertNoticesTable :SaveCheckAuth="SaveCheckAuth" :NoticeData="noticeList" :onChange="onNoticesChange" />
<AlertNoticesTable
:SaveCheckAuth="SaveCheckAuth"
:NoticeData="noticeList"
:onChange="onNoticesChange"
/>
</div>
</form>
</template>
<template #modalAction>
<button type="reset" class="btn btn-outline-success mr-2" @click.prevent="closeModal">
取消
<button
type="reset"
class="btn btn-outline-success mr-2"
@click.prevent="closeModal"
>
{{ $t("button.cancel") }}
</button>
<button type="submit" class="btn btn-outline-success" @click.prevent="onOk">
確定
<button
type="submit"
class="btn btn-outline-success"
@click.prevent="onOk"
>
{{ $t("buttton.submit") }}
</button>
</template>
</Modal>

View File

@ -10,29 +10,73 @@ const closeModal = () => {
props.onCancel();
};
const columns = [
{
title: "排程名稱",
key: "schedule_name",
},
{
title: "時段",
key: "schedule_time",
},
{
title: "狀態",
key: "enable",
},
{
title: "功能",
key: "operation",
width: 150,
},
];
</script>
<template>
<Modal id="outliers_time_item" :open="open" :onCancel="closeModal" width="710">
<Modal
id="outliers_time_item"
:open="open"
:onCancel="closeModal"
width="800"
>
<template #modalTitle>
<p>限制告警時間設定</p>
<p>時間設定</p>
<button class="fixed right-10 top-5" @click.prevent="closeModal">
<font-awesome-icon :icon="['fas', 'times']" size="1x" class="text-[#a5abb1]" />
<font-awesome-icon
:icon="['fas', 'times']"
size="1x"
class="text-[#a5abb1]"
/>
</button>
</template>
<template #modalContent>
<iframe :src="'/ord?station:%7Cslot:/NTPC/F3/Pd/DP/U2F/NA/DP$2d260ML/N4|view:GraphicM?fullScreen=true'
"></iframe>
<Table
:columns="columns"
:dataSource="tableData"
class="mt-3"
:withStyle="false"
>
<template #bodyCell="{ record, column, index }">
<template v-if="column.key === 'operation'">
<button
class="btn btn-sm btn-success text-white mr-2"
@click.stop.prevent="() => openModal(record)"
>
修改
</button>
<button
class="btn btn-sm btn-error text-white"
@click.stop.prevent="() => remove(record.userinfo_guid)"
>
刪除
</button>
</template>
<template v-else>
{{ record[column.key] }}
</template>
</template>
</Table>
</template>
</Modal>
</template>
<style lang="scss" scoped>
iframe {
width: 100%;
height: 400px;
margin-top: 20px;
border-radius: 5px;
box-shadow: 0px 0px 50px 5px #35eded47;
}
</style>
<style lang="scss" scoped></style>

View File

@ -16,11 +16,10 @@ onMounted(() => {
});
provide("notify_table", { noticeList });
</script>
<template>
<h1 class="text-2xl font-extrabold mb-2">告警設定</h1>
<h1 class="text-2xl font-extrabold mb-2">{{ $t("alert.setting_title") }}</h1>
<div>
<AlertSubList />
<AlertOutliersTable />

View File

@ -1,13 +1,9 @@
<script setup>
import Forge from "@/components/forge/Forge.vue";
import DashboardProduct from "./components/DashboardProduct.vue";
import DashboardFormulaTemp from "./components/DashboardFormulaTemp.vue";
import DashboardImmediateTemp from "./components/DashboardImmediateTemp.vue";
import DashboardFrozenTemp from "./components/DashboardFrozenTemp.vue";
import DashboardEnergy from "./components/DashboardEnergy.vue";
import DashboardAlert from "./components/DashboardAlert.vue";
import DashboardMoreModal from "./components/DashboardMoreModal.vue";
import DashboardForgeOptionButton from "./components/DashboardForgeOptionButton.vue";
import DashboardStat from "./components/DashboardStat.vue";
import DashboardElecChart from "./components/DashboardElecChart.vue";
import DashboardSysCard from "./components/DashboardSysCard.vue";
import DashboardSysProgress from "./components/DashboardSysProgress.vue";
import { getDashboardInit } from "@/apis/dashboard";
import { onMounted, ref, provide, watch } from "vue";
@ -45,43 +41,46 @@ provide("dashboard_items", {
</script>
<template>
<!-- <Forge /> -->
<DashboardMoreModal />
<div class="flex justify-between my-10">
<div
class="w-1/4 h-full flex flex-col justify-start z-10 border-dashboard px-12"
>
<div class="">
<DashboardProduct />
</div>
<div class="">
<DashboardProductComplete />
</div>
<div class="mt-10">
<DashboardImmediateTemp />
</div>
<div class="mt-10">
<DashboardFrozenTemp />
<div class="flex flex-wrap items-center">
<!-- 建築圖 -->
<div class="w-full xl:w-1/3">
<div class="area-img-box">
<Forge />
</div>
</div>
<Forge :fullScreen="true" :initialData="initialData" />
<div
class="w-1/4 flex flex-col justify-start border-dashboard z-20"
>
<DashboardForgeOptionButton :initialData="initialData" />
<div class="">
<DashboardFormulaTemp />
</div>
<DashboardEnergy />
<div class="mt-10">
<DashboardAlert />
</div>
<div class="w-full xl:w-2/3">
<!-- 用電數據 -->
<DashboardStat />
<!-- 用電圖表 -->
<DashboardElecChart />
</div>
<!-- 設備小卡 -->
<div class="w-full lg:w-2/3">
<DashboardSysCard />
</div>
<!--狀態進度-->
<div class="w-full lg:w-1/3">
<DashboardSysProgress />
</div>
</div>
</template>
<style lang="css" scoped>
.border-dashboard {
@apply border border-light-info bg-dark-info bg-opacity-70 p-3 first:mt-0 last:mb-0;
.area-img-box {
@apply border border-light-info bg-dark-info w-full h-[400px] block relative rounded-sm mb-4;
}
.area-img-box::before {
@apply absolute left-0 right-0 -top-[4px] m-auto h-[8px] w-[140px] bg-no-repeat z-10;
content: "";
background: url(@ASSET/img/area-img-box-line-top.png) center center;
}
.area-img-box::after {
@apply absolute left-0 right-0 -bottom-[4px] m-auto h-[8px] w-[140px] bg-no-repeat z-10;
content: "";
background: url(@ASSET/img/area-img-box-line-bottom.png) center center;
}
</style>

View File

@ -1,40 +0,0 @@
<script setup>
import useAlarmStore from "@/stores/useAlarmStore";
import dayjs from "dayjs";
import { computed } from "vue";
const store = useAlarmStore();
const alarms = computed(() =>
store.alarmData.slice(0, 5).map((d) => ({
...d,
timestamp_date: dayjs(d.timestamp_date).format("YYYY.MM.DD"),
timestamp_time: d.timestamp_time.split(":").slice(0, 2).join(":"),
}))
);
</script>
<template>
<div class="overflow-x-auto">
<h3 class="text-info font-bold text-xl text-center">異常資料 Top 5</h3>
<table class="table">
<thead>
<tr class="border-b-2 border-info text-base">
<th>日期</th>
<th>時間</th>
<th>設備名稱</th>
<th>備註</th>
</tr>
</thead>
<tbody class="text-base">
<tr v-for="alarm in alarms" :key="alarm.uuid">
<td>{{ alarm.timestamp_date }}</td>
<td>{{ alarm.timestamp_time }}</td>
<td>{{ alarm.full_name }}</td>
<td>{{ alarm.msg }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<style lang="scss" scoped></style>

Some files were not shown because too many files have changed in this diff Show More