ConfigDevicePerson.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. <template>
  2. <div class="container">
  3. <el-row :gutter="20" class="equal-height-row">
  4. <!-- 左侧设备列表 -->
  5. <el-col :span="12">
  6. <div class="card">
  7. <h3>设备列表</h3>
  8. <div class="dept-select">
  9. <el-tree-select
  10. clearable
  11. v-model="formData.deptId1"
  12. :data="deptList"
  13. :props="defaultProps"
  14. check-strictly
  15. node-key="id"
  16. filterable
  17. placeholder="请选择设备所属部门"
  18. @node-click="handleDeptDeviceTreeNodeClick"
  19. />
  20. </div>
  21. <el-scrollbar height="400px">
  22. <el-radio-group v-model="selectedDevice">
  23. <div
  24. v-for="device in simpleDevices"
  25. :key="device.id"
  26. class="radio-item"
  27. >
  28. <el-radio :label="device.id">
  29. {{ device.deviceCode }} ({{ device.deviceName }}) - {{ device.devicePersons }}
  30. </el-radio>
  31. </div>
  32. </el-radio-group>
  33. </el-scrollbar>
  34. </div>
  35. </el-col>
  36. <!-- 右侧责任人选择 -->
  37. <el-col :span="12">
  38. <div class="card">
  39. <h3>责任人关联设置</h3>
  40. <div class="dept-select">
  41. <el-tree-select
  42. clearable
  43. v-model="formData.deptId"
  44. :data="deptList"
  45. :props="defaultProps"
  46. check-strictly
  47. node-key="id"
  48. filterable
  49. placeholder="请选择人员所属部门"
  50. @node-click="handleDeptUserTreeNodeClick"
  51. />
  52. </div>
  53. <el-scrollbar height="400px">
  54. <el-checkbox-group v-model="selectedUsers" @change="handleUserSelectionChange">
  55. <div
  56. v-for="user in simpleUsers"
  57. :key="user.id"
  58. class="list-item"
  59. >
  60. <el-checkbox :label="user.id">
  61. {{ user.nickname }} ({{ user.deptName }})
  62. </el-checkbox>
  63. </div>
  64. </el-checkbox-group>
  65. </el-scrollbar>
  66. </div>
  67. </el-col>
  68. </el-row>
  69. <!-- 暂存关联列表 -->
  70. <div class="temp-list card">
  71. <h3>设备责任人调整记录</h3>
  72. <el-table :data="tempRelations" style="width: 100%">
  73. <el-table-column prop="deviceNames" label="设备" width="200" />
  74. <el-table-column prop="userNames" label="关联责任人" />
  75. <el-table-column label="操作" width="120">
  76. <template #default="{ row }">
  77. <el-button
  78. type="danger"
  79. size="small"
  80. @click="removeTempRelation(row.deviceIds)"
  81. >
  82. 删除
  83. </el-button>
  84. </template>
  85. </el-table-column>
  86. </el-table>
  87. <div class="submit-area">
  88. <el-button
  89. type="primary"
  90. size="large"
  91. @click="submitRelations"
  92. :disabled="tempRelations.length === 0"
  93. >
  94. 保 存
  95. </el-button>
  96. </div>
  97. </div>
  98. </div>
  99. </template>
  100. <script setup lang="ts">
  101. import { ref, computed } from 'vue'
  102. import { ElMessage } from 'element-plus'
  103. import {defaultProps, handleTree} from "@/utils/tree";
  104. import * as DeptApi from "@/api/system/dept";
  105. import {IotDeviceApi, IotDeviceVO} from "@/api/pms/device";
  106. import {IotDevicePersonApi, IotDevicePersonVO} from "@/api/pms/iotdeviceperson";
  107. import * as UserApi from "@/api/system/user";
  108. import {simpleUserList, UserVO} from "@/api/system/user";
  109. defineOptions({ name: 'ConfigDevicePerson' })
  110. // 模拟数据
  111. const deviceList = ref([
  112. { id: 'd1', name: '数控机床', type: '生产设备' },
  113. { id: 'd2', name: '检测仪', type: '检测设备' },
  114. { id: 'd3', name: '包装机', type: '包装设备' },
  115. // 更多设备...
  116. ])
  117. const simpleDevices = ref<IotDeviceVO[]>([])
  118. const simpleUsers = ref<UserVO[]>([])
  119. const deptList = ref<Tree[]>([]) // 树形结构
  120. const formData = ref({
  121. id: undefined,
  122. deviceCode: undefined,
  123. deviceName: undefined,
  124. brand: undefined,
  125. model: undefined,
  126. deptId: undefined as string | undefined,
  127. deptId1: undefined as string | undefined,
  128. deviceStatus: undefined,
  129. assetProperty: undefined,
  130. picUrl: undefined,
  131. })
  132. const queryParams = reactive({
  133. deptId1: formData.value.deptId1,
  134. deptId: formData.value.deptId,
  135. })
  136. const emit = defineEmits(['success', 'node-click']) // 定义 success 树点击 事件,用于操作成功后的回调
  137. const departmentList = ref([
  138. { id: 'dept1', name: '生产部' },
  139. { id: 'dept2', name: '质检部' },
  140. { id: 'dept3', name: '设备部' },
  141. ])
  142. const userList = ref([
  143. { id: 'u1', name: '张三', position: '工程师', deptId: 'dept1' },
  144. { id: 'u2', name: '李四', position: '技术员', deptId: 'dept1' },
  145. { id: 'u3', name: '王五', position: '质检员', deptId: 'dept2' },
  146. // 更多用户...
  147. ])
  148. // 响应式数据
  149. const selectedDevice = ref<number>(0)
  150. const selectedDept = ref('')
  151. const selectedUsers = ref<number[]>([])
  152. const tempRelations = ref<Array<{
  153. deviceIds: number[]
  154. deviceNames: string
  155. userIds: number[]
  156. userNames: string
  157. }>>([])
  158. // 计算属性
  159. const filteredUsers = computed(() => {
  160. return userList.value.filter(user => user.deptId === selectedDept.value)
  161. })
  162. const canSave = computed(() => {
  163. return !!selectedDevice.value && selectedUsers.value.length > 0
  164. })
  165. // 方法
  166. const loadUsers = () => {
  167. selectedUsers.value = []
  168. }
  169. // 添加设备选择监听
  170. watch(selectedDevice, (newVal) => {
  171. if (newVal) {
  172. // 切换设备时清空人员选择
  173. selectedUsers.value = []
  174. // 可选:清空部门选择
  175. formData.value.deptId = undefined
  176. simpleUsers.value = []
  177. }
  178. })
  179. // 处理人员选择变化
  180. const handleUserSelectionChange = (value: number[]) => {
  181. if (!selectedDevice.value) {
  182. ElMessage.warning('请先选择设备')
  183. selectedUsers.value = []
  184. return
  185. }
  186. // 获取当前选择的设备
  187. const device = simpleDevices.value.find(d => d.id === selectedDevice.value)
  188. if (!device) return
  189. // 新增或更新关联关系
  190. updateDeviceRelation(device, value)
  191. }
  192. // 修改 暂时 保存关联方法
  193. const saveTempRelation = () => {
  194. if (!selectedDevice.value || selectedUsers.value.length === 0) return
  195. const device = simpleDevices.value.find(d => d.id === selectedDevice.value)
  196. const users = simpleUsers.value.filter(u => selectedUsers.value.includes(u.id))
  197. if (!device) return
  198. const newRelation = {
  199. deviceIds: [selectedDevice.value], // 保持数组结构但只包含单个设备
  200. deviceNames: `${device.deviceCode} (${device.deviceName})`,
  201. userIds: users.map(u => u.id),
  202. userNames: users.map(u => u.nickname).join(', ')
  203. }
  204. // 覆盖已存在的设备关联
  205. const existIndex = tempRelations.value.findIndex(
  206. r => r.deviceIds[0] === selectedDevice.value
  207. )
  208. if (existIndex > -1) {
  209. tempRelations.value[existIndex] = newRelation
  210. } else {
  211. tempRelations.value.push(newRelation)
  212. }
  213. clearSelection()
  214. }
  215. /** 处理 部门-设备 树 被点击 */
  216. const handleDeptDeviceTreeNodeClick = async (row: { [key: string]: any }) => {
  217. emit('node-click', row)
  218. formData.value.deptId1 = row.id
  219. await getDeviceList()
  220. }
  221. /** 获得 部门下的设备 列表 */
  222. const getDeviceList = async () => {
  223. try {
  224. const params = { deptId: formData.value.deptId1 }
  225. const data = await IotDeviceApi.simpleDevices(params)
  226. simpleDevices.value = data || []
  227. } catch (error) {
  228. simpleDevices.value = []
  229. console.error('获取设备列表失败:', error)
  230. }
  231. }
  232. /** 处理 部门-人员 树 被点击 */
  233. const handleDeptUserTreeNodeClick = async (row: { [key: string]: any }) => {
  234. emit('node-click', row)
  235. formData.value.deptId = row.id
  236. await getUserList()
  237. }
  238. /** 获得 部门下的人员 列表 */
  239. const getUserList = async () => {
  240. try {
  241. const params = {
  242. deptId: formData.value.deptId,
  243. pageNo: 1,
  244. pageSize: 10 }
  245. const data = await UserApi.simpleUserList(params)
  246. simpleUsers.value = data || []
  247. } catch (error) {
  248. simpleUsers.value = []
  249. console.error('获取人员列表失败:', error)
  250. }
  251. }
  252. // 更新设备关联关系
  253. const updateDeviceRelation = (device: IotDeviceVO, userIds: number[]) => {
  254. // 获取当前选择的人员
  255. const users = simpleUsers.value.filter(u => userIds.includes(u.id))
  256. const newRelation = {
  257. deviceIds: [device.id],
  258. deviceNames: `${device.deviceCode} (${device.deviceName})`,
  259. userIds: users.map(u => u.id),
  260. userNames: users.map(u => u.nickname).join(', ')
  261. }
  262. // 查找是否已存在该设备的关联
  263. const existIndex = tempRelations.value.findIndex(
  264. r => r.deviceIds[0] === device.id
  265. )
  266. if (existIndex > -1) {
  267. // 如果没有选择人员,移除该关联
  268. if (userIds.length === 0) {
  269. tempRelations.value.splice(existIndex, 1)
  270. } else {
  271. // 更新关联
  272. tempRelations.value[existIndex] = newRelation
  273. }
  274. } else if (userIds.length > 0) {
  275. // 添加新关联
  276. tempRelations.value.push(newRelation)
  277. }
  278. }
  279. const clearSelection = () => {
  280. selectedDevice.value = ''
  281. selectedUsers.value = []
  282. selectedDept.value = ''
  283. }
  284. const removeTempRelation = (deviceIds: string[]) => {
  285. tempRelations.value = tempRelations.value.filter(
  286. r => r.deviceIds.join() !== deviceIds.join()
  287. )
  288. }
  289. const submitRelations = async () => {
  290. try {
  291. // 转换为后端需要的格式
  292. const submitData = tempRelations.value.flatMap(relation => {
  293. return relation.deviceIds.map(deviceId => ({
  294. deviceId,
  295. userIds: relation.userIds
  296. }))
  297. })
  298. await IotDevicePersonApi.saveDevicePersonRelation(submitData)
  299. // 模拟API调用
  300. console.log('提交数据:', submitData)
  301. ElMessage.success('提交成功')
  302. tempRelations.value = []
  303. } catch (error) {
  304. ElMessage.error('提交失败,请重试')
  305. }
  306. }
  307. /** 初始化 */
  308. onMounted(async () => {
  309. // 初始化部门树 根据选择的部门 查询设备列表 部门人员列表
  310. deptList.value = handleTree(await DeptApi.getSimpleDeptList())
  311. })
  312. </script>
  313. <style scoped>
  314. .equal-height-row {
  315. display: flex;
  316. align-items: stretch;
  317. }
  318. .container {
  319. padding: 20px;
  320. }
  321. .card {
  322. border: 1px solid #ebeef5;
  323. border-radius: 4px;
  324. padding: 20px;
  325. margin-bottom: 20px;
  326. background: white;
  327. box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
  328. transition: box-shadow 0.2s;
  329. }
  330. .list-item {
  331. padding: 8px 12px;
  332. border-bottom: 1px solid #f0f0f0;
  333. }
  334. .dept-select {
  335. margin-bottom: 20px;
  336. }
  337. .action-bar {
  338. text-align: right;
  339. }
  340. .temp-list {
  341. margin-top: 20px;
  342. }
  343. .submit-area {
  344. margin-top: 20px;
  345. text-align: center;
  346. }
  347. h3 {
  348. margin: 0 0 15px 0;
  349. color: #303133;
  350. }
  351. .radio-item {
  352. width: 100%;
  353. padding: 8px 12px;
  354. border-bottom: 1px solid #f0f0f0;
  355. }
  356. .radio-item .el-radio {
  357. width: 100%;
  358. height: 100%;
  359. }
  360. .radio-item .el-radio__label {
  361. display: block;
  362. white-space: nowrap;
  363. overflow: hidden;
  364. text-overflow: ellipsis;
  365. }
  366. </style>