Преглед изворни кода

Merge branch 'equipManage'

huoyi пре 4 недеља
родитељ
комит
8abbd609fa

+ 54 - 0
src/api/equipManage/equipLedger.js

@@ -0,0 +1,54 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询设备台账列表
4
+export function listEquipLedger(query) {
5
+  return request({
6
+    url: '/equipment/ledger/list',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+// 获取设备台账详细信息
13
+export function getEquipLedger(ledgerId) {
14
+  return request({
15
+    url: '/equipment/ledger/' + ledgerId,
16
+    method: 'get'
17
+  })
18
+}
19
+
20
+// 新增设备台账
21
+export function addEquipLedger(data) {
22
+  return request({
23
+    url: '/equipment/ledger',
24
+    method: 'post',
25
+    data: data
26
+  })
27
+}
28
+
29
+// 修改设备台账
30
+export function updateEquipLedger(data) {
31
+  return request({
32
+    url: '/equipment/ledger',
33
+    method: 'put',
34
+    data: data
35
+  })
36
+}
37
+
38
+// 删除设备台账
39
+export function delEquipLedger(ledgerIds) {
40
+  return request({
41
+    url: '/equipment/ledger/' + ledgerIds,
42
+    method: 'delete'
43
+  })
44
+}
45
+
46
+// 导出设备台账列表
47
+export function exportEquipLedger(data) {
48
+  return request({
49
+    url: '/equipment/ledger/export',
50
+    method: 'post',
51
+    data: data,
52
+    responseType: 'blob'
53
+  })
54
+}

+ 7 - 0
src/api/system/position.js

@@ -42,3 +42,10 @@ export function delPosition(id) {
42 42
     method: 'delete'
43 43
   })
44 44
 }
45
+//查询位置列表树形结构
46
+export function treePosition() {
47
+  return request({
48
+    url: '/system/position/listTree',
49
+    method: 'get'
50
+  })
51
+}

+ 147 - 0
src/components/TreePositionSelect/index.vue

@@ -0,0 +1,147 @@
1
+<template>
2
+  <el-cascader
3
+    v-model="selectedValue"
4
+    :options="options"
5
+    :props="cascaderProps"
6
+    :placeholder="placeholder"
7
+    :disabled="disabled"
8
+    :style="{ width: width }"
9
+    filterable
10
+    clearable
11
+    collapse-tags
12
+    collapse-tags-tooltip
13
+    separator="/"
14
+    @change="handleChange"
15
+    @clear="handleClear"
16
+  />
17
+</template>
18
+
19
+<script lang="ts" setup>
20
+import { ref, watch, computed, onMounted, nextTick } from 'vue'
21
+import { treePosition } from '@/api/system/position'
22
+
23
+const props = defineProps<{
24
+  modelValue: any
25
+  disabled?: boolean
26
+  width?: string
27
+  placeholder?: string
28
+}>()
29
+
30
+const emits = defineEmits(['update:modelValue', 'change'])
31
+
32
+const options = ref<any[]>([])
33
+const loading = ref(false)
34
+const selectedValue = ref<any[]>([])
35
+const dataLoaded = ref(false)
36
+
37
+const placeholder = computed(() => props.placeholder || '请选择安装位置')
38
+
39
+const cascaderProps = {
40
+  value: 'code',
41
+  label: 'name',
42
+  children: 'children',
43
+  expandTrigger: 'hover' as const
44
+}
45
+
46
+const loadData = async () => {
47
+  if (dataLoaded.value) return
48
+  loading.value = true
49
+  try {
50
+    const response = await treePosition()
51
+    options.value = response.data || []
52
+    dataLoaded.value = true
53
+    
54
+    // 等待DOM更新后再设置选中值
55
+    await nextTick()
56
+    await nextTick()
57
+    
58
+    if (props.modelValue) {
59
+      updateSelectedValue(props.modelValue)
60
+    }
61
+  } catch (error) {
62
+    console.error('获取位置列表失败:', error)
63
+  } finally {
64
+    loading.value = false
65
+  }
66
+}
67
+
68
+const updateSelectedValue = (value: any) => {
69
+  if (value && typeof value === 'object') {
70
+    const obj = value as any
71
+    selectedValue.value = [obj.terminlCode, obj.regionalCode, obj.channelCode].filter(Boolean)
72
+  } else {
73
+    selectedValue.value = []
74
+  }
75
+}
76
+
77
+const findNodeByCode = (nodes: any[], code: string): any | null => {
78
+  for (const node of nodes) {
79
+    if (node.code === code) {
80
+      return node
81
+    }
82
+    if (node.children && node.children.length > 0) {
83
+      const found = findNodeByCode(node.children, code)
84
+      if (found) return found
85
+    }
86
+  }
87
+  return null
88
+}
89
+
90
+const handleChange = (value: any) => {
91
+  console.log(value,"value")
92
+  if (!value || value.length === 0) {
93
+    selectedValue.value = []
94
+    emits('update:modelValue', null)
95
+    emits('change', null)
96
+    return
97
+  }
98
+  
99
+  const terminal = findNodeByCode(options.value, value[0])
100
+  const region = value[1] ? findNodeByCode(options.value, value[1]) || (terminal?.children?.find((c: any) => c.code === value[1])) : null
101
+  const channel = value[2] ? (region?.children?.find((c: any) => c.code === value[2])) : null
102
+  
103
+  const result = {
104
+    terminlCode: value[0] || '',
105
+    terminlName: terminal?.name || '',
106
+    regionalCode: value[1] || '',
107
+    regionalName: region?.name || '',
108
+    channelCode: value[2] || '',
109
+    channelName: channel?.name || ''
110
+  }
111
+  
112
+  emits('update:modelValue', result)
113
+  emits('change', result)
114
+}
115
+
116
+const handleClear = () => {
117
+  selectedValue.value = []
118
+  emits('update:modelValue', null)
119
+  emits('change', null)
120
+}
121
+
122
+watch(() => props.modelValue, async (newValue) => {
123
+  if (newValue) {
124
+    if (!dataLoaded.value) {
125
+      await loadData()
126
+    } else {
127
+      await nextTick()
128
+      updateSelectedValue(newValue)
129
+    }
130
+  }
131
+}, { deep: true, immediate: true })
132
+
133
+watch(() => options.value, (newOptions) => {
134
+  if (newOptions.length > 0 && props.modelValue) {
135
+    nextTick(() => {
136
+      updateSelectedValue(props.modelValue)
137
+    })
138
+  }
139
+}, { immediate: false })
140
+
141
+onMounted(() => {
142
+  loadData()
143
+})
144
+</script>
145
+
146
+<style scoped>
147
+</style>

+ 36 - 36
src/components/UserSelect/index.vue

@@ -9,19 +9,17 @@
9 9
 </template>
10 10
 
11 11
 <script lang="ts" setup>
12
-import { onMounted, ref, watch, defineProps, defineEmits, onUnmounted } from 'vue'
12
+import { nextTick, onMounted, ref, watch, defineProps, defineEmits, onUnmounted } from 'vue'
13 13
 import { debounce } from 'lodash-es'
14 14
 import { listUser, getUser } from "@/api/system/user"
15
-import { unref } from 'vue'
16 15
 
17 16
 interface ListItem {
18
-  value: string
17
+  value: string | number
19 18
   label: string
20 19
 }
21 20
 
22
-// 定义组件属性
23 21
 const props = defineProps<{
24
-  modelValue: string | string[]
22
+  modelValue: string | string[] | number | number[]
25 23
   multiple?: boolean
26 24
   disabled?: boolean
27 25
   width?: string
@@ -29,19 +27,16 @@ const props = defineProps<{
29 27
   placeholder?: string
30 28
 }>()
31 29
 
32
-// 定义组件事件
33 30
 const emits = defineEmits(['update:modelValue'])
34 31
 
35 32
 const options = ref<ListItem[]>([])
36 33
 const loading = ref(false)
37
-const selectedValue = ref<string | string[]>(props.modelValue)
34
+const selectedValue = ref<string | string[] | number | number[]>(props.modelValue)
38 35
 
39
-// 根据用户ID列表获取用户信息
40
-const fetchUsersByIds = async (userIds: string[]) => {
36
+const fetchUsersByIds = async (userIds: (string | number)[]) => {
41 37
   if (!userIds || userIds.length === 0) return []
42 38
   try {
43
-    // 使用Promise.all并行获取每个用户的信息
44
-    const userPromises = userIds.map(userId => getUser(userId))
39
+    const userPromises = userIds.map(userId => getUser(String(userId)))
45 40
     const userResponses = await Promise.all(userPromises)
46 41
 
47 42
     return userResponses.map((response: any) => ({
@@ -54,17 +49,14 @@ const fetchUsersByIds = async (userIds: string[]) => {
54 49
   }
55 50
 }
56 51
 
57
-// 远程搜索方法
58 52
 const remoteMethod = async (query: string) => {
59 53
   if (query) {
60 54
     loading.value = true
61 55
     try {
62
-      // 调用远程接口获取数据
63 56
       const response = await listUser({ nickName: query })
64
-      // 假设接口返回的数据结构为 { data: [...] },根据实际情况调整
65 57
       options.value = response.rows.map((item) => ({
66
-        value: item.userId, // 根据实际接口返回字段调整
67
-        label: item.nickName // 根据实际接口返回字段调整
58
+        value: item.userId,
59
+        label: item.nickName
68 60
       }))
69 61
     } catch (error) {
70 62
       console.error('远程搜索出错:', error)
@@ -76,39 +68,47 @@ const remoteMethod = async (query: string) => {
76 68
   }
77 69
 }
78 70
 
79
-// 防抖处理远程搜索
80 71
 const debouncedRemoteMethod = debounce(remoteMethod, 300)
81
-
82
-// 标记是否正在初始化回显,避免循环更新
83 72
 const isInitializing = ref(false)
84 73
 
85
-// 监听modelValue变化,有值时执行回显逻辑
74
+const hasValue = (value: any): boolean => {
75
+  if (value === null || value === undefined) return false
76
+  if (typeof value === 'string') return value.length > 0
77
+  if (Array.isArray(value)) return value.length > 0
78
+  if (typeof value === 'number') return true
79
+  return false
80
+}
81
+
82
+const initSelection = async (value: any) => {
83
+  if (!hasValue(value)) return
84
+  
85
+  isInitializing.value = true
86
+  try {
87
+    const userIds = Array.isArray(value) ? value : [value]
88
+    const userOptions = await fetchUsersByIds(userIds as (string | number)[])
89
+    options.value = userOptions
90
+    
91
+    await nextTick()
92
+    selectedValue.value = value
93
+  } catch (error) {
94
+    console.error('初始化选中失败:', error)
95
+  } finally {
96
+    isInitializing.value = false
97
+  }
98
+}
99
+
86 100
 watch(() => props.modelValue, async (newValue, oldValue) => {
87
-  // 只有当值从无到有或值发生变化时才执行回显
88
-  if (newValue && newValue.length > 0 && newValue !== oldValue) {
89
-    isInitializing.value = true
90
-    try {
91
-      const userIds = Array.isArray(newValue) ? newValue : [newValue]
92
-      const userOptions = await fetchUsersByIds(userIds)
93
-      options.value = userOptions
94
-      
95
-      // 设置选中值,但不触发更新事件
96
-      selectedValue.value = newValue
97
-    } finally {
98
-      isInitializing.value = false
99
-    }
101
+  if (hasValue(newValue) && newValue !== oldValue) {
102
+    await initSelection(newValue)
100 103
   }
101 104
 }, { immediate: true })
102 105
 
103
-// 监听选中值变化并触发更新事件
104 106
 watch(selectedValue, (newValue) => {
105
-  // 只有在非初始化状态下才触发更新事件
106 107
   if (!isInitializing.value) {
107 108
     emits('update:modelValue', newValue)
108 109
   }
109 110
 })
110 111
 
111
-// 组件销毁时清空数据
112 112
 onUnmounted(() => {
113 113
   options.value = []
114 114
   selectedValue.value = props.multiple ? [] : ''

+ 725 - 0
src/views/equipManage/equipLedger/index.vue

@@ -0,0 +1,725 @@
1
+<template>
2
+  <div class="app-container">
3
+    <!-- 查询条件 -->
4
+    <div class="filter-container">
5
+      <el-form :model="queryParams" ref="queryFormRef" :inline="true" class="search-form">
6
+        <el-form-item label="设备名称" prop="equipmentName">
7
+          <el-input v-model="queryParams.equipmentName" placeholder="请输入设备名称" clearable style="width: 200px" />
8
+        </el-form-item>
9
+
10
+        <el-form-item label="设备序列号" prop="equipmentSerialNumber">
11
+          <el-input v-model="queryParams.equipmentSerialNumber" placeholder="请输入设备序列号" clearable style="width: 200px" />
12
+        </el-form-item>
13
+        <el-form-item label="使用状态" prop="usageStatus">
14
+          <el-select v-model="queryParams.usageStatus" placeholder="请选择使用状态" clearable style="width: 200px">
15
+            <el-option v-for="dict in equipment_usage_status" :key="dict.value" :label="dict.label"
16
+              :value="dict.value" />
17
+          </el-select>
18
+        </el-form-item>
19
+        <el-form-item label="最近定/自检日期" prop="inspectionSelfCheckDate">
20
+          <el-date-picker v-model="queryParams.inspectionSelfCheckDate" type="date" value-format="YYYY-MM-DD"
21
+            placeholder="请选择日期" style="width: 200px" />
22
+        </el-form-item>
23
+        <el-form-item label="检查成员" prop="inspectionTeamUserName">
24
+          <el-input v-model="queryParams.inspectionTeamUserName" placeholder="请输入检查成员" clearable style="width: 200px" />
25
+        </el-form-item>
26
+        <el-form-item>
27
+          <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
28
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
29
+        </el-form-item>
30
+      </el-form>
31
+    </div>
32
+
33
+    <!-- 操作按钮 -->
34
+    <div class="operation-container">
35
+      <el-button type="primary" plain icon="Plus" @click="handleAdd"
36
+        v-hasPermi="['equipment:ledger:add']">新增</el-button>
37
+      <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleBatchDelete"
38
+        v-hasPermi="['equipment:ledger:delete']">删除</el-button>
39
+      <el-button type="warning" plain icon="Download" @click="handleExport"
40
+        v-hasPermi="['equipment:ledger:export']">导出</el-button>
41
+      <el-button type="info" plain icon="Upload" @click="handleImport"
42
+        v-hasPermi="['equipment:ledger:import']">导入</el-button>
43
+    </div>
44
+
45
+    <!-- 数据表格 -->
46
+    <el-table v-loading="loading" :data="tableData" border @selection-change="handleSelectionChange"
47
+      :row-class-name="tableRowClassName">
48
+      <el-table-column type="selection" width="55" align="center" />
49
+      <el-table-column label="序号" prop="rowIndex" width="70" align="center">
50
+        <template #default="scope">
51
+          {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
52
+        </template>
53
+      </el-table-column>
54
+      <el-table-column label="设备编号" prop="equipmentCode" align="center" min-width="120">
55
+        <template #default="scope">
56
+          <el-link type="primary" @click="handleDetail(scope.row)">{{ scope.row.equipmentCode }}</el-link>
57
+        </template>
58
+      </el-table-column>
59
+      <el-table-column label="设备名称" prop="equipmentName" align="center" min-width="150" show-overflow-tooltip />
60
+      <el-table-column label="设备种类" prop="equipmentType" align="center" min-width="120" show-overflow-tooltip />
61
+      <el-table-column label="设备序列号" prop="equipmentSerialNumber" align="center" min-width="150"
62
+        show-overflow-tooltip />
63
+      <el-table-column label="安装位置" prop="installationLocation" align="center" min-width="180" show-overflow-tooltip>
64
+        <template #default="scope">
65
+          <span>{{ scope.row.terminlName }}{{ scope.row.terminlName ? '/' : '' }}{{ scope.row.regionalName
66
+          }}{{ scope.row.regionalName ? '/' : '' }}{{ scope.row.channelName }}</span>
67
+        </template>
68
+      </el-table-column>
69
+      <el-table-column label="使用状态" prop="usageStatus" align="center" min-width="100">
70
+        <template #default="scope">
71
+          <dict-tag :options="equipment_usage_status" :value="scope.row.usageStatus" />
72
+
73
+        </template>
74
+      </el-table-column>
75
+      <el-table-column label="定/自检小组组长" prop="inspectionTeamLeaderName" align="center" min-width="120"
76
+        show-overflow-tooltip />
77
+      <el-table-column label="定/自检小组组员1" prop="inspectionTeamMember1Name" align="center" min-width="120"
78
+        show-overflow-tooltip />
79
+      <el-table-column label="定/自检小组组员2" prop="inspectionTeamMember2Name" align="center" min-width="120"
80
+        show-overflow-tooltip />
81
+      <el-table-column label="最近定检/自检到期日期" prop="nextInspectionDueDate" align="center" min-width="120" />
82
+      <el-table-column label="操作" align="center" width="220" fixed="right" class-name="operation-column">
83
+        <template #default="scope">
84
+          <el-button link type="primary" icon="View" @click="handleDetail(scope.row)">详情</el-button>
85
+          <el-button link type="primary" icon="Edit" @click="handleEdit(scope.row)">编辑</el-button>
86
+          <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
87
+        </template>
88
+      </el-table-column>
89
+    </el-table>
90
+
91
+    <!-- 分页 -->
92
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
93
+      v-model:limit="queryParams.pageSize" @pagination="getList" />
94
+
95
+    <!-- 编辑/详情弹窗 -->
96
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="1000px" :close-on-click-modal="false"
97
+      destroy-on-close>
98
+      <el-form :model="form" ref="formRef" :rules="rules" label-width="160px">
99
+        <el-row :gutter="20">
100
+          <el-col :span="12">
101
+            <el-form-item label="设备名称" prop="equipmentName">
102
+              <el-input v-model="form.equipmentName" placeholder="请输入设备名称" :disabled="formDisabled" />
103
+            </el-form-item>
104
+          </el-col>
105
+          <el-col :span="12">
106
+            <el-form-item label="设备种类" prop="equipmentType">
107
+              <el-input v-model="form.equipmentType" placeholder="请输入设备种类" :disabled="formDisabled" />
108
+            </el-form-item>
109
+          </el-col>
110
+          <el-col :span="12">
111
+            <el-form-item label="设备类别" prop="equipmentCategory">
112
+              <el-input v-model="form.equipmentCategory" placeholder="请输入设备类别" :disabled="formDisabled" />
113
+            </el-form-item>
114
+          </el-col>
115
+          <el-col :span="12">
116
+            <el-form-item label="设备品牌" prop="equipmentBrand">
117
+              <el-input v-model="form.equipmentBrand" placeholder="请输入设备品牌" :disabled="formDisabled" />
118
+            </el-form-item>
119
+          </el-col>
120
+          <el-col :span="12">
121
+            <el-form-item label="设备型号" prop="equipmentModel">
122
+              <el-input v-model="form.equipmentModel" placeholder="请输入设备型号" :disabled="formDisabled" />
123
+            </el-form-item>
124
+          </el-col>
125
+          <el-col :span="12">
126
+            <el-form-item label="设备序列号" prop="equipmentSerialNumber">
127
+              <el-input v-model="form.equipmentSerialNumber" placeholder="请输入设备序列号" :disabled="formDisabled" />
128
+            </el-form-item>
129
+          </el-col>
130
+          <el-col :span="12">
131
+            <el-form-item label="生产厂家" prop="manufacturer">
132
+              <el-input v-model="form.manufacturer" placeholder="请输入生产厂家" :disabled="formDisabled" />
133
+            </el-form-item>
134
+          </el-col>
135
+          <el-col :span="12">
136
+            <el-form-item label="出厂日期" prop="manufacturingDate">
137
+              <el-date-picker v-model="form.manufacturingDate" type="date" value-format="YYYY-MM-DD"
138
+                placeholder="请选择出厂日期" :disabled="formDisabled" />
139
+            </el-form-item>
140
+          </el-col>
141
+          <el-col :span="12">
142
+            <el-form-item label="验收日期" prop="acceptanceDate">
143
+              <el-date-picker v-model="form.acceptanceDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择验收日期"
144
+                :disabled="formDisabled" />
145
+            </el-form-item>
146
+          </el-col>
147
+          <el-col :span="12">
148
+            <el-form-item label="启用日期" prop="commissioningDate">
149
+              <el-date-picker v-model="form.commissioningDate" type="date" value-format="YYYY-MM-DD"
150
+                placeholder="请选择启用日期" :disabled="formDisabled" />
151
+            </el-form-item>
152
+          </el-col>
153
+          <el-col :span="12">
154
+            <el-form-item label="使用状态" prop="usageStatus">
155
+              <el-select v-model="form.usageStatus" placeholder="请选择使用状态" :disabled="formDisabled">
156
+                <el-option v-for="dict in equipment_usage_status" :key="dict.value" :label="dict.label"
157
+                  :value="dict.value" />
158
+              </el-select>
159
+            </el-form-item>
160
+          </el-col>
161
+          <el-col :span="12">
162
+            <el-form-item label="报废日期" prop="scrappingDate">
163
+              <el-date-picker v-model="form.scrappingDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择报废日期"
164
+                :disabled="formDisabled" />
165
+            </el-form-item>
166
+          </el-col>
167
+          <el-col :span="12">
168
+            <el-form-item label="安装位置" prop="installationLocation">
169
+              <TreePositionSelect v-model="form.installationLocation" :disabled="formDisabled" width="100%"
170
+                placeholder="请选择安装位置" />
171
+            </el-form-item>
172
+          </el-col>
173
+          <el-col :span="12">
174
+            <el-form-item label="定/自检日期" prop="inspectionSelfCheckDate">
175
+              <el-date-picker v-model="form.inspectionSelfCheckDate" type="date" value-format="YYYY-MM-DD"
176
+                placeholder="请选择定/自检日期" :disabled="formDisabled" @change="calculateDueDate" />
177
+            </el-form-item>
178
+          </el-col>
179
+          <el-col :span="12">
180
+            <el-form-item label="定/自检周期" prop="inspectionSelfCheckCycle">
181
+              <el-input-number v-model="form.inspectionSelfCheckCycle" placeholder="请输入定/自检周期" :min="0" :precision="0"
182
+                :disabled="formDisabled" @change="calculateDueDate" />&nbsp;月
183
+            </el-form-item>
184
+          </el-col>
185
+          <el-col :span="12">
186
+            <el-form-item label="最近定/自检到期日期" prop="nextInspectionDueDate">
187
+              <el-date-picker v-model="form.nextInspectionDueDate" type="date" value-format="YYYY-MM-DD"
188
+                placeholder="请选择最近定/自检到期日期" :disabled="formDisabled" />
189
+            </el-form-item>
190
+          </el-col>
191
+          <el-col :span="12">
192
+            <el-form-item label="定/自检小组组长" prop="inspectionTeamLeaderId">
193
+              <UserSelect v-model="form.inspectionTeamLeaderId" :disabled="formDisabled" width="100%"
194
+                placeholder="请选择组长" />
195
+            </el-form-item>
196
+          </el-col>
197
+          <el-col :span="12">
198
+            <el-form-item label="定/自检小组组员1" prop="inspectionTeamMember1Id">
199
+              <UserSelect v-model="form.inspectionTeamMember1Id" :disabled="formDisabled" width="100%"
200
+                placeholder="请选择组员1" />
201
+            </el-form-item>
202
+          </el-col>
203
+          <el-col :span="12">
204
+            <el-form-item label="定/自检小组组员2" prop="inspectionTeamMember2Id">
205
+              <UserSelect v-model="form.inspectionTeamMember2Id" :disabled="formDisabled" width="100%"
206
+                placeholder="请选择组员2" />
207
+            </el-form-item>
208
+          </el-col>
209
+          <el-col :span="24">
210
+            <el-form-item label="首次验收情况" prop="initialAcceptanceStatus">
211
+              <el-input v-model="form.initialAcceptanceStatus" type="textarea" :rows="3" placeholder="请输入首次验收情况"
212
+                :disabled="formDisabled" />
213
+            </el-form-item>
214
+          </el-col>
215
+          <el-col :span="24">
216
+            <el-form-item label="设备图片" prop="baseAttachmentList">
217
+              <ImageUpload v-model="form.baseAttachmentList" :disabled="formDisabled" :limit="3" result-type="object" />
218
+            </el-form-item>
219
+          </el-col>
220
+        </el-row>
221
+
222
+        <!-- 定检记录表格 -->
223
+        <el-divider content-position="left">定检记录</el-divider>
224
+        <el-button type="primary" plain icon="Plus" @click="addCheckRecord" v-if="!formDisabled">新增定检记录</el-button>
225
+        <el-table :data="form.equipmentInspectionRecordList" border style="margin-top: 10px">
226
+          <el-table-column label="定检日期" prop="inspectionDate">
227
+            <template #default="scope">
228
+              <el-date-picker v-model="scope.row.inspectionDate" type="date" value-format="YYYY-MM-DD"
229
+                placeholder="选择日期" :disabled="formDisabled"
230
+                @change="handleInspectionDateChange(scope.row, scope.$index)" />
231
+            </template>
232
+          </el-table-column>
233
+          <el-table-column label="定检小组" prop="inspectionTeamId">
234
+            <template #default="scope">
235
+              <UserSelect v-model="scope.row.inspectionTeamId" :multiple="true" :disabled="formDisabled" width="100%"
236
+                placeholder="请选择" />
237
+            </template>
238
+          </el-table-column>
239
+          <el-table-column label="定检结论" prop="inspectionResult">
240
+            <template #default="scope">
241
+              <el-input v-model="scope.row.inspectionResult" placeholder="定检结论" :disabled="formDisabled" />
242
+            </template>
243
+          </el-table-column>
244
+          <el-table-column label="定检性质" prop="inspectionNature">
245
+            <template #default="scope">
246
+              <el-select v-model="scope.row.inspectionNature" placeholder="请选择" :disabled="formDisabled">
247
+                <el-option v-for="dict in equipment_inspection_nature" :key="dict.value" :label="dict.label"
248
+                  :value="dict.value" />
249
+              </el-select>
250
+            </template>
251
+          </el-table-column>
252
+          <el-table-column label="操作" width="80" v-if="!formDisabled">
253
+            <template #default="scope">
254
+              <el-button type="danger" link icon="Delete" @click="removeCheckRecord(scope.$index)"
255
+                v-hasPermi="['equipment:ledger:delete']">删除</el-button>
256
+            </template>
257
+          </el-table-column>
258
+        </el-table>
259
+      </el-form>
260
+
261
+      <template #footer>
262
+        <el-button @click="dialog.visible = false">取消</el-button>
263
+        <el-button type="primary" @click="submitForm" v-if="!formDisabled">确定</el-button>
264
+      </template>
265
+    </el-dialog>
266
+
267
+    <!-- 导入对话框 -->
268
+    <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
269
+      <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers"
270
+        :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading"
271
+        :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
272
+        <el-icon class="el-icon--upload">
273
+          <UploadFilled />
274
+        </el-icon>
275
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
276
+        <template #tip>
277
+          <div class="el-upload__tip text-center">
278
+            <span>仅允许导入xls、xlsx格式文件。</span>
279
+            <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline"
280
+              @click="importTemplate">下载模板</el-link>
281
+          </div>
282
+        </template>
283
+      </el-upload>
284
+      <template #footer>
285
+        <div class="dialog-footer">
286
+          <el-button type="primary" @click="submitFileForm">确 定</el-button>
287
+          <el-button @click="upload.open = false">取 消</el-button>
288
+        </div>
289
+      </template>
290
+    </el-dialog>
291
+  </div>
292
+</template>
293
+
294
+<script setup>
295
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
296
+import { ElMessage } from 'element-plus'
297
+import { Delete, UploadFilled } from '@element-plus/icons-vue'
298
+import ImageUpload from '@/components/ImageUpload/index.vue'
299
+import UserSelect from '@/components/UserSelect/index.vue'
300
+import TreePositionSelect from '@/components/TreePositionSelect/index.vue'
301
+import { listEquipLedger, getEquipLedger, addEquipLedger, updateEquipLedger, delEquipLedger, exportEquipLedger } from '@/api/equipManage/equipLedger'
302
+import { getUser } from '@/api/system/user'
303
+import { getToken } from '@/utils/auth'
304
+
305
+const { proxy } = getCurrentInstance()
306
+
307
+const { equipment_usage_status, equipment_inspection_nature } = proxy.useDict('equipment_usage_status', 'equipment_inspection_nature')
308
+
309
+const loading = ref(false)
310
+
311
+const queryFormRef = ref()
312
+const formRef = ref()
313
+const tableData = ref([])
314
+const total = ref(0)
315
+const single = ref(true)
316
+const multiple = ref(true)
317
+const queryParams = reactive({
318
+  pageNum: 1,
319
+  pageSize: 10,
320
+  equipmentName: '',
321
+
322
+  equipmentSerialNumber: '',
323
+  usageStatus: '',
324
+  inspectionSelfCheckDate: '',
325
+  inspectionTeamUserName: ''
326
+})
327
+const dialog = reactive({
328
+  visible: false,
329
+  title: ''
330
+})
331
+const formDisabled = ref(false)
332
+const form = reactive({
333
+  id: null,
334
+  equipmentName: '',
335
+  equipmentType: '',
336
+  equipmentCategory: '',
337
+  equipmentBrand: '',
338
+  equipmentModel: '',
339
+  equipmentSerialNumber: '',
340
+  manufacturer: '',
341
+  manufacturingDate: '',
342
+  acceptanceDate: '',
343
+  commissioningDate: '',
344
+  usageStatus: '',
345
+  scrappingDate: '',
346
+  installationLocation: null,
347
+  inspectionSelfCheckDate: '',
348
+  inspectionSelfCheckCycle: 0,
349
+  nextInspectionDueDate: '',
350
+  inspectionTeamLeaderId: '',
351
+  inspectionTeamMember1Id: '',
352
+  inspectionTeamMember2Id: '',
353
+  initialAcceptanceStatus: '',
354
+  baseAttachmentList: [],
355
+  equipmentInspectionRecordList: []
356
+})
357
+const baseAttachmentListInternal = ref([])
358
+
359
+watch(() => form.baseAttachmentList, (newVal) => {
360
+  if (newVal && Array.isArray(newVal)) {
361
+    baseAttachmentListInternal.value = newVal.map(item => ({
362
+      attachmentName: item.name || item.originalFilename || '',
363
+      attachmentUrl: item.url || ''
364
+    }))
365
+  }
366
+}, { deep: true, immediate: true })
367
+const rules = {
368
+  equipmentName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }],
369
+  equipmentSerialNumber: [{ required: true, message: '设备序列号不能为空', trigger: 'blur' }]
370
+}
371
+const selectedIds = ref([])
372
+
373
+const upload = reactive({
374
+  open: false,
375
+  title: '',
376
+  isUploading: false,
377
+  updateSupport: 1,
378
+  headers: { Authorization: 'Bearer ' + getToken() },
379
+  url: import.meta.env.VITE_APP_BASE_API + '/equipment/ledger/importData'
380
+})
381
+
382
+function getList() {
383
+  loading.value = true
384
+  listEquipLedger(queryParams).then(response => {
385
+    tableData.value = response.rows
386
+    total.value = response.total
387
+    loading.value = false
388
+  })
389
+}
390
+
391
+function handleQuery() {
392
+  queryParams.pageNum = 1
393
+  getList()
394
+}
395
+
396
+function resetQuery() {
397
+  queryFormRef.value?.resetFields()
398
+  handleQuery()
399
+}
400
+
401
+function handleSelectionChange(selection) {
402
+  selectedIds.value = selection.map(item => item.id)
403
+  single.value = selection.length !== 1
404
+  multiple.value = !selection.length
405
+}
406
+
407
+function handleAdd() {
408
+  resetForm()
409
+  formDisabled.value = false
410
+  dialog.title = '新增设备'
411
+  dialog.visible = true
412
+}
413
+
414
+function handleEdit(row) {
415
+  getEquipLedger(row.id).then(response => {
416
+    Object.assign(form, response.data)
417
+    if (form.baseAttachmentList && Array.isArray(form.baseAttachmentList)) {
418
+      form.baseAttachmentList = form.baseAttachmentList.map(item => ({
419
+        name: item.attachmentName || item.name || '',
420
+        url: item.attachmentUrl || item.url || ''
421
+      }))
422
+    } else {
423
+      form.baseAttachmentList = []
424
+    }
425
+    if (!form.equipmentInspectionRecordList || !Array.isArray(form.equipmentInspectionRecordList)) {
426
+      form.equipmentInspectionRecordList = []
427
+    }
428
+    // form.installationLocation = [response.data.terminlCode, response.data.regionalCode, response.data.channelCode]
429
+ 
430
+    form.installationLocation = {
431
+      terminlCode: response.data.terminlCode,
432
+      regionalCode: response.data.regionalCode,
433
+      channelCode: response.data.channelCode
434
+    }
435
+    form.inspectionTeamLeaderId = form.inspectionTeamLeaderId || ''
436
+    form.inspectionTeamMember1Id = form.inspectionTeamMember1Id || ''
437
+    form.inspectionTeamMember2Id = form.inspectionTeamMember2Id || ''
438
+    form.equipmentInspectionRecordList.forEach(item => {
439
+      item.inspectionTeamId = item.inspectionTeamId ? item.inspectionTeamId.split(',').map(id => Number(id)).filter(id => !isNaN(id)) : []
440
+    })
441
+    formDisabled.value = false
442
+    dialog.title = '编辑设备'
443
+    dialog.visible = true
444
+  })
445
+}
446
+
447
+function handleDetail(row) {
448
+  getEquipLedger(row.id).then(response => {
449
+    Object.assign(form, response.data)
450
+    if (form.baseAttachmentList && Array.isArray(form.baseAttachmentList)) {
451
+      form.baseAttachmentList = form.baseAttachmentList.map(item => ({
452
+        name: item.attachmentName || item.name || '',
453
+        url: item.attachmentUrl || item.url || ''
454
+      }))
455
+    } else {
456
+      form.baseAttachmentList = []
457
+    }
458
+    if (!form.equipmentInspectionRecordList || !Array.isArray(form.equipmentInspectionRecordList)) {
459
+      form.equipmentInspectionRecordList = []
460
+    }
461
+    // form.installationLocation = [response.data.terminlCode, response.data.regionalCode, response.data.channelCode]
462
+ 
463
+    form.installationLocation = {
464
+      terminlCode: response.data.terminlCode,
465
+      regionalCode: response.data.regionalCode,
466
+      channelCode: response.data.channelCode
467
+    }
468
+    form.inspectionTeamLeaderId = form.inspectionTeamLeaderId || ''
469
+    form.inspectionTeamMember1Id = form.inspectionTeamMember1Id || ''
470
+    form.inspectionTeamMember2Id = form.inspectionTeamMember2Id || ''
471
+    form.equipmentInspectionRecordList.forEach(item => {
472
+      item.inspectionTeamId = item.inspectionTeamId ? item.inspectionTeamId.split(',').map(id => Number(id)).filter(id => !isNaN(id)) : []
473
+    })
474
+    formDisabled.value = true
475
+    dialog.title = '设备详情'
476
+    dialog.visible = true
477
+  })
478
+}
479
+
480
+function handleDelete(row) {
481
+  proxy.$modal.confirm('是否确认删除该设备?').then(function () {
482
+    return delEquipLedger(row.id)
483
+  }).then(() => {
484
+    getList()
485
+    proxy.$modal.msgSuccess('删除成功')
486
+  }).catch(() => { })
487
+}
488
+
489
+
490
+
491
+function handleBatchDelete() {
492
+  proxy.$modal.confirm('是否确认删除选中的设备?').then(function () {
493
+    return delEquipLedger(selectedIds.value.join(','))
494
+  }).then(() => {
495
+    getList()
496
+    proxy.$modal.msgSuccess('删除成功')
497
+  }).catch(() => { })
498
+}
499
+
500
+function tableRowClassName({ row }) {
501
+  if (row.colorType === 'RED') {
502
+    return 'row-red'
503
+  } else if (row.colorType === 'ORANGE') {
504
+    return 'row-orange'
505
+  } else if (row.colorType === 'YELLOW') {
506
+    return 'row-yellow'
507
+  }
508
+  return ''
509
+}
510
+
511
+function handleExport() {
512
+  proxy.download('equipment/ledger/export', {
513
+    ...queryParams
514
+  }, `equipLedger_${new Date().getTime()}.xlsx`)
515
+}
516
+
517
+function handleImport() {
518
+  upload.title = '设备台账导入'
519
+  upload.open = true
520
+}
521
+
522
+function importTemplate() {
523
+  proxy.download('equipment/ledger/importTemplate', {}, `equipLedger_template_${new Date().getTime()}.xlsx`)
524
+}
525
+
526
+function resetForm() {
527
+  Object.assign(form, {
528
+    id: null,
529
+    equipmentName: '',
530
+    equipmentType: '',
531
+    equipmentCategory: '',
532
+    equipmentBrand: '',
533
+    equipmentModel: '',
534
+    equipmentSerialNumber: '',
535
+    manufacturer: '',
536
+    manufacturingDate: '',
537
+    acceptanceDate: '',
538
+    commissioningDate: '',
539
+    usageStatus: '',
540
+    scrappingDate: '',
541
+    installationLocation: null,
542
+    inspectionSelfCheckDate: '',
543
+    inspectionSelfCheckCycle: 0,
544
+    nextInspectionDueDate: '',
545
+    inspectionTeamLeaderId: '',
546
+    inspectionTeamMember1Id: '',
547
+    inspectionTeamMember2Id: '',
548
+    initialAcceptanceStatus: '',
549
+    baseAttachmentList: [],
550
+    equipmentInspectionRecordList: []
551
+  })
552
+}
553
+
554
+async function submitForm() {
555
+  formRef.value?.validate(async valid => {
556
+    if (valid) {
557
+      const submitData = { ...form, ...form.installationLocation }
558
+      submitData.baseAttachmentList = baseAttachmentListInternal.value
559
+      // 收集所有定检小组用户ID,批量获取名称
560
+      const allTeamIds = new Set()
561
+      form.equipmentInspectionRecordList.forEach(item => {
562
+        if (item.inspectionTeamId && Array.isArray(item.inspectionTeamId)) {
563
+          item.inspectionTeamId.forEach(id => allTeamIds.add(id))
564
+        }
565
+      })
566
+      const userMap = new Map()
567
+      if (allTeamIds.size > 0) {
568
+        const userPromises = Array.from(allTeamIds).map(async id => {
569
+          try {
570
+            const res = await getUser(String(id))
571
+            return { id, name: res.data?.nickName || '' }
572
+          } catch {
573
+            return { id, name: '' }
574
+          }
575
+        })
576
+        const results = await Promise.all(userPromises)
577
+        results.forEach(({ id, name }) => userMap.set(id, name))
578
+      }
579
+      submitData.equipmentInspectionRecordList = form.equipmentInspectionRecordList.map(item => {
580
+        const teamIds = item.inspectionTeamId && Array.isArray(item.inspectionTeamId) ? item.inspectionTeamId.join(',') : ''
581
+        const teamNames = item.inspectionTeamId && Array.isArray(item.inspectionTeamId) ? item.inspectionTeamId.map(id => userMap.get(id) || '').filter(Boolean).join(',') : ''
582
+        return {
583
+          ...item,
584
+          inspectionTeamId: teamIds,
585
+          inspectionTeamName: teamNames
586
+        }
587
+      })
588
+      try {
589
+        if (submitData.inspectionTeamLeaderId) {
590
+          const leaderRes = await getUser(submitData.inspectionTeamLeaderId)
591
+          submitData.inspectionTeamLeaderName = leaderRes.data?.nickName || ''
592
+        }
593
+        if (submitData.inspectionTeamMember1Id) {
594
+          const member1Res = await getUser(submitData.inspectionTeamMember1Id)
595
+          submitData.inspectionTeamMember1Name = member1Res.data?.nickName || ''
596
+        }
597
+        if (submitData.inspectionTeamMember2Id) {
598
+          const member2Res = await getUser(submitData.inspectionTeamMember2Id)
599
+          submitData.inspectionTeamMember2Name = member2Res.data?.nickName || ''
600
+        }
601
+      } catch (error) {
602
+        console.error('获取用户信息失败:', error)
603
+      }
604
+      delete submitData.installationLocation
605
+
606
+      if (submitData.id != null) {
607
+        updateEquipLedger(submitData).then(response => {
608
+          proxy.$modal.msgSuccess('修改成功')
609
+          dialog.visible = false
610
+          getList()
611
+        })
612
+      } else {
613
+        addEquipLedger(submitData).then(response => {
614
+          proxy.$modal.msgSuccess('新增成功')
615
+          dialog.visible = false
616
+          getList()
617
+        })
618
+      }
619
+    }
620
+  })
621
+}
622
+
623
+function addCheckRecord() {
624
+  const teamIds = []
625
+  if (form.inspectionTeamLeaderId) {
626
+    teamIds.push(form.inspectionTeamLeaderId)
627
+  }
628
+  if (form.inspectionTeamMember1Id) {
629
+    teamIds.push(form.inspectionTeamMember1Id)
630
+  }
631
+  if (form.inspectionTeamMember2Id) {
632
+    teamIds.push(form.inspectionTeamMember2Id)
633
+  }
634
+  form.equipmentInspectionRecordList.push({
635
+    inspectionDate: '',
636
+    inspectionTeamId: teamIds,
637
+    inspectionTeamName: '',
638
+    inspectionResult: '',
639
+    inspectionNature: ''
640
+  })
641
+}
642
+
643
+function removeCheckRecord(index) {
644
+  form.equipmentInspectionRecordList.splice(index, 1)
645
+}
646
+
647
+function handleInspectionDateChange(row, index) {
648
+  if (row.inspectionDate) {
649
+    const recordDate = new Date(row.inspectionDate)
650
+    const checkDate = form.inspectionSelfCheckDate ? new Date(form.inspectionSelfCheckDate) : null
651
+
652
+    if (!checkDate || recordDate > checkDate) {
653
+      form.inspectionSelfCheckDate = row.inspectionDate
654
+      calculateDueDate()
655
+    }
656
+  }
657
+}
658
+
659
+function calculateDueDate() {
660
+  if (form.inspectionSelfCheckDate && form.inspectionSelfCheckCycle && form.inspectionSelfCheckCycle > 0) {
661
+    const checkDate = new Date(form.inspectionSelfCheckDate)
662
+    checkDate.setMonth(checkDate.getMonth() + form.inspectionSelfCheckCycle)
663
+    const year = checkDate.getFullYear()
664
+    const month = String(checkDate.getMonth() + 1).padStart(2, '0')
665
+    const day = String(checkDate.getDate()).padStart(2, '0')
666
+    form.nextInspectionDueDate = `${year}-${month}-${day}`
667
+  }
668
+}
669
+
670
+function handleFileUploadProgress(event, file, fileList) {
671
+  upload.isUploading = true
672
+}
673
+
674
+function handleFileSuccess(response, file, fileList) {
675
+  upload.open = false
676
+  upload.isUploading = false
677
+  proxy.$refs['uploadRef'].handleRemove(file)
678
+  proxy.$alert('<div style="overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;">' + response.msg + '</div>', '导入结果', { dangerouslyUseHTMLString: true })
679
+  getList()
680
+}
681
+
682
+function submitFileForm() {
683
+  proxy.$refs['uploadRef'].submit()
684
+}
685
+
686
+onMounted(() => {
687
+  getList()
688
+})
689
+</script>
690
+
691
+<style scoped>
692
+.app-container {
693
+  padding: 20px;
694
+}
695
+
696
+.filter-container {}
697
+
698
+.operation-container {
699
+  margin-bottom: 15px;
700
+}
701
+
702
+.search-form {
703
+  display: flex;
704
+  flex-wrap: wrap;
705
+  gap: 10px;
706
+}
707
+
708
+:deep(.row-red) {
709
+  background-color: rgba(245, 108, 108, 0.3) !important;
710
+}
711
+
712
+:deep(.row-orange) {
713
+  background-color: rgba(230, 162, 60, 0.3) !important;
714
+}
715
+
716
+:deep(.row-yellow) {
717
+  background-color: rgba(255, 206, 86, 0.3) !important;
718
+}
719
+
720
+:deep(.operation-column) {
721
+
722
+  background-color: white !important;
723
+  z-index: 3;
724
+}
725
+</style>

+ 0 - 0
src/views/equipManage/equipLedger/设备台账