From 94ee31388ed0012d2c65437bd164e6878f4c635d Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 10 四月 2026 13:31:48 +0800
Subject: [PATCH] 新疆大罗素 1.设备保养、设备维修、设备巡检新增时可以多选设备 2.设备台账添加区域维护字段 3.添加环境页面实时展示设备数据
---
src/views/equipmentManagement/upkeep/index.vue | 8
src/views/equipmentManagement/upkeep/Form/PlanModal.vue | 245 +++
src/views/equipmentManagement/repair/Modal/RepairModal.vue | 227 +++
src/views/equipmentManagement/inspectionManagement/components/formDia.vue | 595 ++++++---
src/layout/index.vue | 183 +-
src/views/equipmentManagement/repair/index.vue | 8
src/views/equipmentManagement/inspectionManagement/index.vue | 677 +++++-----
src/views/equipmentManagement/ledger/Form.vue | 42
src/views/equipmentManagement/ledger/Modal.vue | 7
src/api/equipmentManagement/deviceArea.js | 55
src/views/equipmentManagement/upkeep/Form/formDia.vue | 734 +++++++----
src/views/inventoryManagement/environmentalMonitoring/index.vue | 277 ++++
src/views/equipmentManagement/ledger/index.vue | 631 ++++++++--
src/api/inventoryManagement/environmentalMonitoring.js | 8
14 files changed, 2,536 insertions(+), 1,161 deletions(-)
diff --git a/src/api/equipmentManagement/deviceArea.js b/src/api/equipmentManagement/deviceArea.js
new file mode 100644
index 0000000..2671840
--- /dev/null
+++ b/src/api/equipmentManagement/deviceArea.js
@@ -0,0 +1,55 @@
+import request from "@/utils/request";
+
+export function getDeviceAreaTree(params) {
+ return request({
+ url: "/device/area/tree",
+ method: "get",
+ params,
+ });
+}
+
+export function getDeviceAreaTreeWithDevices(params) {
+ return request({
+ url: "/device/area/treeWithDevices",
+ method: "get",
+ });
+}
+
+export function getDeviceAreaPage(params) {
+ return request({
+ url: "/device/area/page",
+ method: "get",
+ params,
+ });
+}
+
+export function getDeviceAreaDetail(id) {
+ return request({
+ url: `/device/area/${id}`,
+ method: "get",
+ });
+}
+
+export function addDeviceArea(data) {
+ return request({
+ url: "/device/area",
+ method: "post",
+ data,
+ });
+}
+
+export function updateDeviceArea(data) {
+ return request({
+ url: "/device/area",
+ method: "put",
+ data,
+ });
+}
+
+export function deleteDeviceArea(ids) {
+ return request({
+ url: "/device/area",
+ method: "delete",
+ data: ids,
+ });
+}
diff --git a/src/api/inventoryManagement/environmentalMonitoring.js b/src/api/inventoryManagement/environmentalMonitoring.js
new file mode 100644
index 0000000..f0b7187
--- /dev/null
+++ b/src/api/inventoryManagement/environmentalMonitoring.js
@@ -0,0 +1,8 @@
+import request from "@/utils/request";
+
+export const getEnvironmentalRealData = () => {
+ return request({
+ url: "/iot/getRealData",
+ method: "get",
+ });
+};
diff --git a/src/layout/index.vue b/src/layout/index.vue
index 2d4bf01..d3580d0 100644
--- a/src/layout/index.vue
+++ b/src/layout/index.vue
@@ -1,8 +1,14 @@
<template>
- <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
- <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
- <sidebar v-if="!sidebar.hide" class="sidebar-container" />
- <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
+ <div :class="classObj"
+ class="app-wrapper"
+ :style="{ '--current-color': theme }">
+ <div v-if="device === 'mobile' && sidebar.opened"
+ class="drawer-bg"
+ @click="handleClickOutside" />
+ <sidebar v-if="!sidebar.hide"
+ class="sidebar-container" />
+ <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }"
+ class="main-container">
<div :class="{ 'fixed-header': fixedHeader }">
<navbar @setLayout="setLayout" />
<tags-view v-if="needTagsView" />
@@ -14,105 +20,112 @@
</template>
<script setup>
-import { useWindowSize } from '@vueuse/core'
-import Sidebar from './components/Sidebar/index.vue'
-import { AppMain, Navbar, Settings, TagsView } from './components'
-import defaultSettings from '@/settings'
+ import { useWindowSize } from "@vueuse/core";
+ import Sidebar from "./components/Sidebar/index.vue";
+ import { AppMain, Navbar, Settings, TagsView } from "./components";
+ import defaultSettings from "@/settings";
-import useAppStore from '@/store/modules/app'
-import useSettingsStore from '@/store/modules/settings'
+ import useAppStore from "@/store/modules/app";
+ import useSettingsStore from "@/store/modules/settings";
-const settingsStore = useSettingsStore()
-const theme = computed(() => settingsStore.theme)
-const sideTheme = computed(() => settingsStore.sideTheme)
-const sidebar = computed(() => useAppStore().sidebar)
-const device = computed(() => useAppStore().device)
-const needTagsView = computed(() => settingsStore.tagsView)
-const fixedHeader = computed(() => settingsStore.fixedHeader)
+ const settingsStore = useSettingsStore();
+ const theme = computed(() => settingsStore.theme);
+ const sideTheme = computed(() => settingsStore.sideTheme);
+ const sidebar = computed(() => useAppStore().sidebar);
+ const device = computed(() => useAppStore().device);
+ const needTagsView = computed(() => settingsStore.tagsView);
+ const fixedHeader = computed(() => settingsStore.fixedHeader);
-const classObj = computed(() => ({
- hideSidebar: !sidebar.value.opened,
- openSidebar: sidebar.value.opened,
- withoutAnimation: sidebar.value.withoutAnimation,
- mobile: device.value === 'mobile'
-}))
+ const classObj = computed(() => ({
+ hideSidebar: !sidebar.value.opened,
+ openSidebar: sidebar.value.opened,
+ withoutAnimation: sidebar.value.withoutAnimation,
+ mobile: device.value === "mobile",
+ }));
-const { width, height } = useWindowSize()
-const WIDTH = 992 // refer to Bootstrap's responsive design
+ const { width, height } = useWindowSize();
+ const WIDTH = 992; // refer to Bootstrap's responsive design
-watch(() => device.value, () => {
- if (device.value === 'mobile' && sidebar.value.opened) {
- useAppStore().closeSideBar({ withoutAnimation: false })
+ watch(
+ () => device.value,
+ () => {
+ if (device.value === "mobile" && sidebar.value.opened) {
+ useAppStore().closeSideBar({ withoutAnimation: false });
+ }
+ }
+ );
+
+ watchEffect(() => {
+ if (width.value - 1 < WIDTH) {
+ useAppStore().toggleDevice("mobile");
+ useAppStore().closeSideBar({ withoutAnimation: true });
+ } else {
+ useAppStore().toggleDevice("desktop");
+ }
+ });
+
+ function handleClickOutside() {
+ useAppStore().closeSideBar({ withoutAnimation: false });
}
-})
-watchEffect(() => {
- if (width.value - 1 < WIDTH) {
- useAppStore().toggleDevice('mobile')
- useAppStore().closeSideBar({ withoutAnimation: true })
- } else {
- useAppStore().toggleDevice('desktop')
+ const settingRef = ref(null);
+ function setLayout() {
+ settingRef.value.openSetting();
}
-})
-
-function handleClickOutside() {
- useAppStore().closeSideBar({ withoutAnimation: false })
-}
-
-const settingRef = ref(null)
-function setLayout() {
- settingRef.value.openSetting()
-}
</script>
<style lang="scss" scoped>
@import "@/assets/styles/mixin.scss";
@import "@/assets/styles/variables.module.scss";
-.app-wrapper {
- @include clearfix;
- position: relative;
- height: 100%;
- width: 100%;
- background:
- radial-gradient(circle at top, rgba(223, 232, 226, 0.95), transparent 32%),
- linear-gradient(180deg, #f7faf8 0%, var(--app-bg) 100%);
+ .app-wrapper {
+ @include clearfix;
+ position: relative;
+ height: 100%;
+ width: 100%;
+ background: radial-gradient(
+ circle at top,
+ rgba(223, 232, 226, 0.95),
+ transparent 32%
+ ),
+ linear-gradient(180deg, #f7faf8 0%, var(--app-bg) 100%);
- &.mobile.openSidebar {
- position: fixed;
- top: 0;
+ &.mobile.openSidebar {
+ position: fixed;
+ top: 0;
+ }
}
-}
-.drawer-bg {
- background: #000;
- opacity: 0.3;
- width: 100%;
- top: 0;
- height: 100%;
- position: absolute;
- z-index: 999;
-}
+ .drawer-bg {
+ background: #000;
+ opacity: 0.3;
+ width: 100%;
+ top: 0;
+ height: 100%;
+ position: absolute;
+ z-index: 999;
+ }
-.fixed-header {
- position: fixed;
- top: 12px;
- right: 16px;
- z-index: 9;
- width: calc(100% - #{$base-sidebar-width} - 32px);
- transition: width 0.28s, right 0.28s;
- padding-bottom: 8px;
-}
+ .fixed-header {
+ position: fixed;
+ top: 0px;
+ padding-top: 12px;
+ right: 16px;
+ z-index: 9;
+ width: calc(100% - #{$base-sidebar-width} - 32px);
+ transition: width 0.28s, right 0.28s;
+ padding-bottom: 8px;
+ background-color: #f3f6f4;
+ }
+ .hideSidebar .fixed-header {
+ width: calc(100% - 100px);
+ }
-.hideSidebar .fixed-header {
- width: calc(100% - 100px);
-}
+ .sidebarHide .fixed-header {
+ width: calc(100% - 32px);
+ }
-.sidebarHide .fixed-header {
- width: calc(100% - 32px);
-}
-
-.mobile .fixed-header {
- width: 100%;
-}
+ .mobile .fixed-header {
+ width: 100%;
+ }
</style>
diff --git a/src/views/equipmentManagement/inspectionManagement/components/formDia.vue b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
index 9f509b1..6432617 100644
--- a/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
+++ b/src/views/equipmentManagement/inspectionManagement/components/formDia.vue
@@ -1,26 +1,73 @@
<template>
<div>
- <el-dialog :title="operationType === 'add' ? '鏂板宸℃浠诲姟' : '缂栬緫宸℃浠诲姟'"
- v-model="dialogVisitable" width="800px" @close="cancel">
+ <el-dialog
+ v-model="dialogVisitable"
+ :title="operationType === 'add' ? '鏂板宸℃浠诲姟' : '缂栬緫宸℃浠诲姟'"
+ width="800px"
+ @close="cancel"
+ >
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
- <el-form-item label="璁惧鍚嶇О" prop="taskId">
- <el-select v-model="form.taskId" @change="setDeviceModel">
+ <el-form-item label="鎵�灞炲尯鍩�" prop="areaId">
+ <el-tree-select
+ v-model="form.areaId"
+ :data="areaOptions"
+ :props="areaTreeProps"
+ node-key="id"
+ value-key="id"
+ check-strictly
+ clearable
+ filterable
+ placeholder="璇烽�夋嫨鎵�灞炲尯鍩�"
+ style="width: 100%"
+ @change="handleAreaChange"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璁惧鍚嶇О" prop="deviceLedgerIds">
+ <el-select
+ v-model="form.deviceLedgerIds"
+ multiple
+ collapse-tags
+ collapse-tags-tooltip
+ clearable
+ filterable
+ placeholder="璇烽�夋嫨璁惧"
+ style="width: 100%"
+ @change="setDeviceModels"
+ >
<el-option
- v-for="(item, index) in deviceOptions"
- :key="index"
+ v-for="item in deviceOptions"
+ :key="item.id"
:label="item.deviceName"
:value="item.id"
- ></el-option>
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="宸℃浜�" prop="inspector">
+ <el-select v-model="form.inspector" placeholder="璇烽�夋嫨" multiple clearable>
+ <el-option
+ v-for="item in userList"
+ :key="item.userId"
+ :label="item.nickName"
+ :value="item.userId"
+ />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="宸℃浜�" prop="inspector">
- <el-select v-model="form.inspector" placeholder="璇烽�夋嫨" multiple clearable>
- <el-option v-for="item in userList" :label="item.nickName" :value="item.userId" :key="item.userId"/>
- </el-select>
+ <el-form-item label="瑙勬牸鍨嬪彿">
+ <el-input
+ v-model="form.deviceModel"
+ placeholder="鑷姩甯﹀嚭瑙勬牸鍨嬪彿"
+ disabled
+ />
</el-form-item>
</el-col>
</el-row>
@@ -35,55 +82,51 @@
<el-col :span="12">
<el-form-item label="浠诲姟棰戠巼" prop="frequencyType">
<el-select v-model="form.frequencyType" placeholder="璇烽�夋嫨" clearable>
- <el-option label="姣忔棩" value="DAILY"/>
- <el-option label="姣忓懆" value="WEEKLY"/>
- <el-option label="姣忔湀" value="MONTHLY"/>
- <!-- <el-option label="瀛e害" value="QUARTERLY"/> -->
+ <el-option label="姣忔棩" value="DAILY" />
+ <el-option label="姣忓懆" value="WEEKLY" />
+ <el-option label="姣忔湀" value="MONTHLY" />
</el-select>
</el-form-item>
</el-col>
- <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
+ <el-col :span="12" v-if="form.frequencyType === 'DAILY'">
<el-form-item label="鏃ユ湡" prop="frequencyDetail">
- <el-time-picker v-model="form.frequencyDetail" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
- value-format="HH:mm" />
- </el-form-item>
- </el-col>
- <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
- <el-form-item label="鏃ユ湡" prop="frequencyDetail">
- <el-select v-model="form.week" placeholder="璇烽�夋嫨" clearable style="width: 50%">
- <el-option label="鍛ㄤ竴" value="MON"/>
- <el-option label="鍛ㄤ簩" value="TUE"/>
- <el-option label="鍛ㄤ笁" value="WED"/>
- <el-option label="鍛ㄥ洓" value="THU"/>
- <el-option label="鍛ㄤ簲" value="FRI"/>
- <el-option label="鍛ㄥ叚" value="SAT"/>
- <el-option label="鍛ㄦ棩" value="SUN"/>
- </el-select>
- <el-time-picker v-model="form.time" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
- value-format="HH:mm" style="width: 50%"/>
- </el-form-item>
- </el-col>
- <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
- <el-form-item label="鏃ユ湡" prop="frequencyDetail">
- <el-date-picker
- v-model="form.frequencyDetail"
- type="datetime"
- clearable
- placeholder="閫夋嫨寮�濮嬫棩鏈�"
- format="DD,HH:mm"
- value-format="DD,HH:mm"
+ <el-time-picker
+ v-model="form.frequencyDetail"
+ placeholder="閫夋嫨鏃堕棿"
+ format="HH:mm"
+ value-format="HH:mm"
/>
</el-form-item>
</el-col>
- <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType">
+ <el-col :span="12" v-if="form.frequencyType === 'WEEKLY'">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-select v-model="form.week" placeholder="璇烽�夋嫨" clearable style="width: 50%">
+ <el-option label="鍛ㄤ竴" value="MON" />
+ <el-option label="鍛ㄤ簩" value="TUE" />
+ <el-option label="鍛ㄤ笁" value="WED" />
+ <el-option label="鍛ㄥ洓" value="THU" />
+ <el-option label="鍛ㄤ簲" value="FRI" />
+ <el-option label="鍛ㄥ叚" value="SAT" />
+ <el-option label="鍛ㄦ棩" value="SUN" />
+ </el-select>
+ <el-time-picker
+ v-model="form.time"
+ placeholder="閫夋嫨鏃堕棿"
+ format="HH:mm"
+ value-format="HH:mm"
+ style="width: 50%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'MONTHLY'">
<el-form-item label="鏃ユ湡" prop="frequencyDetail">
<el-date-picker
- v-model="form.frequencyDetail"
- type="datetime"
- clearable
- placeholder="閫夋嫨寮�濮嬫棩鏈�"
- format="MM,DD,HH:mm"
- value-format="MM,DD,HH:mm"
+ v-model="form.frequencyDetail"
+ type="datetime"
+ clearable
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ format="DD,HH:mm"
+ value-format="DD,HH:mm"
/>
</el-form-item>
</el-col>
@@ -100,188 +143,308 @@
</template>
<script setup>
-import {reactive, ref, getCurrentInstance, toRefs} from "vue";
-import useUserStore from '@/store/modules/user'
-import {addOrEditTimingTask} from "@/api/inspectionManagement/index.js";
-import {userListNoPageByTenantId} from "@/api/system/user.js";
-import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
+import { getCurrentInstance, reactive, ref, toRefs } from "vue";
+import useUserStore from "@/store/modules/user";
+import { addOrEditTimingTask } from "@/api/inspectionManagement/index.js";
+import { userListNoPageByTenantId } from "@/api/system/user.js";
+import {
+ getDeviceAreaTree,
+ getDeviceAreaTreeWithDevices,
+} from "@/api/equipmentManagement/deviceArea";
-const { proxy } = getCurrentInstance()
-const emit = defineEmits()
-const userStore = useUserStore()
+const { proxy } = getCurrentInstance();
+const emit = defineEmits(["closeDia"]);
+const userStore = useUserStore();
const dialogVisitable = ref(false);
-const operationType = ref('add');
+const operationType = ref("add");
+const areaOptions = ref([]);
const deviceOptions = ref([]);
-const data = reactive({
- form: {
- taskId: undefined,
- taskName: undefined,
- inspector: '',
- inspectorIds: '',
- remarks: '',
- frequencyType: '',
- frequencyDetail: '',
- week: '',
- time: ''
- },
- rules: {
- taskId: [{ required: true, message: "璇烽�夋嫨璁惧", trigger: "change" },],
- inspector: [{ required: true, message: "璇疯緭鍏ュ贰妫�浜�", trigger: "blur" },],
- dateStr: [{ required: true, message: "璇烽�夋嫨鐧昏鏃堕棿", trigger: "change" }],
- frequencyType: [{ required: true, message: "璇烽�夋嫨浠诲姟棰戠巼", trigger: "change" }],
- frequencyDetail: [
- {
- required: true,
- message: "璇烽�夋嫨鏃ユ湡",
- trigger: "change",
- validator: (rule, value, callback) => {
- if (!form.value.frequencyType) {
- callback()
- return
- }
- if (form.value.frequencyType === 'WEEKLY') {
- if (!form.value.week || !form.value.time) {
- callback(new Error("璇烽�夋嫨鏃ユ湡鍜屾椂闂�"))
- } else {
- callback()
- }
- } else {
- if (!value) {
- callback(new Error("璇烽�夋嫨鏃ユ湡"))
- } else {
- callback()
- }
- }
- }
- }
- ],
- week: [
- {
- required: true,
- message: "璇烽�夋嫨鏄熸湡",
- trigger: "change",
- validator: (rule, value, callback) => {
- if (form.value.frequencyType === 'WEEKLY' && !value) {
- callback(new Error("璇烽�夋嫨鏄熸湡"))
- } else {
- callback()
- }
- }
- }
- ],
- time: [
- {
- required: true,
- message: "璇烽�夋嫨鏃堕棿",
- trigger: "change",
- validator: (rule, value, callback) => {
- if (form.value.frequencyType === 'WEEKLY' && !value) {
- callback(new Error("璇烽�夋嫨鏃堕棿"))
- } else {
- callback()
- }
- }
- }
- ]
- }
-})
-const { form, rules } = toRefs(data)
-const userList = ref([])
-
-const loadDeviceName = async () => {
- const { data } = await getDeviceLedger();
- deviceOptions.value = data;
+const userList = ref([]);
+const areaTreeProps = {
+ label: "areaName",
+ children: "children",
};
-const setDeviceModel = (id) => {
- const option = deviceOptions.value.find((item) => item.id === id);
- if (option) {
- form.value.taskName = option.deviceName;
- }
-}
+const data = reactive({
+ form: {
+ areaId: undefined,
+ taskId: undefined,
+ taskIds: [],
+ taskIdsStr: undefined,
+ deviceLedgerIds: [],
+ deviceLedgerIdsStr: undefined,
+ taskName: undefined,
+ deviceModel: undefined,
+ inspector: [],
+ inspectorIds: "",
+ remarks: "",
+ frequencyType: "",
+ frequencyDetail: "",
+ week: "",
+ time: "",
+ },
+ rules: {
+ areaId: [{ required: true, message: "璇烽�夋嫨鎵�灞炲尯鍩�", trigger: "change" }],
+ deviceLedgerIds: [{ required: true, message: "璇烽�夋嫨璁惧", trigger: "change" }],
+ inspector: [{ required: true, message: "璇烽�夋嫨宸℃浜�", trigger: "change" }],
+ frequencyType: [{ required: true, message: "璇烽�夋嫨浠诲姟棰戠巼", trigger: "change" }],
+ frequencyDetail: [
+ {
+ required: true,
+ trigger: "change",
+ validator: (rule, value, callback) => {
+ if (!form.value.frequencyType) {
+ callback();
+ return;
+ }
+ if (form.value.frequencyType === "WEEKLY") {
+ if (!form.value.week || !form.value.time) {
+ callback(new Error("璇烽�夋嫨鏃ユ湡鍜屾椂闂�"));
+ } else {
+ callback();
+ }
+ return;
+ }
+ if (!value) {
+ callback(new Error("璇烽�夋嫨鏃ユ湡"));
+ return;
+ }
+ callback();
+ },
+ },
+ ],
+ },
+});
-// 鎵撳紑寮规
+const { form, rules } = toRefs(data);
+
+const loadAreaTree = async () => {
+ const { data } = await getDeviceAreaTree();
+ areaOptions.value = Array.isArray(data) ? data : [];
+};
+
+const normalizeIdList = (value) => {
+ if (Array.isArray(value)) {
+ return value
+ .map((item) => Number(item))
+ .filter((item) => Number.isFinite(item));
+ }
+ if (typeof value === "string") {
+ return value
+ .split(",")
+ .map((item) => Number(item.trim()))
+ .filter((item) => Number.isFinite(item));
+ }
+ if (value !== undefined && value !== null && value !== "") {
+ const numericValue = Number(value);
+ return Number.isFinite(numericValue) ? [numericValue] : [];
+ }
+ return [];
+};
+
+const getNodeDevices = (node) => {
+ const candidates = [
+ node?.deviceList,
+ node?.devices,
+ node?.deviceLedgerList,
+ node?.deviceLedgers,
+ node?.ledgerList,
+ node?.ledgers,
+ ];
+ return candidates.find((item) => Array.isArray(item)) || [];
+};
+
+const normalizeDevice = (item) => ({
+ ...item,
+ id: item.id ?? item.deviceLedgerId,
+ deviceName: item.deviceName ?? item.name,
+ deviceModel: item.deviceModel ?? item.model,
+});
+
+const collectDevices = (node) => {
+ const currentDevices = getNodeDevices(node).map(normalizeDevice);
+ const childDevices = (node?.children || []).flatMap((child) =>
+ collectDevices(child)
+ );
+ const deviceMap = new Map();
+ [...currentDevices, ...childDevices].forEach((item) => {
+ if (item?.id !== undefined && item?.id !== null) {
+ deviceMap.set(Number(item.id), item);
+ }
+ });
+ return Array.from(deviceMap.values());
+};
+
+const findAreaNode = (nodes, areaId) => {
+ for (const node of nodes || []) {
+ if (Number(node.id) === Number(areaId)) {
+ return node;
+ }
+ const target = findAreaNode(node.children, areaId);
+ if (target) {
+ return target;
+ }
+ }
+ return null;
+};
+
+const loadDevicesByArea = async (areaId) => {
+ if (!areaId) {
+ deviceOptions.value = [];
+ return;
+ }
+ const { data } = await getDeviceAreaTreeWithDevices();
+ const treeData = Array.isArray(data) ? data : [];
+ const currentNode = findAreaNode(treeData, areaId);
+ deviceOptions.value = currentNode ? collectDevices(currentNode) : [];
+};
+
+const syncDeviceFields = (deviceIds) => {
+ const selectedIds = normalizeIdList(deviceIds);
+ const selectedDevices = selectedIds
+ .map((deviceId) =>
+ deviceOptions.value.find((item) => Number(item.id) === Number(deviceId))
+ )
+ .filter(Boolean);
+
+ form.value.deviceLedgerIds = selectedIds;
+ form.value.deviceLedgerIdsStr = selectedIds.join(",");
+ form.value.taskIds = [...selectedIds];
+ form.value.taskIdsStr = selectedIds.join(",");
+ form.value.taskId = selectedIds[0];
+ form.value.taskName = selectedDevices
+ .map((item) => item.deviceName)
+ .filter(Boolean)
+ .join(",");
+ form.value.deviceModel = selectedDevices
+ .map((item) => item.deviceModel || "-")
+ .join(",");
+};
+
+const setDeviceModels = (deviceIds) => {
+ syncDeviceFields(deviceIds);
+};
+
+const handleAreaChange = async (areaId) => {
+ form.value.taskId = undefined;
+ form.value.taskIds = [];
+ form.value.taskIdsStr = undefined;
+ form.value.deviceLedgerIds = [];
+ form.value.deviceLedgerIdsStr = undefined;
+ form.value.taskName = undefined;
+ form.value.deviceModel = undefined;
+ await loadDevicesByArea(areaId);
+};
+
+const resetForm = () => {
+ if (proxy.$refs.formRef) {
+ proxy.$refs.formRef.resetFields();
+ }
+ form.value = {
+ areaId: undefined,
+ taskId: undefined,
+ taskIds: [],
+ taskIdsStr: undefined,
+ deviceLedgerIds: [],
+ deviceLedgerIdsStr: undefined,
+ taskName: undefined,
+ deviceModel: undefined,
+ inspector: [],
+ inspectorIds: "",
+ remarks: "",
+ frequencyType: "",
+ frequencyDetail: "",
+ week: "",
+ time: "",
+ };
+};
+
const openDialog = async (type, row) => {
- dialogVisitable.value = true
- operationType.value = type
-
- // 閲嶇疆琛ㄥ崟
+ dialogVisitable.value = true;
+ operationType.value = type;
resetForm();
-
- // 鍔犺浇鐢ㄦ埛鍒楄〃
+
userListNoPageByTenantId().then((res) => {
userList.value = res.data;
});
-
- // 鍔犺浇璁惧鍒楄〃
- await loadDeviceName();
-
- if (type === 'edit' && row) {
- form.value = {...row}
- form.value.inspector = form.value.inspectorIds.split(',').map(Number)
-
- // 濡傛灉鏈夎澶嘔D锛岃嚜鍔ㄨ缃澶囦俊鎭�
- if (form.value.taskId) {
- setDeviceModel(form.value.taskId);
+
+ await loadAreaTree();
+
+ if (type === "edit" && row) {
+ form.value = {
+ ...form.value,
+ ...row,
+ inspector: row.inspectorIds
+ ? String(row.inspectorIds)
+ .split(",")
+ .map((item) => Number(item))
+ .filter((item) => Number.isFinite(item))
+ : [],
+ };
+
+ form.value.deviceLedgerIds = normalizeIdList(
+ row.deviceLedgerIds ??
+ row.deviceLedgerIdsStr ??
+ row.taskIds ??
+ row.taskIdsStr ??
+ row.taskId
+ );
+ form.value.deviceLedgerIdsStr = form.value.deviceLedgerIds.join(",");
+ form.value.taskIds = [...form.value.deviceLedgerIds];
+ form.value.taskIdsStr = form.value.deviceLedgerIds.join(",");
+ form.value.taskId = form.value.deviceLedgerIds[0];
+
+ if (form.value.areaId) {
+ await loadDevicesByArea(form.value.areaId);
+ syncDeviceFields(form.value.deviceLedgerIds);
}
}
-}
+};
-// 鍏抽棴瀵硅瘽妗�
const cancel = () => {
- resetForm()
- dialogVisitable.value = false
- emit('closeDia')
-}
+ resetForm();
+ dialogVisitable.value = false;
+ emit("closeDia");
+};
-// 閲嶇疆琛ㄥ崟鍑芥暟
-const resetForm = () => {
- if (proxy.$refs.formRef) {
- proxy.$refs.formRef.resetFields()
- }
- // 閲嶇疆琛ㄥ崟鏁版嵁纭繚璁惧淇℃伅姝g‘閲嶇疆
- form.value = {
- taskId: undefined,
- taskName: undefined,
- inspector: '',
- inspectorIds: '',
- remarks: '',
- frequencyType: '',
- frequencyDetail: '',
- week: '',
- time: ''
- }
-}
-
-// 鎻愪氦琛ㄥ崟
const submitForm = () => {
- proxy.$refs["formRef"].validate(async valid => {
- if (valid) {
- try {
- form.value.inspectorIds = form.value.inspector.join(',')
- delete form.value.inspector
-
- if (form.value.frequencyType === 'WEEKLY') {
- let frequencyDetail = ''
- frequencyDetail = form.value.week + ',' + form.value.time
- form.value.frequencyDetail = frequencyDetail
- }
-
- let res = await userStore.getInfo()
- form.value.registrantId = res.user.userId
-
- await addOrEditTimingTask(form.value)
- cancel()
- proxy.$modal.msgSuccess('鎻愪氦鎴愬姛')
- } catch (error) {
- proxy.$modal.msgError('鎻愪氦澶辫触锛岃閲嶈瘯')
- }
+ proxy.$refs.formRef.validate(async (valid) => {
+ if (!valid) {
+ return;
}
- })
-}
-defineExpose({ openDialog })
+ try {
+ syncDeviceFields(form.value.deviceLedgerIds);
+ const payload = { ...form.value };
+
+ payload.inspectorIds = Array.isArray(form.value.inspector)
+ ? form.value.inspector.join(",")
+ : "";
+ delete payload.inspector;
+
+ if (payload.frequencyType === "WEEKLY") {
+ payload.frequencyDetail = `${payload.week},${payload.time}`;
+ }
+
+ const userInfo = await userStore.getInfo();
+ payload.registrantId = userInfo.user.userId;
+ payload.taskId = form.value.deviceLedgerIds[0];
+ payload.taskIds = [...form.value.deviceLedgerIds];
+ payload.taskIdsStr = form.value.deviceLedgerIds.join(",");
+ payload.deviceLedgerIds = [...form.value.deviceLedgerIds];
+ payload.deviceLedgerIdsStr = form.value.deviceLedgerIds.join(",");
+ payload.taskName = form.value.taskName;
+ payload.deviceModel = form.value.deviceModel || "-";
+
+ await addOrEditTimingTask(payload);
+ cancel();
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ } catch (error) {
+ proxy.$modal.msgError("鎻愪氦澶辫触锛岃閲嶈瘯");
+ }
+ });
+};
+
+defineExpose({ openDialog });
</script>
-<style scoped>
-
-</style>
\ No newline at end of file
+<style scoped></style>
diff --git a/src/views/equipmentManagement/inspectionManagement/index.vue b/src/views/equipmentManagement/inspectionManagement/index.vue
index 35f82d5..ef15b75 100644
--- a/src/views/equipmentManagement/inspectionManagement/index.vue
+++ b/src/views/equipmentManagement/inspectionManagement/index.vue
@@ -1,393 +1,360 @@
<template>
<div class="app-container">
- <el-form :inline="true"
- :model="queryParams"
- class="search-form">
+ <el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="宸℃浠诲姟鍚嶇О">
- <el-input v-model="queryParams.taskName"
- placeholder="璇疯緭鍏ュ贰妫�浠诲姟鍚嶇О"
- clearable
- style="width: 200px " />
+ <el-input
+ v-model="queryParams.taskName"
+ placeholder="璇疯緭鍏ュ贰妫�浠诲姟鍚嶇О"
+ clearable
+ style="width: 200px"
+ />
+ </el-form-item>
+ <el-form-item label="鎵�灞炲尯鍩�">
+ <el-tree-select
+ v-model="queryParams.areaId"
+ :data="areaOptions"
+ :props="areaTreeProps"
+ node-key="id"
+ value-key="id"
+ check-strictly
+ clearable
+ filterable
+ placeholder="璇烽�夋嫨鎵�灞炲尯鍩�"
+ style="width: 220px"
+ />
</el-form-item>
<el-form-item>
- <el-button type="primary"
- @click="handleQuery">鏌ヨ</el-button>
+ <el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
<el-button @click="resetQuery">閲嶇疆</el-button>
</el-form-item>
</el-form>
<el-card>
- <div style="display: flex;flex-direction: row;justify-content: space-between;margin-bottom: 10px;">
- <el-radio-group v-model="activeRadio"
- @change="radioChange">
- <el-radio-button v-for="tab in radios"
- :key="tab.name"
- :label="tab.label"
- :value="tab.name" />
+ <div class="toolbar">
+ <el-radio-group v-model="activeRadio" @change="radioChange">
+ <el-radio-button
+ v-for="tab in radios"
+ :key="tab.name"
+ :label="tab.label"
+ :value="tab.name"
+ />
</el-radio-group>
- <!-- 鎿嶄綔鎸夐挳鍖� -->
<el-space v-if="activeRadio !== 'task'">
- <el-button type="primary"
- :icon="Plus"
- @click="handleAdd(undefined)">鏂板缓</el-button>
- <el-button type="danger"
- :icon="Delete"
- @click="handleDelete">鍒犻櫎</el-button>
+ <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">鏂板缓</el-button>
+ <el-button type="danger" :icon="Delete" @click="handleDelete">鍒犻櫎</el-button>
<el-button @click="handleOut">瀵煎嚭</el-button>
</el-space>
<el-space v-else>
<el-button @click="handleOut">瀵煎嚭</el-button>
</el-space>
</div>
- <div>
- <PIMTable :table-loading="tableLoading"
- :table-data="tableData"
- :column="tableColumns"
- @selection-change="handleSelectionChange"
- @pagination="handlePagination"
- :is-selection="true"
- :border="true"
- :page="{
- current: pageNum,
- size: pageSize,
- total: total,
- layout: 'total, sizes, prev, pager, next, jumper'
- }"
- :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }">
- <template #inspector="{ row }">
- <div class="person-tags">
- <!-- 璋冭瘯淇℃伅锛屼笂绾挎椂鍒犻櫎 -->
- <!-- {{ console.log('inspector data:', row.inspector) }} -->
- <template v-if="row.inspector && row.inspector.length > 0">
- <el-tag v-for="(person, index) in row.inspector"
- :key="index"
- size="small"
- type="primary"
- class="person-tag">
- {{ person }}
- </el-tag>
- </template>
- <span v-else
- class="no-data">--</span>
- </div>
- </template>
- </PIMTable>
- </div>
+ <PIMTable
+ :table-loading="tableLoading"
+ :table-data="tableData"
+ :column="tableColumns"
+ :is-selection="true"
+ :border="true"
+ :page="{
+ current: pageNum,
+ size: pageSize,
+ total,
+ layout: 'total, sizes, prev, pager, next, jumper',
+ }"
+ :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }"
+ @selection-change="handleSelectionChange"
+ @pagination="handlePagination"
+ >
+ <template #inspector="{ row }">
+ <div class="person-tags">
+ <template v-if="row.inspector && row.inspector.length > 0">
+ <el-tag
+ v-for="(person, index) in row.inspector"
+ :key="index"
+ size="small"
+ type="primary"
+ class="person-tag"
+ >
+ {{ person }}
+ </el-tag>
+ </template>
+ <span v-else class="no-data">--</span>
+ </div>
+ </template>
+ </PIMTable>
</el-card>
- <form-dia ref="formDia"
- @closeDia="handleQuery"></form-dia>
- <view-files ref="viewFiles"></view-files>
+ <form-dia ref="formDia" @closeDia="handleQuery" />
+ <view-files ref="viewFiles" />
</div>
</template>
<script setup>
- import { Delete, Plus } from "@element-plus/icons-vue";
- import { onMounted, ref, reactive, getCurrentInstance, nextTick } from "vue";
- import { ElMessageBox } from "element-plus";
+import { Delete, Plus } from "@element-plus/icons-vue";
+import { ElMessageBox } from "element-plus";
+import { getCurrentInstance, nextTick, onMounted, reactive, ref } from "vue";
+import PIMTable from "@/components/PIMTable/PIMTable.vue";
+import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue";
+import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue";
+import {
+ delTimingTask,
+ inspectionTaskList,
+ timingTaskList,
+} from "@/api/inspectionManagement/index.js";
+import { getDeviceAreaTree } from "@/api/equipmentManagement/deviceArea";
- // 缁勪欢寮曞叆
- import PIMTable from "@/components/PIMTable/PIMTable.vue";
- import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue";
- import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue";
+const { proxy } = getCurrentInstance();
+const formDia = ref();
+const viewFiles = ref();
- // 鎺ュ彛寮曞叆
- import {
- delTimingTask,
- inspectionTaskList,
- timingTaskList,
- } from "@/api/inspectionManagement/index.js";
+const queryParams = reactive({
+ taskName: "",
+ areaId: undefined,
+});
- // 鍏ㄥ眬鍙橀噺
- const { proxy } = getCurrentInstance();
- const formDia = ref();
- const viewFiles = ref();
+const areaOptions = ref([]);
+const areaTreeProps = {
+ label: "areaName",
+ children: "children",
+};
- // 鏌ヨ鍙傛暟
- const queryParams = reactive({
- taskName: "",
- });
+const activeRadio = ref("taskManage");
+const radios = reactive([
+ { name: "taskManage", label: "瀹氭椂浠诲姟绠$悊" },
+ { name: "task", label: "瀹氭椂浠诲姟璁板綍" },
+]);
- // 鍗曢�夋閰嶇疆
- const activeRadio = ref("taskManage");
- const radios = reactive([
- { name: "taskManage", label: "瀹氭椂浠诲姟绠$悊" },
- { name: "task", label: "瀹氭椂浠诲姟璁板綍" },
- ]);
+const selectedRows = ref([]);
+const tableData = ref([]);
+const tableColumns = ref([]);
+const tableLoading = ref(false);
+const total = ref(0);
+const pageNum = ref(1);
+const pageSize = ref(10);
- // 琛ㄦ牸鏁版嵁
- const selectedRows = ref([]);
- const tableData = ref([]);
- const operationsArr = ref([]);
- const tableColumns = ref([]);
- const tableLoading = ref(false);
- const total = ref(0);
- const pageNum = ref(1);
- const pageSize = ref(10);
-
- // 鍒楅厤缃�
- const columns = ref([
- { prop: "taskName", label: "宸℃浠诲姟鍚嶇О", minWidth: 160 },
- { prop: "remarks", label: "澶囨敞", minWidth: 150 },
- { prop: "inspector", label: "鎵ц宸℃浜�", minWidth: 150, slot: "inspector" },
- {
- prop: "frequencyType",
- label: "棰戞",
- minWidth: 150,
- // formatter: (_, __, val) => ({
- // DAILY: "姣忔棩",
- // WEEKLY: "姣忓懆",
- // MONTHLY: "姣忔湀",
- // QUARTERLY: "瀛e害"
- // }[val] || "")
- formatData: params => {
- return params === "DAILY"
- ? "姣忔棩"
- : params === "WEEKLY"
- ? "姣忓懆"
- : params === "MONTHLY"
- ? "姣忔湀"
- : params === "QUARTERLY"
- ? "瀛e害"
- : "";
- },
- },
- {
- prop: "frequencyDetail",
- label: "寮�濮嬫棩鏈熶笌鏃堕棿",
- minWidth: 150,
- formatter: (row, column, cellValue) => {
- // 鍏堝垽鏂槸鍚︽槸瀛楃涓�
- if (typeof cellValue !== "string") return "";
- let val = cellValue;
- const replacements = {
- MON: "鍛ㄤ竴",
- TUE: "鍛ㄤ簩",
- WED: "鍛ㄤ笁",
- THU: "鍛ㄥ洓",
- FRI: "鍛ㄤ簲",
- SAT: "鍛ㄥ叚",
- SUN: "鍛ㄦ棩",
- };
- // 浣跨敤姝e垯涓�娆℃�ф浛鎹㈡墍鏈夊尮閰嶉」
- return val.replace(
- /MON|TUE|WED|THU|FRI|SAT|SUN/g,
- match => replacements[match]
- );
- },
- },
- { prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
- { prop: "createTime", label: "鐧昏鏃ユ湡", minWidth: 100 },
- ]);
-
- // 鎿嶄綔鍒楅厤缃�
- const getOperationColumn = operations => {
- if (!operations || operations.length === 0) return null;
-
- const operationConfig = {
- label: "鎿嶄綔",
- width: 130,
- fixed: "right",
- dataType: "action",
- operation: operations
- .map(op => {
- switch (op) {
- case "edit":
- return {
- name: "缂栬緫",
- clickFun: handleAdd,
- color: "#409EFF",
- };
- case "viewFile":
- return {
- name: "鏌ョ湅闄勪欢",
- clickFun: viewFile,
- color: "#67C23A",
- };
- default:
- return null;
- }
- })
- .filter(Boolean),
- };
-
- return operationConfig;
- };
-
- onMounted(() => {
- radioChange("taskManage");
- });
-
- // 鍗曢�夊彉鍖�
- const radioChange = value => {
- if (value === "taskManage") {
- const operationColumn = getOperationColumn(["edit"]);
- tableColumns.value = [
- ...columns.value,
- ...(operationColumn ? [operationColumn] : []),
- ];
- operationsArr.value = ["edit"];
- } else if (value === "task") {
- const operationColumn = getOperationColumn(["viewFile"]);
- tableColumns.value = [
- ...columns.value,
- ...(operationColumn ? [operationColumn] : []),
- ];
- operationsArr.value = ["viewFile"];
- }
- pageNum.value = 1;
- pageSize.value = 10;
- getList();
- };
-
- // 鏌ヨ鎿嶄綔
- const handleQuery = () => {
- pageNum.value = 1;
- pageSize.value = 10;
- getList();
- };
- // 鍒嗛〉澶勭悊
- const handlePagination = val => {
- pageNum.value = val.page;
- pageSize.value = val.limit;
- getList();
- };
- // 鑾峰彇鍒楄〃鏁版嵁
- const getList = () => {
- tableLoading.value = true;
-
- const params = {
- ...queryParams,
- size: pageSize.value,
- current: pageNum.value,
- };
-
- let apiCall;
- if (activeRadio.value === "task") {
- apiCall = inspectionTaskList(params);
- } else {
- apiCall = timingTaskList(params);
- }
-
- apiCall
- .then(res => {
- const rawData = res.data.records || [];
- // 澶勭悊 inspector 瀛楁锛屽皢瀛楃涓茶浆鎹负鏁扮粍锛堥�傜敤浜庢墍鏈夋儏鍐碉級
- tableData.value = rawData.map(item => {
- const processedItem = { ...item };
-
- // 澶勭悊 inspector 瀛楁
- if (processedItem.inspector) {
- if (typeof processedItem.inspector === "string") {
- // 瀛楃涓叉寜閫楀彿鍒嗗壊
- processedItem.inspector = processedItem.inspector
- .split(",")
- .map(s => s.trim())
- .filter(s => s);
- } else if (!Array.isArray(processedItem.inspector)) {
- // 闈炴暟缁勮浆涓烘暟缁�
- processedItem.inspector = [processedItem.inspector];
- }
- } else {
- // 绌哄�艰涓虹┖鏁扮粍
- processedItem.inspector = [];
- }
-
- return processedItem;
- });
- total.value = res.data.total || 0;
- })
- .finally(() => {
- tableLoading.value = false;
- });
- };
-
- // 閲嶇疆鏌ヨ
- const resetQuery = () => {
- for (const key in queryParams) {
- if (!["pageNum", "pageSize"].includes(key)) {
- queryParams[key] = "";
+const columns = ref([
+ {
+ label: "鎵�鍦ㄥ尯鍩�",
+ prop: "areaName",
+ },
+ { prop: "taskName", label: "宸℃浠诲姟鍚嶇О", minWidth: 160 },
+ { prop: "remarks", label: "澶囨敞", minWidth: 150 },
+ { prop: "inspector", label: "鎵ц宸℃浜�", minWidth: 150, slot: "inspector" },
+ {
+ prop: "frequencyType",
+ label: "棰戞",
+ minWidth: 150,
+ formatData: (value) =>
+ ({
+ DAILY: "姣忔棩",
+ WEEKLY: "姣忓懆",
+ MONTHLY: "姣忔湀",
+ QUARTERLY: "瀛e害",
+ }[value] || ""),
+ },
+ {
+ prop: "frequencyDetail",
+ label: "寮�濮嬫棩鏈熶笌鏃堕棿",
+ minWidth: 150,
+ formatter: (row, column, cellValue) => {
+ if (typeof cellValue !== "string") {
+ return "";
}
- }
- handleQuery();
- };
+ const replacements = {
+ MON: "鍛ㄤ竴",
+ TUE: "鍛ㄤ簩",
+ WED: "鍛ㄤ笁",
+ THU: "鍛ㄥ洓",
+ FRI: "鍛ㄤ簲",
+ SAT: "鍛ㄥ叚",
+ SUN: "鍛ㄦ棩",
+ };
+ return cellValue.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, (match) => replacements[match]);
+ },
+ },
+ { prop: "registrant", label: "鐧昏浜�", minWidth: 100 },
+ { prop: "createTime", label: "鐧昏鏃ユ湡", minWidth: 100 },
+]);
- // 鏂板 / 缂栬緫
- const handleAdd = row => {
- const type = row ? "edit" : "add";
- nextTick(() => {
- formDia.value?.openDialog(type, row);
- });
- };
-
- // 鏌ョ湅闄勪欢
- const viewFile = row => {
- nextTick(() => {
- viewFiles.value?.openDialog(row);
- });
- };
-
- // 鍒犻櫎鎿嶄綔
- const handleDelete = () => {
- if (!selectedRows.value.length) {
- proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
- return;
- }
-
- const deleteIds = selectedRows.value.map(item => item.id);
-
- proxy.$modal
- .confirm("鏄惁纭鍒犻櫎鎵�閫夋暟鎹」锛�")
- .then(() => {
- return delTimingTask(deleteIds);
- })
- .then(() => {
- proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
- handleQuery();
- })
- .catch(() => {});
- };
-
- // 澶氶�夊彉鏇�
- const handleSelectionChange = selection => {
- selectedRows.value = selection;
- };
-
- // 瀵煎嚭
- const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- // 鏍规嵁褰撳墠閫変腑鐨勬爣绛鹃〉璋冪敤涓嶅悓鐨勫鍑烘帴鍙�
- if (activeRadio.value === "taskManage") {
- // 瀹氭椂浠诲姟绠$悊
- proxy.download("/timingTask/export", {}, "瀹氭椂浠诲姟绠$悊.xlsx");
- } else if (activeRadio.value === "task") {
- // 瀹氭椂浠诲姟璁板綍
- proxy.download("/inspectionTask/export", {}, "瀹氭椂浠诲姟璁板綍.xlsx");
+const getOperationColumn = (operations) => {
+ if (!operations || operations.length === 0) {
+ return null;
+ }
+ return {
+ label: "鎿嶄綔",
+ width: 130,
+ fixed: "right",
+ dataType: "action",
+ operation: operations
+ .map((op) => {
+ switch (op) {
+ case "edit":
+ return {
+ name: "缂栬緫",
+ clickFun: handleAdd,
+ color: "#409EFF",
+ };
+ case "viewFile":
+ return {
+ name: "鏌ョ湅闄勪欢",
+ clickFun: viewFile,
+ color: "#67C23A",
+ };
+ default:
+ return null;
}
})
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
+ .filter(Boolean),
};
+};
+
+const loadAreaTree = async () => {
+ const { data } = await getDeviceAreaTree();
+ areaOptions.value = Array.isArray(data) ? data : [];
+};
+
+onMounted(() => {
+ loadAreaTree();
+ radioChange("taskManage");
+});
+
+const radioChange = (value) => {
+ if (value === "taskManage") {
+ const operationColumn = getOperationColumn(["edit"]);
+ tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])];
+ } else {
+ const operationColumn = getOperationColumn(["viewFile"]);
+ tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])];
+ }
+ pageNum.value = 1;
+ pageSize.value = 10;
+ getList();
+};
+
+const handleQuery = () => {
+ pageNum.value = 1;
+ pageSize.value = 10;
+ getList();
+};
+
+const handlePagination = (val) => {
+ pageNum.value = val.page;
+ pageSize.value = val.limit;
+ getList();
+};
+
+const getList = () => {
+ tableLoading.value = true;
+ const params = {
+ ...queryParams,
+ size: pageSize.value,
+ current: pageNum.value,
+ };
+ const apiCall =
+ activeRadio.value === "task" ? inspectionTaskList(params) : timingTaskList(params);
+
+ apiCall
+ .then((res) => {
+ const rawData = res?.data?.records || [];
+ tableData.value = rawData.map((item) => {
+ const processedItem = { ...item };
+ if (processedItem.inspector) {
+ if (typeof processedItem.inspector === "string") {
+ processedItem.inspector = processedItem.inspector
+ .split(",")
+ .map((text) => text.trim())
+ .filter(Boolean);
+ } else if (!Array.isArray(processedItem.inspector)) {
+ processedItem.inspector = [processedItem.inspector];
+ }
+ } else {
+ processedItem.inspector = [];
+ }
+ return processedItem;
+ });
+ total.value = res?.data?.total || 0;
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+};
+
+const resetQuery = () => {
+ queryParams.taskName = "";
+ queryParams.areaId = undefined;
+ handleQuery();
+};
+
+const handleAdd = (row) => {
+ const type = row ? "edit" : "add";
+ nextTick(() => {
+ formDia.value?.openDialog(type, row);
+ });
+};
+
+const viewFile = (row) => {
+ nextTick(() => {
+ viewFiles.value?.openDialog(row);
+ });
+};
+
+const handleDelete = () => {
+ if (!selectedRows.value.length) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑鏁版嵁");
+ return;
+ }
+ const deleteIds = selectedRows.value.map((item) => item.id);
+ proxy.$modal
+ .confirm("鏄惁纭鍒犻櫎鎵�閫夋暟鎹」锛�")
+ .then(() => delTimingTask(deleteIds))
+ .then(() => {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ handleQuery();
+ })
+ .catch(() => {});
+};
+
+const handleSelectionChange = (selection) => {
+ selectedRows.value = selection;
+};
+
+const handleOut = () => {
+ ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ confirmButtonText: "纭",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ })
+ .then(() => {
+ if (activeRadio.value === "taskManage") {
+ proxy.download("/timingTask/export", {}, "瀹氭椂浠诲姟绠$悊.xlsx");
+ } else {
+ proxy.download("/inspectionTask/export", {}, "瀹氭椂浠诲姟璁板綍.xlsx");
+ }
+ })
+ .catch(() => {
+ proxy.$modal.msg("宸插彇娑�");
+ });
+};
</script>
<style scoped>
- .person-tags {
- display: flex;
- flex-wrap: wrap;
- gap: 4px;
- }
+.toolbar {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
- .person-tag {
- margin-right: 4px;
- margin-bottom: 2px;
- }
+.person-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+}
- .no-data {
- color: #909399;
- font-size: 14px;
- }
-</style>
\ No newline at end of file
+.person-tag {
+ margin-right: 4px;
+ margin-bottom: 2px;
+}
+
+.no-data {
+ color: #909399;
+ font-size: 14px;
+}
+</style>
diff --git a/src/views/equipmentManagement/ledger/Form.vue b/src/views/equipmentManagement/ledger/Form.vue
index 72d594e..5cfa309 100644
--- a/src/views/equipmentManagement/ledger/Form.vue
+++ b/src/views/equipmentManagement/ledger/Form.vue
@@ -2,6 +2,22 @@
<el-form :model="form" label-width="120px" :rules="formRules" ref="formRef">
<el-row :gutter="20">
<el-col :span="12">
+ <el-form-item label="鎵�灞炲尯鍩�" prop="areaId">
+ <el-tree-select
+ v-model="form.areaId"
+ :data="areaOptions"
+ :props="areaTreeProps"
+ node-key="id"
+ value-key="id"
+ check-strictly
+ clearable
+ filterable
+ placeholder="璇烽�夋嫨鎵�灞炲尯鍩�"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
<el-form-item label="璁惧鍚嶇О" prop="deviceName">
<el-input v-model="form.deviceName" placeholder="璇疯緭鍏ヨ澶囧悕绉�" />
</el-form-item>
@@ -72,7 +88,6 @@
<el-form-item label="鏁伴噺" prop="number">
<el-input-number :min="1" style="width: 100%"
v-model="form.number"
- disabled
placeholder="璇疯緭鍏ユ暟閲�"
@change="mathNum"
/>
@@ -168,19 +183,25 @@
import useFormData from "@/hooks/useFormData";
// import useUserStore from "@/store/modules/user";
import { getLedgerById } from "@/api/equipmentManagement/ledger";
+import { getDeviceAreaTree } from "@/api/equipmentManagement/deviceArea";
import dayjs from "dayjs";
import {
calculateTaxIncludeTotalPrice,
calculateTaxExclusiveTotalPrice,
} from "@/utils/summarizeTable";
import { ElMessage } from "element-plus";
-import {ref} from "vue";
+import { ref, onMounted } from "vue";
defineOptions({
name: "璁惧鍙拌处琛ㄥ崟",
});
const formRef = ref(null);
const operationType = ref('');
+const areaOptions = ref([]);
+const areaTreeProps = {
+ label: "areaName",
+ children: "children",
+};
// 璁惧绫诲瀷鍥哄畾閫夐」
const deviceTypeOptions = ref([
'鐢熶骇璁惧',
@@ -190,6 +211,7 @@
'鍏朵粬璁惧'
]);
const formRules = {
+ areaId: [{ required: true, trigger: "change", message: "璇烽�夋嫨鎵�灞炲尯鍩�" }],
deviceName: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
deviceModel: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
type: [{ required: true, trigger: "change", message: "璇烽�夋嫨鎴栬緭鍏ヨ澶囩被鍨�" }],
@@ -214,6 +236,7 @@
}
const { form, resetForm } = useFormData({
+ areaId: undefined, // 鍖哄煙ID
deviceName: undefined, // 璁惧鍚嶇О
deviceModel: undefined, // 瑙勬牸鍨嬪彿
deviceBrand: undefined, // 璁惧鍝佺墝
@@ -239,6 +262,7 @@
}
const { code, data } = await getLedgerById(id);
if (code == 200) {
+ form.areaId = data.areaId;
form.deviceName = data.deviceName;
form.deviceModel = data.deviceModel;
form.deviceBrand = data.deviceBrand;
@@ -261,6 +285,15 @@
form.planRuntimeTime = undefined;
}
}
+};
+
+const setAreaId = (areaId) => {
+ form.areaId = areaId;
+};
+
+const loadAreaOptions = async () => {
+ const res = await getDeviceAreaTree();
+ areaOptions.value = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : [];
};
const handleDeviceTypeChange = (value) => {
@@ -298,9 +331,14 @@
clearValidate();
};
+onMounted(() => {
+ loadAreaOptions();
+});
+
defineExpose({
form,
loadForm,
+ setAreaId,
resetForm,
clearValidate,
resetFormAndValidate,
diff --git a/src/views/equipmentManagement/ledger/Modal.vue b/src/views/equipmentManagement/ledger/Modal.vue
index 16166c6..3870862 100644
--- a/src/views/equipmentManagement/ledger/Modal.vue
+++ b/src/views/equipmentManagement/ledger/Modal.vue
@@ -62,8 +62,15 @@
formRef.value.loadForm(id);
};
+const openCreateModal = async (areaId) => {
+ openModal();
+ await nextTick();
+ formRef.value.setAreaId(areaId);
+};
+
defineExpose({
openModal,
loadForm,
+ openCreateModal,
});
</script>
diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue
index 7ba9401..8e3fae5 100644
--- a/src/views/equipmentManagement/ledger/index.vue
+++ b/src/views/equipmentManagement/ledger/index.vue
@@ -1,85 +1,191 @@
<template>
- <div class="app-container">
- <el-form :model="filters" :inline="true">
- <el-form-item label="璁惧鍚嶇О">
+ <div class="app-container ledger-view">
+ <div class="left-panel">
+ <div class="tree-toolbar">
<el-input
- v-model="filters.deviceName"
- style="width: 200px"
- placeholder="璇疯緭鍏ヨ澶囧悕绉�"
+ v-model="treeKeyword"
+ style="width: calc(100% - 102px)"
+ placeholder="璇疯緭鍏ュ尯鍩熷悕绉�"
clearable
- @change="getTableData"
+ prefix-icon="Search"
+ @input="filterTree"
+ @clear="filterTree"
/>
- </el-form-item>
- <el-form-item label="瑙勬牸鍨嬪彿">
- <el-input
+ <el-button type="primary" @click="openAreaDialog('addRoot')">鏂板鍖哄煙</el-button>
+ </div>
+ <div class="tree-actions">
+ <el-button link type="primary" @click="resetTreeSelection">鍏ㄩ儴鍖哄煙</el-button>
+ </div>
+ <el-tree
+ ref="treeRef"
+ v-loading="treeLoading"
+ :data="treeData"
+ :props="treeProps"
+ node-key="id"
+ highlight-current
+ default-expand-all
+ :expand-on-click-node="false"
+ :filter-node-method="filterTreeNode"
+ class="ledger-tree"
+ @node-click="handleTreeNodeClick"
+ >
+ <template #default="{ node, data }">
+ <div class="tree-node">
+ <span class="tree-node-content">
+ <el-icon class="tree-node-icon">
+ <component
+ :is="
+ data.children && data.children.length > 0
+ ? node.expanded
+ ? 'FolderOpened'
+ : 'Folder'
+ : 'Tickets'
+ "
+ />
+ </el-icon>
+ <span class="tree-node-label">{{ data.areaName }}</span>
+ </span>
+ <div class="tree-node-actions">
+ <el-button link type="primary" @click.stop="openAreaDialog('edit', data)">缂栬緫</el-button>
+ <el-button link type="primary" @click.stop="openAreaDialog('addChild', data)">鏂板</el-button>
+ <el-button
+ v-if="!hasChildren(data)"
+ link
+ type="danger"
+ @click.stop="handleDeleteArea(data)"
+ >
+ 鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+ </template>
+ </el-tree>
+ </div>
+
+ <div class="right-panel">
+ <el-form :model="filters" :inline="true">
+ <el-form-item label="璁惧鍚嶇О">
+ <el-input
+ v-model="filters.deviceName"
+ style="width: 200px"
+ placeholder="璇疯緭鍏ヨ澶囧悕绉�"
+ clearable
+ @change="getTableData"
+ />
+ </el-form-item>
+ <el-form-item label="瑙勬牸鍨嬪彿">
+ <el-input
v-model="filters.deviceModel"
style="width: 200px"
placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
clearable
@change="getTableData"
- />
- </el-form-item>
- <el-form-item label="渚涘簲鍟�">
- <el-input
+ />
+ </el-form-item>
+ <el-form-item label="渚涘簲鍟�">
+ <el-input
v-model="filters.supplierName"
style="width: 200px"
placeholder="璇疯緭鍏ヤ緵搴斿晢"
clearable
@change="getTableData"
- />
- </el-form-item>
- <el-form-item label="褰曞叆鏃ユ湡:">
- <el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
- placeholder="璇烽�夋嫨" clearable @change="changeDaterange" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="getTableData">鎼滅储</el-button>
- <el-button @click="resetFilters">閲嶇疆</el-button>
- </el-form-item>
- </el-form>
- <div class="table_list">
- <div class="actions">
- <div></div>
- <div>
- <el-button type="primary" @click="add" icon="Plus"> 鏂板 </el-button>
- <el-button type="info" @click="handleImport" icon="Upload">瀵煎叆</el-button>
- <el-button @click="handleOut" icon="download">瀵煎嚭</el-button>
- <el-button
- type="danger"
- icon="Delete"
- :disabled="multipleList.length <= 0"
- @click="deleteRow(multipleList.map((item) => item.id))"
- >
- 鎵归噺鍒犻櫎
- </el-button>
+ />
+ </el-form-item>
+ <el-form-item label="褰曞叆鏃ユ湡">
+ <el-date-picker
+ v-model="filters.entryDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="daterange"
+ placeholder="璇烽�夋嫨"
+ clearable
+ @change="changeDaterange"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button @click="handleResetFilters">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <div class="table_list">
+ <div class="actions">
+ <div class="actions-tip">
+ <span v-if="selectedAreaName">褰撳墠鍖哄煙锛歿{ selectedAreaName }}</span>
+ </div>
+ <div>
+ <el-button type="primary" icon="Plus" @click="add">鏂板</el-button>
+ <el-button type="info" icon="Upload" @click="handleImport">瀵煎叆</el-button>
+ <el-button icon="download" @click="handleOut">瀵煎嚭</el-button>
+ <el-button
+ type="danger"
+ icon="Delete"
+ :disabled="multipleList.length <= 0"
+ @click="deleteRow(multipleList.map((item) => item.id))"
+ >
+ 鎵归噺鍒犻櫎
+ </el-button>
+ </div>
</div>
+ <PIMTable
+ rowKey="id"
+ isSelection
+ :column="columns"
+ :tableData="dataList"
+ :page="{
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ total: pagination.total,
+ }"
+ @selection-change="handleSelectionChange"
+ @pagination="changePage"
+ />
</div>
- <PIMTable
- rowKey="id"
- isSelection
- :column="columns"
- :tableData="dataList"
- :page="{
- current: pagination.currentPage,
- size: pagination.pageSize,
- total: pagination.total,
- }"
- @selection-change="handleSelectionChange"
- @pagination="changePage"
- >
- </PIMTable>
</div>
- <Modal ref="modalRef" @success="getTableData"></Modal>
+
+ <Modal ref="modalRef" @success="getTableData" />
+
+ <el-dialog
+ v-model="areaDialogVisible"
+ :title="areaDialogTitle"
+ width="480px"
+ @close="closeAreaDialog"
+ >
+ <el-form ref="areaFormRef" :model="areaForm" :rules="areaRules" label-width="88px">
+ <el-form-item label="鍖哄煙鍚嶇О" prop="areaName">
+ <el-input v-model="areaForm.areaName" placeholder="璇疯緭鍏ュ尯鍩熷悕绉�" />
+ </el-form-item>
+ <el-form-item label="鎺掑簭" prop="sort">
+ <el-input-number v-model="areaForm.sort" :min="0" :step="1" style="width: 100%" />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input
+ v-model="areaForm.remark"
+ type="textarea"
+ :rows="4"
+ maxlength="200"
+ show-word-limit
+ placeholder="璇疯緭鍏ュ娉�"
+ />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button type="primary" @click="submitAreaForm">纭畾</el-button>
+ <el-button @click="closeAreaDialog">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+
<el-dialog v-model="qrDialogVisible" title="浜岀淮鐮�" width="300px" draggable>
- <div style="text-align:center;">
- <img :src="qrCodeUrl" alt="浜岀淮鐮�" style="width:200px;height:200px;" />
- <div style="margin:10px 0;">
+ <div class="qr-dialog">
+ <img :src="qrCodeUrl" alt="浜岀淮鐮�" class="qr-image" />
+ <div class="qr-footer">
<el-button type="primary" @click="downloadQRCode">涓嬭浇浜岀淮鐮佸浘鐗�</el-button>
</div>
</div>
</el-dialog>
-
- <!-- 瀵煎叆瀵硅瘽妗� -->
+
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
<el-upload
ref="uploadRef"
@@ -97,15 +203,22 @@
<div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
<template #tip>
<div class="el-upload__tip text-center">
- <span>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</span>
- <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline; margin-left: 5px;" @click="importTemplate">涓嬭浇妯℃澘</el-link>
+ <span>浠呭厑璁稿鍏� xls銆亁lsx 鏍煎紡鏂囦欢銆�</span>
+ <el-link
+ type="primary"
+ :underline="false"
+ style="font-size: 12px; vertical-align: baseline; margin-left: 5px"
+ @click="importTemplate"
+ >
+ 涓嬭浇妯℃澘
+ </el-link>
</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
- <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
- <el-button @click="upload.open = false">鍙� 娑�</el-button>
+ <el-button type="primary" @click="submitFileForm">纭畾</el-button>
+ <el-button @click="upload.open = false">鍙栨秷</el-button>
</div>
</template>
</el-dialog>
@@ -114,8 +227,14 @@
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
-// import { Search } from "@element-plus/icons-vue";
import { getLedgerPage, delLedger } from "@/api/equipmentManagement/ledger";
+import {
+ getDeviceAreaTree,
+ getDeviceAreaDetail,
+ addDeviceArea,
+ updateDeviceArea,
+ deleteDeviceArea,
+} from "@/api/equipmentManagement/deviceArea";
import { onMounted, getCurrentInstance, ref, reactive } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
@@ -128,28 +247,47 @@
name: "璁惧鍙拌处",
});
-// 琛ㄦ牸澶氶�夋閫変腑椤�
const multipleList = ref([]);
const { proxy } = getCurrentInstance();
const modalRef = ref();
+const treeRef = ref();
+const areaFormRef = ref();
+const treeKeyword = ref("");
+const treeLoading = ref(false);
+const treeData = ref([]);
+const selectedAreaName = ref("");
+const areaDialogVisible = ref(false);
+const areaDialogTitle = ref("鏂板鍖哄煙");
+const areaDialogMode = ref("addRoot");
const qrDialogVisible = ref(false);
const qrCodeUrl = ref("");
const qrRowData = ref(null);
+const uploadRef = ref(null);
-// 瀵煎叆鐩稿叧
-const uploadRef = ref(null)
+const treeProps = {
+ children: "children",
+ label: "areaName",
+};
+
const upload = reactive({
- // 鏄惁鏄剧ず寮瑰嚭灞�
open: false,
- // 寮瑰嚭灞傛爣棰�
title: "",
- // 鏄惁绂佺敤涓婁紶
isUploading: false,
- // 璁剧疆涓婁紶鐨勮姹傚ご閮�
headers: { Authorization: "Bearer " + getToken() },
- // 涓婁紶鐨勫湴鍧�
- url: import.meta.env.VITE_APP_BASE_API + "/device/ledger/import"
-})
+ url: import.meta.env.VITE_APP_BASE_API + "/device/ledger/import",
+});
+
+const areaForm = reactive({
+ id: undefined,
+ areaName: "",
+ parentId: undefined,
+ sort: 0,
+ remark: "",
+});
+
+const areaRules = {
+ areaName: [{ required: true, message: "璇疯緭鍏ュ尯鍩熷悕绉�", trigger: "blur" }],
+};
const {
filters,
@@ -157,7 +295,6 @@
dataList,
pagination,
getTableData,
- resetFilters,
onCurrentChange,
} = usePaginationApi(
getLedgerPage,
@@ -165,10 +302,17 @@
deviceName: undefined,
deviceModel: undefined,
supplierName: undefined,
+ entryDate: undefined,
entryDateStart: undefined,
entryDateEnd: undefined,
+ areaId: undefined,
+ areaName: undefined,
},
[
+ {
+ label: "鎵�鍦ㄥ尯鍩�",
+ prop: "areaName",
+ },
{
label: "璁惧鍚嶇О",
prop: "deviceName",
@@ -205,39 +349,151 @@
label: "褰曞叆鏃ユ湡",
prop: "createTime",
formatData: (v) => {
- if (!v) return '';
- // 濡傛灉鍖呭惈鏃跺垎绉掞紝鍙彇鏃ユ湡閮ㄥ垎
- if (v.includes(' ')) {
- return v.split(' ')[0];
- }
- return v;
+ if (!v) return "";
+ return v.includes(" ") ? v.split(" ")[0] : v;
},
},
- {
- dataType: "action",
- label: "鎿嶄綔",
- align: "center",
- fixed: 'right',
- width: 150,
- operation: [
- {
- name: "缂栬緫",
- clickFun: (row) => {
- edit(row.id)
- },
- },
- {
- name: "鐢熸垚浜岀淮鐮�",
- clickFun: (row) => {
- showQRCode(row)
- },
- },
- ],
- },
+ {
+ dataType: "action",
+ label: "鎿嶄綔",
+ align: "center",
+ fixed: "right",
+ width: 150,
+ operation: [
+ {
+ name: "缂栬緫",
+ clickFun: (row) => {
+ edit(row.id);
+ },
+ },
+ {
+ name: "鐢熸垚浜岀淮鐮�",
+ clickFun: (row) => {
+ showQRCode(row);
+ },
+ },
+ ],
+ },
]
);
-// 澶氶�夊悗鍋氫粈涔�
+const loadTreeData = async () => {
+ treeLoading.value = true;
+ try {
+ const res = await getDeviceAreaTree();
+ treeData.value = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : [];
+ } finally {
+ treeLoading.value = false;
+ }
+};
+
+const resetAreaForm = () => {
+ areaForm.id = undefined;
+ areaForm.areaName = "";
+ areaForm.parentId = undefined;
+ areaForm.sort = 0;
+ areaForm.remark = "";
+};
+
+const filterTree = () => {
+ treeRef.value?.filter(treeKeyword.value);
+};
+
+const filterTreeNode = (value, data) => {
+ if (!value) {
+ return true;
+ }
+ return String(data.areaName || "").includes(value);
+};
+
+const handleTreeNodeClick = (data) => {
+ filters.areaId = data.id;
+ filters.areaName = data.areaName;
+ selectedAreaName.value = data.areaName || "";
+ getTableData();
+};
+
+const openAreaDialog = async (mode, row) => {
+ areaDialogMode.value = mode;
+ areaDialogTitle.value =
+ mode === "edit" ? "缂栬緫鍖哄煙" : mode === "addChild" ? "鏂板瀛愬尯鍩�" : "鏂板鍖哄煙";
+ resetAreaForm();
+ areaDialogVisible.value = true;
+ if (mode === "addChild") {
+ areaForm.parentId = row.id;
+ areaForm.sort = 0;
+ return;
+ }
+ if (mode === "edit" && row?.id) {
+ const res = await getDeviceAreaDetail(row.id);
+ const detail = res?.data || {};
+ areaForm.id = detail.id;
+ areaForm.areaName = detail.areaName || "";
+ areaForm.parentId = detail.parentId;
+ areaForm.sort = detail.sort ?? 0;
+ areaForm.remark = detail.remark || "";
+ }
+};
+
+const closeAreaDialog = () => {
+ areaDialogVisible.value = false;
+ areaFormRef.value?.resetFields();
+ resetAreaForm();
+};
+
+const submitAreaForm = () => {
+ areaFormRef.value?.validate(async (valid) => {
+ if (!valid) {
+ return;
+ }
+ const submitData = {
+ id: areaForm.id,
+ areaName: areaForm.areaName,
+ parentId: areaForm.parentId,
+ sort: areaForm.sort,
+ remark: areaForm.remark,
+ };
+ const request = areaDialogMode.value === "edit" ? updateDeviceArea : addDeviceArea;
+ const { code } = await request(submitData);
+ if (code === 200) {
+ ElMessage.success(areaDialogMode.value === "edit" ? "淇敼鎴愬姛" : "鏂板鎴愬姛");
+ closeAreaDialog();
+ await loadTreeData();
+ }
+ });
+};
+
+const handleDeleteArea = (row) => {
+ if (hasChildren(row)) {
+ ElMessage.warning("褰撳墠鍖哄煙瀛樺湪涓嬬骇鍖哄煙锛屼笉鑳藉垹闄�");
+ return;
+ }
+ ElMessageBox.confirm("姝ゆ搷浣滃皢鍒犻櫎璇ヨ澶囧尯鍩燂紝鏄惁缁х画锛�", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(async () => {
+ const { code } = await deleteDeviceArea([row.id]);
+ if (code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ if (filters.areaId === row.id) {
+ resetTreeSelection();
+ }
+ await loadTreeData();
+ }
+ });
+};
+
+const hasChildren = (row) => Array.isArray(row?.children) && row.children.length > 0;
+
+const resetTreeSelection = () => {
+ treeRef.value?.setCurrentKey(null);
+ selectedAreaName.value = "";
+ filters.areaId = undefined;
+ filters.areaName = undefined;
+ getTableData();
+};
+
const handleSelectionChange = (selectionList) => {
multipleList.value = selectionList;
};
@@ -245,16 +501,19 @@
const add = () => {
modalRef.value.openModal();
};
+
const edit = (id) => {
modalRef.value.loadForm(id);
};
+
const changePage = ({ page, limit }) => {
pagination.currentPage = page;
- pagination.pageSize = limit;
+ pagination.pageSize = limit;
onCurrentChange(page);
};
+
const deleteRow = (id) => {
- ElMessageBox.confirm("姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ユ枃浠�, 鏄惁缁х画?", "鎻愮ず", {
+ ElMessageBox.confirm("姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ユ暟鎹紝鏄惁缁х画锛�", "鎻愮ず", {
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
type: "warning",
@@ -281,14 +540,24 @@
getTableData();
};
+const handleResetFilters = () => {
+ filters.deviceName = undefined;
+ filters.deviceModel = undefined;
+ filters.supplierName = undefined;
+ filters.entryDate = undefined;
+ filters.entryDateStart = undefined;
+ filters.entryDateEnd = undefined;
+ getTableData();
+};
+
const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+ ElMessageBox.confirm("褰撳墠鏌ヨ缁撴灉灏嗚瀵煎嚭锛屾槸鍚︾‘璁ゅ鍑猴紵", "瀵煎嚭", {
confirmButtonText: "纭",
cancelButtonText: "鍙栨秷",
type: "warning",
})
.then(() => {
- proxy.download(`/device/ledger/export`, {}, "璁惧鍙拌处妗f.xlsx");
+ proxy.download("/device/ledger/export", {}, "璁惧鍙拌处妗f.xlsx");
})
.catch(() => {
proxy.$modal.msg("宸插彇娑�");
@@ -296,8 +565,7 @@
};
const showQRCode = async (row) => {
- // 鐩存帴浣跨敤URL锛屼笉瑕佺敤JSON.stringify鍖呰
- const qrContent = proxy.javaApi + '/device-info?deviceId=' + row.id;
+ const qrContent = proxy.javaApi + "/device-info?deviceId=" + row.id;
qrCodeUrl.value = await QRCode.toDataURL(qrContent);
qrRowData.value = row;
qrDialogVisible.value = true;
@@ -310,48 +578,141 @@
a.click();
};
-// 瀵煎叆鎸夐挳鎿嶄綔
const handleImport = () => {
- upload.title = "璁惧鍙拌处瀵煎叆"
- upload.open = true
-}
+ upload.title = "璁惧鍙拌处瀵煎叆";
+ upload.open = true;
+};
-// 涓嬭浇妯℃澘鎿嶄綔
const importTemplate = () => {
- proxy.download("/device/ledger/downloadTemplate", {}, `璁惧鍙拌处瀵煎叆妯℃澘_${new Date().getTime()}.xlsx`)
-}
+ proxy.download("/device/ledger/downloadTemplate", {}, `璁惧鍙拌处瀵煎叆妯℃澘_${new Date().getTime()}.xlsx`);
+};
-// 鏂囦欢涓婁紶涓鐞�
-const handleFileUploadProgress = (event, file, fileList) => {
- upload.isUploading = true
-}
+const handleFileUploadProgress = () => {
+ upload.isUploading = true;
+};
-// 鏂囦欢涓婁紶鎴愬姛澶勭悊
-const handleFileSuccess = (response, file, fileList) => {
- upload.open = false
- upload.isUploading = false
- proxy.$refs["uploadRef"].handleRemove(file)
- proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "瀵煎叆缁撴灉", { dangerouslyUseHTMLString: true })
- getTableData()
-}
+const handleFileSuccess = (response, file) => {
+ upload.open = false;
+ upload.isUploading = false;
+ uploadRef.value?.handleRemove(file);
+ proxy.$alert(
+ "<div style='overflow:auto;overflow-x:hidden;max-height:70vh;padding:10px 20px 0;'>" +
+ response.msg +
+ "</div>",
+ "瀵煎叆缁撴灉",
+ { dangerouslyUseHTMLString: true }
+ );
+ getTableData();
+};
-// 鎻愪氦涓婁紶鏂囦欢
const submitFileForm = () => {
- proxy.$refs["uploadRef"].submit()
-}
+ uploadRef.value?.submit();
+};
-onMounted(() => {
+onMounted(async () => {
+ await loadTreeData();
getTableData();
});
</script>
<style lang="scss" scoped>
-.table_list {
- margin-top: unset;
+.ledger-view {
+ display: flex;
+ gap: 20px;
}
+
+.left-panel {
+ width: 320px;
+ min-width: 320px;
+ padding: 16px;
+ background: #fff;
+ border-radius: 4px;
+}
+
+.right-panel {
+ flex: 1;
+ min-width: 0;
+ padding: 16px;
+ background: #fff;
+ border-radius: 4px;
+}
+
+.tree-toolbar {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.tree-actions {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 8px;
+}
+
+.ledger-tree {
+ height: calc(100vh - 230px);
+ overflow-y: auto;
+}
+
+.tree-node {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+}
+
+.tree-node-content {
+ display: flex;
+ align-items: center;
+ min-width: 0;
+}
+
+.tree-node-icon {
+ color: #e6a23c;
+ margin-right: 8px;
+ font-size: 18px;
+}
+
+.tree-node-label {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.tree-node-actions {
+ flex-shrink: 0;
+}
+
+.table_list {
+ margin-top: 0;
+}
+
.actions {
display: flex;
justify-content: space-between;
+ align-items: center;
margin-bottom: 10px;
+ gap: 12px;
+}
+
+.actions-tip {
+ color: #606266;
+ font-size: 14px;
+}
+
+.qr-dialog {
+ text-align: center;
+}
+
+.qr-image {
+ width: 200px;
+ height: 200px;
+}
+
+.qr-footer {
+ margin: 10px 0;
}
</style>
diff --git a/src/views/equipmentManagement/repair/Modal/RepairModal.vue b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
index 1aa82ec..022ae6f 100644
--- a/src/views/equipmentManagement/repair/Modal/RepairModal.vue
+++ b/src/views/equipmentManagement/repair/Modal/RepairModal.vue
@@ -10,14 +10,41 @@
<el-form :model="form" label-width="100px">
<el-row>
<el-col :span="12">
+ <el-form-item label="鎵�灞炲尯鍩�">
+ <el-tree-select
+ v-model="form.areaId"
+ :data="areaOptions"
+ :props="areaTreeProps"
+ node-key="id"
+ value-key="id"
+ check-strictly
+ clearable
+ filterable
+ placeholder="璇烽�夋嫨鎵�灞炲尯鍩�"
+ style="width: 100%"
+ @change="handleAreaChange"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
<el-form-item label="璁惧鍚嶇О">
- <el-select v-model="form.deviceLedgerId" @change="setDeviceModel" filterable>
+ <el-select
+ v-model="form.deviceLedgerIds"
+ filterable
+ clearable
+ multiple
+ collapse-tags
+ collapse-tags-tooltip
+ placeholder="璇峰厛閫夋嫨鍖哄煙锛屽啀閫夋嫨璁惧"
+ style="width: 100%"
+ @change="setDeviceModels"
+ >
<el-option
- v-for="(item, index) in deviceOptions"
- :key="index"
+ v-for="item in deviceOptions"
+ :key="item.id"
:label="item.deviceName"
:value="item.id"
- ></el-option>
+ />
</el-select>
</el-form-item>
</el-col>
@@ -25,7 +52,7 @@
<el-form-item label="瑙勬牸鍨嬪彿">
<el-input
v-model="form.deviceModel"
- placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
+ placeholder="鑷姩甯﹀嚭瑙勬牸鍨嬪彿"
disabled
/>
</el-form-item>
@@ -53,9 +80,9 @@
<el-col :span="12">
<el-form-item label="鎶ヤ慨鐘舵��">
<el-select v-model="form.status">
- <el-option label="寰呯淮淇�" :value="0"></el-option>
- <el-option label="瀹岀粨" :value="1"></el-option>
- <el-option label="澶辫触" :value="2"></el-option>
+ <el-option label="寰呯淮淇�" :value="0" />
+ <el-option label="瀹岀粨" :value="1" />
+ <el-option label="澶辫触" :value="2" />
</el-select>
</el-form-item>
</el-col>
@@ -77,17 +104,21 @@
</template>
<script setup>
+import { nextTick, ref, unref } from "vue";
+import dayjs from "dayjs";
+import { ElMessage } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import useFormData from "@/hooks/useFormData";
+import useUserStore from "@/store/modules/user";
import {
addRepair,
editRepair,
getRepairById,
} from "@/api/equipmentManagement/repair";
-import { ElMessage } from "element-plus";
-import dayjs from "dayjs";
-import useFormData from "@/hooks/useFormData";
-import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
-import useUserStore from "@/store/modules/user";
+import {
+ getDeviceAreaTree,
+ getDeviceAreaTreeWithDevices,
+} from "@/api/equipmentManagement/deviceArea";
defineOptions({
name: "璁惧鎶ヤ慨寮圭獥",
@@ -98,32 +129,140 @@
const id = ref();
const visible = ref(false);
const loading = ref(false);
-
const userStore = useUserStore();
+const areaOptions = ref([]);
const deviceOptions = ref([]);
-
-const loadDeviceName = async () => {
- const { data } = await getDeviceLedger();
- deviceOptions.value = data;
+const areaTreeProps = {
+ label: "areaName",
+ children: "children",
};
const { form, resetForm } = useFormData({
- deviceLedgerId: undefined, // 璁惧Id
- deviceName: undefined, // 璁惧鍚嶇О
- deviceModel: undefined, // 瑙勬牸鍨嬪彿
- repairTime: dayjs().format("YYYY-MM-DD"), // 鎶ヤ慨鏃ユ湡锛岄粯璁ゅ綋澶�
- repairName: userStore.nickName, // 鎶ヤ慨浜�
- remark: undefined, // 鏁呴殰鐜拌薄
- status: 0, // 鎶ヤ慨鐘舵��
+ areaId: undefined,
+ deviceLedgerId: undefined,
+ deviceLedgerIds: [],
+ deviceLedgerIdsStr: undefined,
+ deviceName: undefined,
+ deviceModel: undefined,
+ repairTime: dayjs().format("YYYY-MM-DD"),
+ repairName: userStore.nickName,
+ remark: undefined,
+ status: 0,
});
-const setDeviceModel = (deviceId) => {
- const option = deviceOptions.value.find((item) => item.id === deviceId);
- form.deviceModel = option.deviceModel;
+const loadAreaTree = async () => {
+ const { data } = await getDeviceAreaTree();
+ areaOptions.value = Array.isArray(data) ? data : [];
+};
+
+const normalizeIdList = (value) => {
+ if (Array.isArray(value)) {
+ return value
+ .map((item) => Number(item))
+ .filter((item) => Number.isFinite(item));
+ }
+ if (typeof value === "string") {
+ return value
+ .split(",")
+ .map((item) => Number(item.trim()))
+ .filter((item) => Number.isFinite(item));
+ }
+ if (value !== undefined && value !== null && value !== "") {
+ const numericValue = Number(value);
+ return Number.isFinite(numericValue) ? [numericValue] : [];
+ }
+ return [];
+};
+
+const getNodeDevices = (node) => {
+ const candidates = [
+ node?.deviceList,
+ node?.devices,
+ node?.deviceLedgerList,
+ node?.deviceLedgers,
+ node?.ledgerList,
+ node?.ledgers,
+ ];
+ return candidates.find((item) => Array.isArray(item)) || [];
+};
+
+const normalizeDevice = (item) => ({
+ ...item,
+ id: item.id ?? item.deviceLedgerId,
+ deviceName: item.deviceName ?? item.name,
+ deviceModel: item.deviceModel ?? item.model,
+});
+
+const collectDevices = (node) => {
+ const currentDevices = getNodeDevices(node).map(normalizeDevice);
+ const childDevices = (node?.children || []).flatMap((child) =>
+ collectDevices(child)
+ );
+ const deviceMap = new Map();
+ [...currentDevices, ...childDevices].forEach((item) => {
+ if (item?.id !== undefined && item?.id !== null) {
+ deviceMap.set(Number(item.id), item);
+ }
+ });
+ return Array.from(deviceMap.values());
+};
+
+const findAreaNode = (nodes, areaId) => {
+ for (const node of nodes || []) {
+ if (Number(node.id) === Number(areaId)) {
+ return node;
+ }
+ const target = findAreaNode(node.children, areaId);
+ if (target) {
+ return target;
+ }
+ }
+ return null;
+};
+
+const loadDevicesByArea = async (areaId) => {
+ if (!areaId) {
+ deviceOptions.value = [];
+ return;
+ }
+ const { data } = await getDeviceAreaTreeWithDevices();
+ const treeData = Array.isArray(data) ? data : [];
+ const currentNode = findAreaNode(treeData, areaId);
+ deviceOptions.value = currentNode ? collectDevices(currentNode) : [];
+};
+
+const syncDeviceFields = (deviceIds) => {
+ const selectedIds = normalizeIdList(deviceIds);
+ const selectedDevices = selectedIds
+ .map((deviceId) =>
+ deviceOptions.value.find((item) => Number(item.id) === Number(deviceId))
+ )
+ .filter(Boolean);
+
+ form.deviceLedgerIds = selectedIds;
+ form.deviceLedgerId = selectedIds[0];
+ form.deviceLedgerIdsStr = selectedIds.join(",");
+ form.deviceName = selectedDevices
+ .map((item) => item.deviceName)
+ .filter(Boolean)
+ .join(",");
+ form.deviceModel = selectedDevices
+ .map((item) => item.deviceModel || "-")
+ .join(",");
+};
+
+const setDeviceModels = (deviceIds) => {
+ syncDeviceFields(deviceIds);
};
const setForm = (data) => {
- form.deviceLedgerId = data.deviceLedgerId;
+ form.areaId = data.areaId;
+ form.deviceLedgerIds = normalizeIdList(
+ data.deviceLedgerIds ?? data.deviceLedgerIdsStr ?? data.deviceLedgerId
+ );
+ form.deviceLedgerId = form.deviceLedgerIds[0];
+ form.deviceLedgerIdsStr =
+ data.deviceLedgerIdsStr ?? form.deviceLedgerIds.join(",");
form.deviceName = data.deviceName;
form.deviceModel = data.deviceModel;
form.repairTime = data.repairTime;
@@ -132,13 +271,30 @@
form.status = data.status;
};
+const handleAreaChange = async (areaId) => {
+ form.deviceLedgerId = undefined;
+ form.deviceLedgerIds = [];
+ form.deviceLedgerIdsStr = undefined;
+ form.deviceName = undefined;
+ form.deviceModel = undefined;
+ await loadDevicesByArea(areaId);
+};
+
const sendForm = async () => {
loading.value = true;
try {
+ syncDeviceFields(form.deviceLedgerIds);
+ const payload = {
+ ...form,
+ deviceLedgerId: form.deviceLedgerIds[0],
+ deviceLedgerIds: [...form.deviceLedgerIds],
+ deviceLedgerIdsStr: form.deviceLedgerIds.join(","),
+ deviceModel: form.deviceModel || "-",
+ };
const { code } = id.value
- ? await editRepair({ id: unref(id), ...form })
- : await addRepair(form);
- if (code == 200) {
+ ? await editRepair({ id: unref(id), ...payload })
+ : await addRepair(payload);
+ if (code === 200) {
ElMessage.success(`${id.value ? "缂栬緫" : "鏂板"}鎶ヤ慨鎴愬姛`);
visible.value = false;
emits("ok");
@@ -162,7 +318,8 @@
id.value = undefined;
visible.value = true;
await nextTick();
- await loadDeviceName();
+ await loadAreaTree();
+ deviceOptions.value = [];
};
const openEdit = async (editId) => {
@@ -170,8 +327,10 @@
id.value = editId;
visible.value = true;
await nextTick();
- await loadDeviceName();
+ await loadAreaTree();
setForm(data);
+ await loadDevicesByArea(form.areaId);
+ syncDeviceFields(form.deviceLedgerIds);
};
defineExpose({
diff --git a/src/views/equipmentManagement/repair/index.vue b/src/views/equipmentManagement/repair/index.vue
index 1e7af53..1699cff 100644
--- a/src/views/equipmentManagement/repair/index.vue
+++ b/src/views/equipmentManagement/repair/index.vue
@@ -7,7 +7,6 @@
style="width: 240px"
placeholder="璇疯緭鍏ヨ澶囧悕绉�"
clearable
- :prefix-icon="Search"
@change="getTableData"
/>
</el-form-item>
@@ -17,7 +16,6 @@
style="width: 240px"
placeholder="璇烽�夋嫨瑙勬牸鍨嬪彿"
clearable
- :prefix-icon="Search"
@change="getTableData"
/>
</el-form-item>
@@ -27,7 +25,6 @@
style="width: 240px"
placeholder="璇疯緭鍏ユ晠闅滅幇璞�"
clearable
- :prefix-icon="Search"
@change="getTableData"
/>
</el-form-item>
@@ -37,7 +34,6 @@
style="width: 240px"
placeholder="璇疯緭鍏ョ淮淇汉"
clearable
- :prefix-icon="Search"
@change="getTableData"
/>
</el-form-item>
@@ -177,6 +173,10 @@
maintenanceTimeStr: undefined,
},
[
+ {
+ label: "鎵�鍦ㄥ尯鍩�",
+ prop: "areaName",
+ },
{
label: "璁惧鍚嶇О",
align: "center",
diff --git a/src/views/equipmentManagement/upkeep/Form/PlanModal.vue b/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
index 19095b9..e44d6af 100644
--- a/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
+++ b/src/views/equipmentManagement/upkeep/Form/PlanModal.vue
@@ -8,27 +8,45 @@
@close="handleClose"
>
<el-form :model="form" label-width="100px">
+ <el-form-item label="鎵�灞炲尯鍩�">
+ <el-tree-select
+ v-model="form.areaId"
+ :data="areaOptions"
+ :props="areaTreeProps"
+ node-key="id"
+ value-key="id"
+ check-strictly
+ clearable
+ filterable
+ placeholder="璇烽�夋嫨鎵�灞炲尯鍩�"
+ style="width: 100%"
+ @change="handleAreaChange"
+ />
+ </el-form-item>
<el-form-item label="璁惧鍚嶇О">
<el-select
- v-model="form.deviceLedgerId"
- @change="setDeviceModel"
- placeholder="璇烽�夋嫨璁惧"
+ v-model="form.deviceLedgerIds"
filterable
- default-first-option
- :reserve-keyword="false"
+ clearable
+ multiple
+ collapse-tags
+ collapse-tags-tooltip
+ placeholder="璇烽�夋嫨璁惧"
+ style="width: 100%"
+ @change="setDeviceModels"
>
<el-option
- v-for="(item, index) in deviceOptions"
- :key="index"
+ v-for="item in deviceOptions"
+ :key="item.id"
:label="item.deviceName"
:value="item.id"
- ></el-option>
+ />
</el-select>
</el-form-item>
<el-form-item label="瑙勬牸鍨嬪彿">
<el-input
v-model="form.deviceModel"
- placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
+ placeholder="鑷姩甯﹀嚭瑙勬牸鍨嬪彿"
disabled
/>
</el-form-item>
@@ -51,19 +69,19 @@
</el-form-item>
<el-form-item v-if="id" label="淇濅慨鐘舵��">
<el-select v-model="form.status">
- <el-option label="寰呬繚淇�" :value="0"></el-option>
- <el-option label="瀹岀粨" :value="1"></el-option>
- <el-option label="澶辫触" :value="2"></el-option>
+ <el-option label="寰呬繚淇�" :value="0" />
+ <el-option label="瀹岀粨" :value="1" />
+ <el-option label="澶辫触" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="璁″垝淇濆吇鏃ユ湡">
<el-date-picker
- style="width: 100%"
v-model="form.maintenancePlanTime"
+ style="width: 100%"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
type="date"
- placeholder="璇烽�夋嫨璁″垝淇濆吇鏃ユ湡鏃ユ湡"
+ placeholder="璇烽�夋嫨璁″垝淇濆吇鏃ユ湡"
clearable
/>
</el-form-item>
@@ -72,18 +90,21 @@
</template>
<script setup>
+import { nextTick, onMounted, ref, unref } from "vue";
+import dayjs from "dayjs";
+import { ElMessage } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import useFormData from "@/hooks/useFormData";
+import { userListNoPage } from "@/api/system/user.js";
import {
addUpkeep,
editUpkeep,
getUpkeepById,
} from "@/api/equipmentManagement/upkeep";
-import { ElMessage } from "element-plus";
-import useFormData from "@/hooks/useFormData";
-import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
-import { onMounted } from "vue";
-import dayjs from "dayjs";
-import { userListNoPage } from "@/api/system/user.js";
+import {
+ getDeviceAreaTree,
+ getDeviceAreaTreeWithDevices,
+} from "@/api/equipmentManagement/deviceArea";
defineOptions({
name: "璁惧淇濆吇鏂板璁″垝",
@@ -94,47 +115,159 @@
const id = ref();
const visible = ref(false);
const loading = ref(false);
-
+const areaOptions = ref([]);
const deviceOptions = ref([]);
-const loadDeviceName = async () => {
- const { data } = await getDeviceLedger();
- deviceOptions.value = data;
+const userList = ref([]);
+const areaTreeProps = {
+ label: "areaName",
+ children: "children",
};
const { form, resetForm } = useFormData({
- deviceLedgerId: undefined, // 璁惧Id
- deviceName: undefined, // 璁惧鍚嶇О
- deviceModel: undefined, // 瑙勬牸鍨嬪彿
- maintenancePlanTime: undefined, // 璁″垝淇濆吇鏃ユ湡
- createUser: undefined, // 褰曞叆浜�
- status: 0, //淇濅慨鐘舵��
+ areaId: undefined,
+ deviceLedgerId: undefined,
+ deviceLedgerIds: [],
+ deviceLedgerIdsStr: undefined,
+ deviceName: undefined,
+ deviceModel: undefined,
+ maintenancePlanTime: undefined,
+ createUser: undefined,
+ status: 0,
});
-const setDeviceModel = (deviceId) => {
- const option = deviceOptions.value.find((item) => item.id === deviceId);
- form.deviceModel = option.deviceModel;
+const loadAreaTree = async () => {
+ const { data } = await getDeviceAreaTree();
+ areaOptions.value = Array.isArray(data) ? data : [];
};
-/**
- * @desc 璁剧疆琛ㄥ崟鍐呭
- * @param data 璁惧淇℃伅
- */
+const normalizeIdList = (value) => {
+ if (Array.isArray(value)) {
+ return value
+ .map((item) => Number(item))
+ .filter((item) => Number.isFinite(item));
+ }
+ if (typeof value === "string") {
+ return value
+ .split(",")
+ .map((item) => Number(item.trim()))
+ .filter((item) => Number.isFinite(item));
+ }
+ if (value !== undefined && value !== null && value !== "") {
+ const numericValue = Number(value);
+ return Number.isFinite(numericValue) ? [numericValue] : [];
+ }
+ return [];
+};
+
+const getNodeDevices = (node) => {
+ const candidates = [
+ node?.deviceList,
+ node?.devices,
+ node?.deviceLedgerList,
+ node?.deviceLedgers,
+ node?.ledgerList,
+ node?.ledgers,
+ ];
+ return candidates.find((item) => Array.isArray(item)) || [];
+};
+
+const normalizeDevice = (item) => ({
+ ...item,
+ id: item.id ?? item.deviceLedgerId,
+ deviceName: item.deviceName ?? item.name,
+ deviceModel: item.deviceModel ?? item.model,
+});
+
+const collectDevices = (node) => {
+ const currentDevices = getNodeDevices(node).map(normalizeDevice);
+ const childDevices = (node?.children || []).flatMap((child) =>
+ collectDevices(child)
+ );
+ const deviceMap = new Map();
+ [...currentDevices, ...childDevices].forEach((item) => {
+ if (item?.id !== undefined && item?.id !== null) {
+ deviceMap.set(Number(item.id), item);
+ }
+ });
+ return Array.from(deviceMap.values());
+};
+
+const findAreaNode = (nodes, areaId) => {
+ for (const node of nodes || []) {
+ if (Number(node.id) === Number(areaId)) {
+ return node;
+ }
+ const target = findAreaNode(node.children, areaId);
+ if (target) {
+ return target;
+ }
+ }
+ return null;
+};
+
+const loadDevicesByArea = async (areaId) => {
+ if (!areaId) {
+ deviceOptions.value = [];
+ return;
+ }
+ const { data } = await getDeviceAreaTreeWithDevices();
+ const treeData = Array.isArray(data) ? data : [];
+ const currentNode = findAreaNode(treeData, areaId);
+ deviceOptions.value = currentNode ? collectDevices(currentNode) : [];
+};
+
+const syncDeviceFields = (deviceIds) => {
+ const selectedIds = normalizeIdList(deviceIds);
+ const selectedDevices = selectedIds
+ .map((deviceId) =>
+ deviceOptions.value.find((item) => Number(item.id) === Number(deviceId))
+ )
+ .filter(Boolean);
+
+ form.deviceLedgerIds = selectedIds;
+ form.deviceLedgerId = selectedIds[0];
+ form.deviceLedgerIdsStr = selectedIds.join(",");
+ form.deviceName = selectedDevices
+ .map((item) => item.deviceName)
+ .filter(Boolean)
+ .join(",");
+ form.deviceModel = selectedDevices
+ .map((item) => item.deviceModel || "-")
+ .join(",");
+};
+
+const setDeviceModels = (deviceIds) => {
+ syncDeviceFields(deviceIds);
+};
+
+const handleAreaChange = async (areaId) => {
+ form.deviceLedgerId = undefined;
+ form.deviceLedgerIds = [];
+ form.deviceLedgerIdsStr = undefined;
+ form.deviceName = undefined;
+ form.deviceModel = undefined;
+ await loadDevicesByArea(areaId);
+};
+
const setForm = (data) => {
- form.deviceLedgerId = data.deviceLedgerId;
+ form.areaId = data.areaId;
+ form.deviceLedgerIds = normalizeIdList(
+ data.deviceLedgerIds ?? data.deviceLedgerIdsStr ?? data.deviceLedgerId
+ );
+ form.deviceLedgerId = form.deviceLedgerIds[0];
+ form.deviceLedgerIdsStr =
+ data.deviceLedgerIdsStr ?? form.deviceLedgerIds.join(",");
form.deviceName = data.deviceName;
form.deviceModel = data.deviceModel;
form.createUser = Number(data.createUser);
form.status = data.status;
- form.maintenancePlanTime = dayjs(data.maintenancePlanTime).format(
- "YYYY-MM-DD HH:mm:ss"
- );
+ form.maintenancePlanTime = data.maintenancePlanTime
+ ? dayjs(data.maintenancePlanTime).format("YYYY-MM-DD HH:mm:ss")
+ : undefined;
};
-// 鐢ㄦ埛鍒楄〃
-const userList = ref([]);
-
onMounted(() => {
- loadDeviceName();
+ loadAreaTree();
userListNoPage().then((res) => {
userList.value = res.data;
});
@@ -145,16 +278,27 @@
id.value = editId;
visible.value = true;
await nextTick();
+ await loadAreaTree();
setForm(data);
+ await loadDevicesByArea(form.areaId);
+ syncDeviceFields(form.deviceLedgerIds);
};
const sendForm = async () => {
loading.value = true;
try {
+ syncDeviceFields(form.deviceLedgerIds);
+ const payload = {
+ ...form,
+ deviceLedgerId: form.deviceLedgerIds[0],
+ deviceLedgerIds: [...form.deviceLedgerIds],
+ deviceLedgerIdsStr: form.deviceLedgerIds.join(","),
+ deviceModel: form.deviceModel || "-",
+ };
const { code } = id.value
- ? await editUpkeep({ id: unref(id), ...form })
- : await addUpkeep(form);
- if (code == 200) {
+ ? await editUpkeep({ id: unref(id), ...payload })
+ : await addUpkeep(payload);
+ if (code === 200) {
ElMessage.success(`${id.value ? "缂栬緫" : "鏂板"}璁″垝鎴愬姛`);
visible.value = false;
emits("ok");
@@ -174,9 +318,12 @@
visible.value = false;
};
-const openModal = () => {
+const openModal = async () => {
id.value = undefined;
visible.value = true;
+ await nextTick();
+ await loadAreaTree();
+ deviceOptions.value = [];
};
defineExpose({
diff --git a/src/views/equipmentManagement/upkeep/Form/formDia.vue b/src/views/equipmentManagement/upkeep/Form/formDia.vue
index 66bf067..57d7b11 100644
--- a/src/views/equipmentManagement/upkeep/Form/formDia.vue
+++ b/src/views/equipmentManagement/upkeep/Form/formDia.vue
@@ -1,304 +1,476 @@
<template>
- <FormDialog
- v-model="dialogVisitable"
- :title="operationType === 'add' ? '鏂板淇濆吇浠诲姟' : '缂栬緫淇濆吇浠诲姟'"
- width="800px"
- :operation-type="operationType"
- @confirm="submitForm"
- @cancel="cancel"
- @close="cancel"
- >
- <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
- <el-row>
- <el-col :span="12">
- <el-form-item label="璁惧鍚嶇О" prop="taskId">
- <el-select v-model="form.taskId" @change="setDeviceModel" filterable>
- <el-option
- v-for="(item, index) in deviceOptions"
- :key="index"
- :label="item.deviceName"
- :value="item.id"
- ></el-option>
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="瑙勬牸鍨嬪彿">
- <el-input
- v-model="form.deviceModel"
- placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
- disabled
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="12">
- <el-form-item label="褰曞叆浜�" prop="inspector">
- <el-select
- v-model="form.inspector"
- filterable
- default-first-option
- :reserve-keyword="false"
- placeholder="璇烽�夋嫨"
- clearable
- >
- <el-option
- v-for="item in userList"
- :label="item.nickName"
- :value="item.userId"
- :key="item.userId"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鐧昏鏃堕棿" prop="registrationDate">
- <el-date-picker
- v-model="form.registrationDate"
- type="date"
- placeholder="閫夋嫨鐧昏鏃ユ湡"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- style="width: 100%"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="12">
- <el-form-item label="浠诲姟棰戠巼" prop="frequencyType">
- <el-select v-model="form.frequencyType" placeholder="璇烽�夋嫨" clearable>
- <el-option label="姣忔棩" value="DAILY"/>
- <el-option label="姣忓懆" value="WEEKLY"/>
- <el-option label="姣忔湀" value="MONTHLY"/>
- <el-option label="瀛e害" value="QUARTERLY"/>
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType">
- <el-form-item label="鏃ユ湡" prop="frequencyDetail">
- <el-time-picker v-model="form.frequencyDetail" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
- value-format="HH:mm" />
- </el-form-item>
- </el-col>
- <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType">
- <el-form-item label="鏃ユ湡" prop="frequencyDetail">
- <el-select v-model="form.week" placeholder="璇烽�夋嫨" clearable style="width: 50%">
- <el-option label="鍛ㄤ竴" value="MON"/>
- <el-option label="鍛ㄤ簩" value="TUE"/>
- <el-option label="鍛ㄤ笁" value="WED"/>
- <el-option label="鍛ㄥ洓" value="THU"/>
- <el-option label="鍛ㄤ簲" value="FRI"/>
- <el-option label="鍛ㄥ叚" value="SAT"/>
- <el-option label="鍛ㄦ棩" value="SUN"/>
- </el-select>
- <el-time-picker v-model="form.time" placeholder="閫夋嫨鏃堕棿" format="HH:mm"
- value-format="HH:mm" style="width: 50%"/>
- </el-form-item>
- </el-col>
- <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType">
- <el-form-item label="鏃ユ湡" prop="frequencyDetail">
- <el-date-picker
- v-model="form.frequencyDetail"
- type="datetime"
- clearable
- placeholder="閫夋嫨寮�濮嬫棩鏈�"
- format="DD,HH:mm"
- value-format="DD,HH:mm"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY' && form.frequencyType">
- <el-form-item label="鏃ユ湡" prop="frequencyDetail">
- <el-date-picker
- v-model="form.frequencyDetail"
- type="datetime"
- clearable
- placeholder="閫夋嫨寮�濮嬫棩鏈�"
- format="MM,DD,HH:mm"
- value-format="MM,DD,HH:mm"
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="12">
- <el-form-item label="澶囨敞" prop="remarks">
- <el-input v-model="form.remarks" placeholder="璇疯緭鍏ュ娉�" type="textarea" />
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- </FormDialog>
+ <FormDialog
+ v-model="dialogVisitable"
+ :title="operationType === 'add' ? '鏂板淇濆吇浠诲姟' : '缂栬緫淇濆吇浠诲姟'"
+ width="800px"
+ :operation-type="operationType"
+ @confirm="submitForm"
+ @cancel="cancel"
+ @close="cancel"
+ >
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="鎵�灞炲尯鍩�" prop="areaId">
+ <el-tree-select
+ v-model="form.areaId"
+ :data="areaOptions"
+ :props="areaTreeProps"
+ node-key="id"
+ value-key="id"
+ check-strictly
+ clearable
+ filterable
+ placeholder="璇烽�夋嫨鎵�灞炲尯鍩�"
+ style="width: 100%"
+ @change="handleAreaChange"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="璁惧鍚嶇О" prop="deviceLedgerIds">
+ <el-select
+ v-model="form.deviceLedgerIds"
+ filterable
+ clearable
+ multiple
+ collapse-tags
+ collapse-tags-tooltip
+ placeholder="璇烽�夋嫨璁惧"
+ style="width: 100%"
+ @change="setDeviceModels"
+ >
+ <el-option
+ v-for="item in deviceOptions"
+ :key="item.id"
+ :label="item.deviceName"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿">
+ <el-input
+ v-model="form.deviceModel"
+ placeholder="鑷姩甯﹀嚭瑙勬牸鍨嬪彿"
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="褰曞叆浜�" prop="inspector">
+ <el-select
+ v-model="form.inspector"
+ filterable
+ default-first-option
+ :reserve-keyword="false"
+ placeholder="璇烽�夋嫨"
+ clearable
+ >
+ <el-option
+ v-for="item in userList"
+ :key="item.userId"
+ :label="item.nickName"
+ :value="item.userId"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐧昏鏃堕棿" prop="registrationDate">
+ <el-date-picker
+ v-model="form.registrationDate"
+ type="date"
+ placeholder="閫夋嫨鐧昏鏃ユ湡"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="浠诲姟棰戠巼" prop="frequencyType">
+ <el-select v-model="form.frequencyType" placeholder="璇烽�夋嫨" clearable>
+ <el-option label="姣忔棩" value="DAILY" />
+ <el-option label="姣忓懆" value="WEEKLY" />
+ <el-option label="姣忔湀" value="MONTHLY" />
+ <el-option label="瀛e害" value="QUARTERLY" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'DAILY'">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-time-picker
+ v-model="form.frequencyDetail"
+ placeholder="閫夋嫨鏃堕棿"
+ format="HH:mm"
+ value-format="HH:mm"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'WEEKLY'">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-select v-model="form.week" placeholder="璇烽�夋嫨" clearable style="width: 50%">
+ <el-option label="鍛ㄤ竴" value="MON" />
+ <el-option label="鍛ㄤ簩" value="TUE" />
+ <el-option label="鍛ㄤ笁" value="WED" />
+ <el-option label="鍛ㄥ洓" value="THU" />
+ <el-option label="鍛ㄤ簲" value="FRI" />
+ <el-option label="鍛ㄥ叚" value="SAT" />
+ <el-option label="鍛ㄦ棩" value="SUN" />
+ </el-select>
+ <el-time-picker
+ v-model="form.time"
+ placeholder="閫夋嫨鏃堕棿"
+ format="HH:mm"
+ value-format="HH:mm"
+ style="width: 50%"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'MONTHLY'">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-date-picker
+ v-model="form.frequencyDetail"
+ type="datetime"
+ clearable
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ format="DD,HH:mm"
+ value-format="DD,HH:mm"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12" v-if="form.frequencyType === 'QUARTERLY'">
+ <el-form-item label="鏃ユ湡" prop="frequencyDetail">
+ <el-date-picker
+ v-model="form.frequencyDetail"
+ type="datetime"
+ clearable
+ placeholder="閫夋嫨寮�濮嬫棩鏈�"
+ format="MM,DD,HH:mm"
+ value-format="MM,DD,HH:mm"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-col :span="12">
+ <el-form-item label="澶囨敞" prop="remarks">
+ <el-input v-model="form.remarks" placeholder="璇疯緭鍏ュ娉�" type="textarea" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </FormDialog>
</template>
<script setup>
+import { getCurrentInstance, reactive, ref, toRefs } from "vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
-import { reactive, ref, getCurrentInstance, toRefs } from "vue";
-import {userListNoPageByTenantId} from "@/api/system/user.js";
-import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
-import { deviceMaintenanceTaskAdd, deviceMaintenanceTaskEdit } from "@/api/equipmentManagement/upkeep";
+import { userListNoPageByTenantId } from "@/api/system/user.js";
+import {
+ getDeviceAreaTree,
+ getDeviceAreaTreeWithDevices,
+} from "@/api/equipmentManagement/deviceArea";
+import {
+ deviceMaintenanceTaskAdd,
+ deviceMaintenanceTaskEdit,
+} from "@/api/equipmentManagement/upkeep";
import { getCurrentDate } from "@/utils/index.js";
import useUserStore from "@/store/modules/user.js";
-const { proxy } = getCurrentInstance()
-const emit = defineEmits()
+const { proxy } = getCurrentInstance();
+const emit = defineEmits(["closeDia"]);
const dialogVisitable = ref(false);
-const operationType = ref('add');
+const operationType = ref("add");
+const areaOptions = ref([]);
const deviceOptions = ref([]);
+const userList = ref([]);
+const areaTreeProps = {
+ label: "areaName",
+ children: "children",
+};
const userStore = useUserStore();
-const data = reactive({
- form: {
- taskId: undefined,
- taskName: undefined,
- // 褰曞叆浜猴細鍗曢�変竴涓敤鎴� id
- inspector: undefined,
- remarks: '',
- frequencyType: '',
- frequencyDetail: '',
- week: '',
- time: '',
- deviceModel: undefined, // 瑙勬牸鍨嬪彿
- registrationDate: ''
- },
- rules: {
- taskId: [{ required: true, message: "璇烽�夋嫨璁惧", trigger: "change" },],
- inspector: [{ required: true, message: "璇烽�夋嫨褰曞叆浜�", trigger: "blur" },],
- registrationDate: [{ required: true, message: "璇烽�夋嫨鐧昏鏃堕棿", trigger: "change" }]
- }
-})
-const { form, rules } = toRefs(data)
-const userList = ref([])
-const loadDeviceName = async () => {
- const { data } = await getDeviceLedger();
- deviceOptions.value = data;
+const data = reactive({
+ form: {
+ areaId: undefined,
+ taskId: undefined,
+ taskIds: [],
+ taskIdsStr: undefined,
+ deviceLedgerIds: [],
+ deviceLedgerIdsStr: undefined,
+ taskName: undefined,
+ inspector: undefined,
+ remarks: "",
+ frequencyType: "",
+ frequencyDetail: "",
+ week: "",
+ time: "",
+ deviceModel: undefined,
+ registrationDate: "",
+ },
+ rules: {
+ areaId: [{ required: true, message: "璇烽�夋嫨鎵�灞炲尯鍩�", trigger: "change" }],
+ deviceLedgerIds: [{ required: true, message: "璇烽�夋嫨璁惧", trigger: "change" }],
+ inspector: [{ required: true, message: "璇烽�夋嫨褰曞叆浜�", trigger: "change" }],
+ registrationDate: [{ required: true, message: "璇烽�夋嫨鐧昏鏃堕棿", trigger: "change" }],
+ },
+});
+
+const { form, rules } = toRefs(data);
+
+const loadAreaTree = async () => {
+ const { data } = await getDeviceAreaTree();
+ areaOptions.value = Array.isArray(data) ? data : [];
};
-// 閫夋嫨璁惧鏃讹紝鍥炲~璁惧鍚嶇О(taskName)鍜岃鏍煎瀷鍙�(deviceModel)
-const setDeviceModel = (id) => {
- const option = deviceOptions.value.find((item) => item.id === id);
- if (option) {
- form.value.taskId = option.id;
- form.value.taskName = option.deviceName;
- form.value.deviceModel = option.deviceModel;
- }
-}
+const normalizeIdList = (value) => {
+ if (Array.isArray(value)) {
+ return value
+ .map((item) => Number(item))
+ .filter((item) => Number.isFinite(item));
+ }
+ if (typeof value === "string") {
+ return value
+ .split(",")
+ .map((item) => Number(item.trim()))
+ .filter((item) => Number.isFinite(item));
+ }
+ if (value !== undefined && value !== null && value !== "") {
+ const numericValue = Number(value);
+ return Number.isFinite(numericValue) ? [numericValue] : [];
+ }
+ return [];
+};
-// 鎵撳紑寮规
-const openDialog = async (type, row) => {
- dialogVisitable.value = true
- operationType.value = type
-
- // 閲嶇疆琛ㄥ崟
- resetForm();
-
- // 鍔犺浇鐢ㄦ埛鍒楄〃
- userListNoPageByTenantId().then((res) => {
- userList.value = res.data;
- });
-
- // 鍔犺浇璁惧鍒楄〃
- await loadDeviceName();
-
- if (type === 'edit' && row) {
- form.value = { ...row }
- // 缂栬緫鏃剁敤鎺ュ彛杩斿洖鐨� registrantId 鍥炴樉褰曞叆浜�
- if (row.registrantId) {
- form.value.inspector = row.registrantId
- }
+const getNodeDevices = (node) => {
+ const candidates = [
+ node?.deviceList,
+ node?.devices,
+ node?.deviceLedgerList,
+ node?.deviceLedgers,
+ node?.ledgerList,
+ node?.ledgers,
+ ];
+ return candidates.find((item) => Array.isArray(item)) || [];
+};
- // 濡傛灉鏈夎澶嘔D锛岃嚜鍔ㄨ缃澶囦俊鎭�
- if (form.value.taskId) {
- setDeviceModel(form.value.taskId);
- }
- } else if (type === 'add') {
- // 鏂板鏃惰缃櫥璁版棩鏈熶负褰撳ぉ
- form.value.registrationDate = getCurrentDate();
- // 鏂板鏃惰缃綍鍏ヤ汉涓哄綋鍓嶇櫥褰曡处鎴�
- form.value.inspector = userStore.id;
- }
-}
+const normalizeDevice = (item) => ({
+ ...item,
+ id: item.id ?? item.deviceLedgerId,
+ deviceName: item.deviceName ?? item.name,
+ deviceModel: item.deviceModel ?? item.model,
+});
-// 鍏抽棴瀵硅瘽妗�
-const cancel = () => {
- resetForm()
- dialogVisitable.value = false
- emit('closeDia')
-}
+const collectDevices = (node) => {
+ const currentDevices = getNodeDevices(node).map(normalizeDevice);
+ const childDevices = (node?.children || []).flatMap((child) =>
+ collectDevices(child)
+ );
+ const deviceMap = new Map();
+ [...currentDevices, ...childDevices].forEach((item) => {
+ if (item?.id !== undefined && item?.id !== null) {
+ deviceMap.set(Number(item.id), item);
+ }
+ });
+ return Array.from(deviceMap.values());
+};
-// 閲嶇疆琛ㄥ崟鍑芥暟
+const findAreaNode = (nodes, areaId) => {
+ for (const node of nodes || []) {
+ if (Number(node.id) === Number(areaId)) {
+ return node;
+ }
+ const target = findAreaNode(node.children, areaId);
+ if (target) {
+ return target;
+ }
+ }
+ return null;
+};
+
+const loadDevicesByArea = async (areaId) => {
+ if (!areaId) {
+ deviceOptions.value = [];
+ return;
+ }
+ const { data } = await getDeviceAreaTreeWithDevices();
+ const treeData = Array.isArray(data) ? data : [];
+ const currentNode = findAreaNode(treeData, areaId);
+ deviceOptions.value = currentNode ? collectDevices(currentNode) : [];
+};
+
+const syncDeviceFields = (deviceIds) => {
+ const selectedIds = normalizeIdList(deviceIds);
+ const selectedDevices = selectedIds
+ .map((deviceId) =>
+ deviceOptions.value.find((item) => Number(item.id) === Number(deviceId))
+ )
+ .filter(Boolean);
+
+ form.value.deviceLedgerIds = selectedIds;
+ form.value.deviceLedgerIdsStr = selectedIds.join(",");
+ form.value.taskIds = [...selectedIds];
+ form.value.taskIdsStr = selectedIds.join(",");
+ form.value.taskId = selectedIds[0];
+ form.value.taskName = selectedDevices
+ .map((item) => item.deviceName)
+ .filter(Boolean)
+ .join(",");
+ form.value.deviceModel = selectedDevices
+ .map((item) => item.deviceModel || "-")
+ .join(",");
+};
+
+const setDeviceModels = (deviceIds) => {
+ syncDeviceFields(deviceIds);
+};
+
+const handleAreaChange = async (areaId) => {
+ form.value.taskId = undefined;
+ form.value.taskIds = [];
+ form.value.taskIdsStr = undefined;
+ form.value.deviceLedgerIds = [];
+ form.value.deviceLedgerIdsStr = undefined;
+ form.value.taskName = undefined;
+ form.value.deviceModel = undefined;
+ await loadDevicesByArea(areaId);
+};
+
const resetForm = () => {
- if (proxy.$refs.formRef) {
- proxy.$refs.formRef.resetFields()
- }
- // 閲嶇疆琛ㄥ崟鏁版嵁纭繚璁惧淇℃伅姝g‘閲嶇疆
- form.value = {
- taskId: undefined,
- taskName: undefined,
- inspector: undefined,
- inspector: undefined,
- remarks: '',
- frequencyType: '',
- frequencyDetail: '',
- week: '',
- time: '',
- deviceModel: undefined,
- registrationDate: ''
- }
-}
+ if (proxy.$refs.formRef) {
+ proxy.$refs.formRef.resetFields();
+ }
+ form.value = {
+ areaId: undefined,
+ taskId: undefined,
+ taskIds: [],
+ taskIdsStr: undefined,
+ deviceLedgerIds: [],
+ deviceLedgerIdsStr: undefined,
+ taskName: undefined,
+ inspector: undefined,
+ remarks: "",
+ frequencyType: "",
+ frequencyDetail: "",
+ week: "",
+ time: "",
+ deviceModel: undefined,
+ registrationDate: "",
+ };
+};
-// 鎻愪氦琛ㄥ崟
+const openDialog = async (type, row) => {
+ dialogVisitable.value = true;
+ operationType.value = type;
+ resetForm();
+
+ userListNoPageByTenantId().then((res) => {
+ userList.value = res.data;
+ });
+
+ await loadAreaTree();
+
+ if (type === "edit" && row) {
+ form.value = {
+ ...form.value,
+ ...row,
+ inspector: row.registrantId || row.inspector,
+ };
+ form.value.deviceLedgerIds = normalizeIdList(
+ row.deviceLedgerIds ??
+ row.deviceLedgerIdsStr ??
+ row.taskIds ??
+ row.taskIdsStr ??
+ row.taskId
+ );
+ form.value.deviceLedgerIdsStr = form.value.deviceLedgerIds.join(",");
+ form.value.taskIds = [...form.value.deviceLedgerIds];
+ form.value.taskIdsStr = form.value.deviceLedgerIds.join(",");
+ form.value.taskId = form.value.deviceLedgerIds[0];
+
+ if (form.value.areaId) {
+ await loadDevicesByArea(form.value.areaId);
+ syncDeviceFields(form.value.deviceLedgerIds);
+ }
+ } else {
+ form.value.registrationDate = getCurrentDate();
+ form.value.inspector = userStore.id;
+ deviceOptions.value = [];
+ }
+};
+
+const cancel = () => {
+ resetForm();
+ dialogVisitable.value = false;
+ emit("closeDia");
+};
+
const submitForm = () => {
- proxy.$refs["formRef"].validate(async valid => {
- if (valid) {
- try {
- const payload = { ...form.value }
- // 涓嶅啀鍚戝悗绔紶淇濆吇浜哄瓧娈碉紝浠呬娇鐢ㄦ帴鍙h姹傜殑 registrant / registrantId
- // 鏍规嵁閫夋嫨鐨�"褰曞叆浜�"璁剧疆 registrant / registrantId
- if (payload.inspector) {
- const selectedUser = userList.value.find(
- (u) => String(u.userId) === String(payload.inspector)
- )
- if (selectedUser) {
- payload.registrantId = selectedUser.userId
- payload.registrant = selectedUser.nickName
- }
- }
- delete payload.inspector
- delete payload.inspectorIds
-
- if (payload.frequencyType === 'WEEKLY') {
- let frequencyDetail = ''
- frequencyDetail = payload.week + ',' + payload.time
- payload.frequencyDetail = frequencyDetail
- }
-
- // 褰曞叆鏃ユ湡锛氱洿鎺ヤ娇鐢ㄨ〃鍗曢噷鐨� registrationDate 瀛楁
- // 涓�浜涢粯璁ょ姸鎬佸瓧娈�
- if (payload.status === undefined || payload.status === null || payload.status === '') {
- payload.status = '0' // 榛樿鐘舵�侊紝鍙寜瀹為檯鏋氫妇璋冩暣
- }
- payload.active = true
- payload.deleted = 0
-
- if (operationType.value === 'edit') {
- await deviceMaintenanceTaskEdit(payload)
- } else {
- await deviceMaintenanceTaskAdd(payload)
- }
- cancel()
- proxy.$modal.msgSuccess('鎻愪氦鎴愬姛')
- } catch (error) {
- proxy.$modal.msgError('鎻愪氦澶辫触锛岃閲嶈瘯')
- }
- }
- })
-}
-defineExpose({ openDialog })
+ proxy.$refs.formRef.validate(async (valid) => {
+ if (!valid) {
+ return;
+ }
+ try {
+ syncDeviceFields(form.value.deviceLedgerIds);
+ const payload = { ...form.value };
+
+ if (payload.inspector) {
+ const selectedUser = userList.value.find(
+ (item) => String(item.userId) === String(payload.inspector)
+ );
+ if (selectedUser) {
+ payload.registrantId = selectedUser.userId;
+ payload.registrant = selectedUser.nickName;
+ }
+ }
+
+ delete payload.inspector;
+ delete payload.inspectorIds;
+
+ if (payload.frequencyType === "WEEKLY") {
+ payload.frequencyDetail = `${payload.week},${payload.time}`;
+ }
+
+ if (
+ payload.status === undefined ||
+ payload.status === null ||
+ payload.status === ""
+ ) {
+ payload.status = "0";
+ }
+
+ payload.deviceLedgerIds = [...form.value.deviceLedgerIds];
+ payload.deviceLedgerIdsStr = form.value.deviceLedgerIds.join(",");
+ payload.taskIds = [...form.value.deviceLedgerIds];
+ payload.taskIdsStr = form.value.deviceLedgerIds.join(",");
+ payload.taskId = form.value.deviceLedgerIds[0];
+ payload.taskName = form.value.taskName;
+ payload.deviceModel = form.value.deviceModel || "-";
+ payload.active = true;
+ payload.deleted = 0;
+
+ if (operationType.value === "edit") {
+ await deviceMaintenanceTaskEdit(payload);
+ } else {
+ await deviceMaintenanceTaskAdd(payload);
+ }
+
+ cancel();
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ } catch (error) {
+ proxy.$modal.msgError("鎻愪氦澶辫触锛岃閲嶈瘯");
+ }
+ });
+};
+
+defineExpose({ openDialog });
</script>
-<style scoped>
-
-</style>
+<style scoped></style>
diff --git a/src/views/equipmentManagement/upkeep/index.vue b/src/views/equipmentManagement/upkeep/index.vue
index 543e37b..e5d358a 100644
--- a/src/views/equipmentManagement/upkeep/index.vue
+++ b/src/views/equipmentManagement/upkeep/index.vue
@@ -299,6 +299,10 @@
// 瀹氭椂浠诲姟绠$悊琛ㄦ牸鍒楅厤缃�
const scheduledColumns = ref([
+ {
+ label: "鎵�鍦ㄥ尯鍩�",
+ prop: "areaName",
+ },
{ prop: "taskName", label: "璁惧鍚嶇О"},
{
label: "瑙勬牸鍨嬪彿",
@@ -352,6 +356,10 @@
// 浠诲姟璁板綍琛ㄦ牸鍒楅厤缃紙鍘熻澶囦繚鍏昏〃鏍煎垪锛�
const columns = ref([
{
+ label: "鎵�鍦ㄥ尯鍩�",
+ prop: "areaName",
+ },
+ {
label: "璁惧鍚嶇О",
align: "center",
prop: "deviceName",
diff --git a/src/views/inventoryManagement/environmentalMonitoring/index.vue b/src/views/inventoryManagement/environmentalMonitoring/index.vue
new file mode 100644
index 0000000..bda17b8
--- /dev/null
+++ b/src/views/inventoryManagement/environmentalMonitoring/index.vue
@@ -0,0 +1,277 @@
+<template>
+ <div class="environment-monitoring-page">
+ <section class="chart-panel">
+ <h3 class="panel-title">鐜瀹炴椂鏌辩姸鍥�</h3>
+ <Echarts
+ :series="barSeries"
+ :x-axis="xAxis"
+ :y-axis="yAxis"
+ :tooltip="tooltip"
+ :grid="grid"
+ :legend="legend"
+ :options="chartTheme"
+ :chart-style="chartStyle"
+ />
+ </section>
+
+ <section class="table-panel">
+ <h3 class="panel-title">璁惧鐜鏁版嵁鍒楄〃</h3>
+ <div class="sensor-table">
+ <div class="sensor-table__head">
+ <span>璁惧</span>
+ <span>娓╁害</span>
+ <span>婀垮害</span>
+ <span>浜屾哀鍖栫⒊</span>
+ <span>鍏夌収</span>
+ </div>
+ <div v-for="item in deviceRows" :key="item.name" class="sensor-table__row">
+ <span>{{ item.name }}</span>
+ <span>{{ item.temperature }}</span>
+ <span>{{ item.humidity }}</span>
+ <span>{{ item.co2 }}</span>
+ <span>{{ item.light }}</span>
+ </div>
+ <div v-if="!deviceRows.length" class="sensor-table__empty">
+ 鏆傛棤鐜鏁版嵁
+ </div>
+ </div>
+ </section>
+ </div>
+</template>
+
+<script setup>
+import { computed, onBeforeUnmount, onMounted, ref } from "vue";
+import Echarts from "@/components/Echarts/echarts.vue";
+import { getEnvironmentalRealData } from "@/api/inventoryManagement/environmentalMonitoring";
+
+const POLL_INTERVAL = 30000;
+
+const latestDevices = ref([]);
+let pollTimer = null;
+
+const metricConfig = [
+ { key: "temperature", label: "娓╁害", color: "#ff7a59" },
+ { key: "humidity", label: "婀垮害", color: "#1ea7fd" },
+ { key: "co2", label: "浜屾哀鍖栫⒊", color: "#12c48b" },
+ { key: "light", label: "鍏夌収", color: "#8b5cf6" },
+];
+
+const chartTheme = {
+ backgroundColor: "transparent",
+ textStyle: { color: "#6c7c96" },
+};
+
+const chartStyle = {
+ width: "100%",
+ height: "360px",
+};
+
+const grid = {
+ left: "4%",
+ right: "4%",
+ top: "16%",
+ bottom: "10%",
+ containLabel: true,
+};
+
+const tooltip = {
+ trigger: "axis",
+ axisPointer: { type: "shadow" },
+ backgroundColor: "rgba(12, 20, 34, 0.88)",
+ borderColor: "rgba(126, 164, 255, 0.18)",
+ textStyle: { color: "#e8edf7" },
+};
+
+const legend = {
+ top: 0,
+ right: 0,
+ textStyle: { color: "#6c7c96" },
+};
+
+const extractNumericValue = (rawValue) => {
+ const matched = String(rawValue ?? "").match(/-?\d+(\.\d+)?/);
+ return matched ? Number(matched[0]) : 0;
+};
+
+const normalizeMetricObject = (source, index) => {
+ const normalized = {
+ name: source.deviceName || source.name || source.deviceNo || `璁惧${index + 1}`,
+ temperature: 0,
+ humidity: 0,
+ co2: 0,
+ light: 0,
+ };
+
+ Object.entries(source || {}).forEach(([key, value]) => {
+ const rawText = String(value ?? "");
+
+ if (rawText.includes("鈩�")) {
+ normalized.temperature = extractNumericValue(rawText);
+ return;
+ }
+ if (rawText.includes("%RH")) {
+ normalized.humidity = extractNumericValue(rawText);
+ return;
+ }
+ if (rawText.includes("ppm")) {
+ normalized.co2 = extractNumericValue(rawText);
+ return;
+ }
+ if (rawText.includes("Lux")) {
+ normalized.light = extractNumericValue(rawText);
+ return;
+ }
+
+ if (key === "temperature") {
+ normalized.temperature = extractNumericValue(rawText);
+ } else if (key === "humidity") {
+ normalized.humidity = extractNumericValue(rawText);
+ } else if (key === "co2") {
+ normalized.co2 = extractNumericValue(rawText);
+ } else if (key === "light") {
+ normalized.light = extractNumericValue(rawText);
+ }
+ });
+
+ return normalized;
+};
+
+const xAxis = computed(() => [
+ {
+ type: "category",
+ data: latestDevices.value.map((item) => item.name),
+ axisLine: { lineStyle: { color: "rgba(79, 110, 148, 0.55)" } },
+ axisLabel: { color: "#6c7c96" },
+ axisTick: { show: false },
+ },
+]);
+
+const yAxis = [
+ {
+ type: "value",
+ axisLine: { show: false },
+ axisTick: { show: false },
+ splitLine: { lineStyle: { color: "rgba(110, 131, 160, 0.12)" } },
+ axisLabel: { color: "#6c7c96" },
+ },
+];
+
+const barSeries = computed(() =>
+ metricConfig.map((item) => ({
+ name: item.label,
+ type: "bar",
+ barMaxWidth: 28,
+ itemStyle: {
+ color: item.color,
+ borderRadius: [8, 8, 0, 0],
+ },
+ data: latestDevices.value.map((device) => Number(device[item.key] || 0)),
+ }))
+);
+
+const deviceRows = computed(() =>
+ latestDevices.value.map((item) => ({
+ name: item.name,
+ temperature: `${Number(item.temperature || 0).toFixed(2)}鈩僠,
+ humidity: `${Number(item.humidity || 0).toFixed(2)}%RH`,
+ co2: `${Number(item.co2 || 0).toFixed(2)}ppm`,
+ light: `${Number(item.light || 0).toFixed(2)}Lux`,
+ }))
+);
+
+const fetchRealData = async () => {
+ try {
+ const res = await getEnvironmentalRealData();
+ const dataList = Array.isArray(res?.data) ? res.data : [];
+ latestDevices.value = dataList.map((item, index) => normalizeMetricObject(item, index));
+ } catch (error) {
+ latestDevices.value = [];
+ }
+};
+
+onMounted(() => {
+ fetchRealData();
+ pollTimer = window.setInterval(fetchRealData, POLL_INTERVAL);
+});
+
+onBeforeUnmount(() => {
+ if (pollTimer) {
+ window.clearInterval(pollTimer);
+ pollTimer = null;
+ }
+});
+</script>
+
+<style scoped lang="scss">
+.environment-monitoring-page {
+ min-height: 100%;
+ padding: 20px;
+}
+
+.chart-panel,
+.table-panel {
+ padding: 20px;
+ border-radius: 16px;
+ background: #fff;
+}
+
+.table-panel {
+ margin-top: 20px;
+}
+
+.panel-title {
+ margin: 0 0 16px;
+ color: #1d344f;
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.sensor-table {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.sensor-table__head,
+.sensor-table__row {
+ display: grid;
+ grid-template-columns: 1.1fr 1fr 1fr 1fr 1fr;
+ gap: 12px;
+ align-items: center;
+}
+
+.sensor-table__head {
+ padding: 0 6px 10px;
+ color: #8393a8;
+ font-size: 13px;
+}
+
+.sensor-table__row {
+ padding: 14px 16px;
+ border-radius: 12px;
+ background: #f6f9fc;
+ color: #1d344f;
+}
+
+.sensor-table__empty {
+ padding: 32px 0;
+ color: #8393a8;
+ text-align: center;
+}
+
+@media (max-width: 768px) {
+ .environment-monitoring-page {
+ padding: 12px;
+ }
+
+ .chart-panel,
+ .table-panel {
+ padding: 16px;
+ }
+
+ .sensor-table__head,
+ .sensor-table__row {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+</style>
--
Gitblit v1.9.3