BomList.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <template>
  2. <doc-alert title="用户体系" url="https://doc.iocoder.cn/user-center/" />
  3. <doc-alert title="三方登陆" url="https://doc.iocoder.cn/social-user/" />
  4. <doc-alert title="Excel 导入导出" url="https://doc.iocoder.cn/excel-import-and-export/" />
  5. <!-- 搜索 -->
  6. <ContentWrap>
  7. <el-form
  8. class="-mb-15px"
  9. :model="queryParams"
  10. ref="queryFormRef"
  11. :inline="true"
  12. label-width="110px"
  13. >
  14. <el-form-item :label="t('bomList.bomNodeName')" prop="name">
  15. <el-input
  16. v-model="queryParams.name"
  17. :placeholder="t('bomList.nameHolder')"
  18. clearable
  19. @keyup.enter="handleQuery"
  20. class="!w-240px"
  21. />
  22. </el-form-item>
  23. <el-form-item :label="t('bomList.bomNode')" prop="status">
  24. <el-select
  25. v-model="queryParams.status"
  26. :placeholder="t('bomList.nodeHolder')"
  27. clearable
  28. class="!w-240px"
  29. >
  30. <el-option
  31. v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
  32. :key="dict.value"
  33. :label="dict.label"
  34. :value="dict.value"
  35. />
  36. </el-select>
  37. </el-form-item>
  38. <el-form-item>
  39. <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />
  40. {{ t('bomList.search') }}</el-button>
  41. <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />
  42. {{ t('bomList.reset') }}</el-button>
  43. <el-button
  44. type="primary"
  45. plain
  46. @click="openForm('create', null)"
  47. v-hasPermi="['rq:iot-bom:create']"
  48. >
  49. <Icon icon="ep:plus" class="mr-5px" />
  50. {{ t('bomList.add') }}
  51. </el-button>
  52. <el-button type="danger" plain @click="toggleExpandAll">
  53. <Icon icon="ep:sort" class="mr-5px" />
  54. {{ t('bomList.unfold') }}
  55. </el-button>
  56. <!--
  57. <el-button @click="handleAllQuery"><Icon icon="ep:search" class="mr-5px" /> 查询所有</el-button> -->
  58. </el-form-item>
  59. </el-form>
  60. </ContentWrap>
  61. <ContentWrap>
  62. <el-table
  63. v-loading="loading"
  64. :data="list"
  65. row-key="id"
  66. :default-expand-all="isExpandAll"
  67. v-if="refreshTable"
  68. style="width: 100%"
  69. >
  70. <el-table-column prop="name" :label="t('bomList.bomNodeName')">
  71. <template #default="scope">
  72. <!-- 使用 el-tooltip 包裹内容 -->
  73. <el-tooltip
  74. effect="dark"
  75. :content="`设备分类:${scope.row.deviceCategoryName || '暂无'}`"
  76. placement="top-start"
  77. :disabled="!scope.row.deviceCategoryName"
  78. >
  79. <!-- 原有显示名称 -->
  80. <span class="bom-node-name">
  81. {{ scope.row.name }}
  82. </span>
  83. </el-tooltip>
  84. </template>
  85. </el-table-column>
  86. <el-table-column prop="deviceCategoryName" :label="t('info.deviceClass')" />
  87. <el-table-column prop="sort" :label="t('common.sort')" />
  88. <el-table-column prop="status" :label="t('common.status')">
  89. <template #default="scope">
  90. <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
  91. </template>
  92. </el-table-column>
  93. <el-table-column prop="materials" :label="t('iotMaintain.numberOfMaterials')" />
  94. <el-table-column :label="t('iotMaintain.operation')" align="center">
  95. <template #default="scope">
  96. <el-button
  97. link
  98. type="primary"
  99. @click="openForm('update', scope.row)"
  100. v-hasPermi="['rq:iot-bom:update']"
  101. >
  102. {{ t('modelTemplate.update') }}
  103. </el-button>
  104. <el-button
  105. link
  106. type="primary"
  107. @click="openSelectMaterialForm(scope.row.id, scope.row.deviceCategoryId)"
  108. v-hasPermi="['rq:iot-bom:update']"
  109. >
  110. {{ t('bomList.add') }}
  111. </el-button>
  112. <el-button
  113. link
  114. type="primary"
  115. @click="handleView(scope.row.id)"
  116. v-hasPermi="['rq:iot-bom:update']"
  117. >
  118. {{ t('action.detail') }}
  119. </el-button>
  120. <el-button
  121. link
  122. type="danger"
  123. @click="handleDelete(scope.row.id)"
  124. v-hasPermi="['rq:iot-bom:delete']"
  125. >
  126. {{ t('action.del') }}
  127. </el-button>
  128. </template>
  129. </el-table-column>
  130. </el-table>
  131. </ContentWrap>
  132. <!-- 添加或修改 Bom树节点 对话框 -->
  133. <BomFormDevice ref="formRef" :deviceId="props.deviceId" @success="getList" />
  134. <!-- 添加物料列表 -->
  135. <MaterialList ref="materialListRef" @choose="chooseMaterial" />
  136. <!-- 抽屉组件 -->
  137. <MaterialListDrawerDevice
  138. :model-value="drawerVisible"
  139. @update:model-value="(val) => (drawerVisible = val)"
  140. :node-id="currentBomNodeId"
  141. :deviceId = "props.deviceId"
  142. ref="showDrawer"
  143. />
  144. </template>
  145. <script lang="ts" setup>
  146. import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  147. import * as BomApi from '@/api/pms/devicebom'
  148. import * as MaterialApi from '@/api/pms/iotdevicematerial'
  149. import BomFormDevice from './BomFormDevice.vue'
  150. import { useTreeStore } from '@/store/modules/treeStore'
  151. import { computed, ref } from 'vue'
  152. import { handleTree } from '@/utils/tree'
  153. import MaterialList from '@/views/pms/bom/MaterialList.vue'
  154. import MaterialListDrawerDevice from '@/views/pms/device/MaterialListDrawerDevice.vue'
  155. defineOptions({ name: 'BomDevice' })
  156. const showDrawer = ref()
  157. const drawerVisible = ref<boolean>(false)
  158. const treeStore = useTreeStore()
  159. const message = useMessage() // 消息弹窗
  160. const { t } = useI18n() // 国际化
  161. const isExpandAll = ref(true) // 是否展开,默认全部展开
  162. const loading = ref(true) // 列表的加载中
  163. const currentBomNodeId = ref() // 当前选中的bom节点
  164. const refreshTable = ref(true) // 重新渲染表格状态
  165. const list = ref() // 列表的数据
  166. const props = defineProps<{ deviceId?: number }>()
  167. const queryParams = reactive({
  168. pageNo: 1,
  169. pageSize: 10,
  170. name: undefined,
  171. status: undefined,
  172. deviceId: undefined
  173. })
  174. const queryFormRef = ref() // 搜索的表单
  175. const CommonBomMaterialData = ref({
  176. id: undefined,
  177. deviceCategoryId: undefined,
  178. bomNodeId: undefined,
  179. name: undefined,
  180. code: undefined,
  181. materialId: undefined,
  182. quantity: undefined,
  183. deviceId: undefined,
  184. })
  185. // 从 Store 中获取左侧设备分类树选中的 节点ID
  186. let selectedId = computed(() => treeStore.selectedId)
  187. /** 查询 BOM树 列表 */
  188. const getList = async () => {
  189. loading.value = true
  190. queryParams.deviceId = props.deviceId
  191. try {
  192. const data = await BomApi.IotDeviceBomApi.getIotDeviceBomPage(queryParams)
  193. if (data&&data.list) {
  194. list.value = handleTree(data.list)
  195. }
  196. } finally {
  197. loading.value = false
  198. }
  199. }
  200. /** 选择物料操作 */
  201. const materialListRef = ref()
  202. const openSelectMaterialForm = (id?: number, deviceCategoryId?: number) => {
  203. materialListRef.value.open(id)
  204. currentBomNodeId.value = id
  205. // 保存当前BOM节点的deviceCategoryId
  206. CommonBomMaterialData.value.deviceCategoryId = deviceCategoryId
  207. }
  208. /** 查看物料详情 */
  209. const handleView = async (nodeId) => {
  210. currentBomNodeId.value = nodeId
  211. drawerVisible.value = true
  212. showDrawer.value.openDrawer()
  213. // 强制刷新物料数据
  214. await showDrawer.value.loadMaterials(nodeId)
  215. }
  216. const chooseMaterial = async (row) => {
  217. // 将物料关联到bom节点
  218. try {
  219. // CommonBomMaterialData.value.deviceCategoryId = row.deviceCategoryId
  220. CommonBomMaterialData.value.bomNodeId = currentBomNodeId.value
  221. CommonBomMaterialData.value.materialId = row.id
  222. CommonBomMaterialData.value.name = row.name
  223. CommonBomMaterialData.value.code = row.code
  224. CommonBomMaterialData.value.deviceId = props.deviceId
  225. const data = CommonBomMaterialData.value as unknown as CommonBomMaterialVO
  226. debugger
  227. await MaterialApi.IotDeviceMaterialApi.createIotDeviceMaterial(data)
  228. message.success(t('common.createSuccess'))
  229. // 保存成功后立即刷新抽屉数据
  230. showDrawer.value.loadMaterials(currentBomNodeId.value)
  231. await getList()
  232. } finally {
  233. // formLoading.value = false
  234. }
  235. }
  236. /** 搜索按钮操作 */
  237. const handleQuery = () => {
  238. queryParams.pageNo = 1
  239. queryParams.deviceCategoryId = selectedId.value
  240. getList()
  241. }
  242. /** 查询所有数据 */
  243. const handleAllQuery = () => {
  244. queryParams.pageNo = 1
  245. queryParams.deviceCategoryId = ''
  246. getList()
  247. }
  248. /** 重置按钮操作 */
  249. const resetQuery = () => {
  250. queryFormRef.value?.resetFields()
  251. handleQuery()
  252. }
  253. /** 处理 设备分类 被点击 */
  254. // const handleDeviceCategoryTreeNodeClick = async (row) => {
  255. // queryParams.deviceCategoryId = row.id
  256. // await getList()
  257. // }
  258. /** 添加/修改操作 */
  259. const formRef = ref()
  260. const openForm = (type: string, row) => {
  261. // 如果是没有点击左侧设备树 直接在初始化的列表页面点击某个 BOM节点的修改 也要保存当前BOM关联的设备分类ID
  262. if (row != null) {
  263. treeStore.setSelectedId(row.deviceCategoryId)
  264. formRef.value.open(type, row.id)
  265. return
  266. }
  267. formRef.value.open(type, null)
  268. }
  269. /** 展开/折叠操作 */
  270. const toggleExpandAll = () => {
  271. refreshTable.value = false
  272. isExpandAll.value = !isExpandAll.value
  273. nextTick(() => {
  274. refreshTable.value = true
  275. })
  276. }
  277. /** 删除按钮操作 */
  278. const handleDelete = async (id: number) => {
  279. try {
  280. // 删除的二次确认
  281. await message.delConfirm()
  282. // 发起删除
  283. await BomApi.IotDeviceBomApi.deleteIotDeviceBom(id)
  284. message.success(t('common.delSuccess'))
  285. // 刷新列表
  286. await getList()
  287. } catch {}
  288. }
  289. /** 初始化 */
  290. onMounted(() => {
  291. getList()
  292. })
  293. // 自定义箭头展开位置
  294. const toggleRowExpansion = (row) => {
  295. const $table = document.querySelector('.el-table__body-wrapper') as any
  296. if ($table) {
  297. $table.__vue__.toggleRowExpansion(row)
  298. }
  299. }
  300. const isExpanded = (row) => {
  301. const $table = document.querySelector('.el-table__body-wrapper') as any
  302. return $table?.__vue__.store.states.expandRows.value.includes(row)
  303. }
  304. </script>
  305. <style scoped>
  306. /* 确保表格容器正确继承宽度 */
  307. :deep(.el-table) {
  308. width: 100% !important;
  309. }
  310. /* 操作按钮换行优化 */
  311. .flex-wrap {
  312. flex-wrap: wrap;
  313. }
  314. .gap-4px {
  315. gap: 4px;
  316. }
  317. /* BOM节点名称样式 */
  318. .bom-node-name {
  319. flex: 1;
  320. overflow: hidden;
  321. text-overflow: ellipsis;
  322. white-space: nowrap;
  323. }
  324. </style>