index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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. <el-row :gutter="20">
  6. <!-- 左侧 设备分类 树 -->
  7. <el-col :span="4" :xs="24">
  8. <ContentWrap class="h-1/1">
  9. <DeviceCategoryTree @node-click="handleDeviceCategoryTreeNodeClick" />
  10. </ContentWrap>
  11. </el-col>
  12. <el-col :span="20" :xs="24">
  13. <!-- 搜索 -->
  14. <ContentWrap>
  15. <el-form
  16. class="-mb-15px"
  17. :model="queryParams"
  18. ref="queryFormRef"
  19. :inline="true"
  20. label-width="110px"
  21. >
  22. <el-form-item label="BOM节点名称" prop="name">
  23. <el-input
  24. v-model="queryParams.name"
  25. placeholder="请输入BOM节点名称"
  26. clearable
  27. @keyup.enter="handleQuery"
  28. class="!w-240px"
  29. />
  30. </el-form-item>
  31. <el-form-item label="BOM节点" prop="status">
  32. <el-select
  33. v-model="queryParams.status"
  34. placeholder="请选择BOM节点"
  35. clearable
  36. class="!w-240px"
  37. >
  38. <el-option
  39. v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
  40. :key="dict.value"
  41. :label="dict.label"
  42. :value="dict.value"
  43. />
  44. </el-select>
  45. </el-form-item>
  46. <el-form-item>
  47. <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
  48. <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
  49. <el-button
  50. type="primary"
  51. plain
  52. @click="openForm('create', null)"
  53. v-hasPermi="['rq:iot-bom:create']"
  54. >
  55. <Icon icon="ep:plus" class="mr-5px" /> 新增
  56. </el-button>
  57. <el-button type="danger" plain @click="toggleExpandAll">
  58. <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
  59. </el-button>
  60. <!--
  61. <el-button @click="handleAllQuery"><Icon icon="ep:search" class="mr-5px" /> 查询所有</el-button> -->
  62. </el-form-item>
  63. </el-form>
  64. </ContentWrap>
  65. <ContentWrap>
  66. <el-table
  67. v-loading="loading"
  68. :data="list"
  69. row-key="id"
  70. :default-expand-all="isExpandAll"
  71. v-if="refreshTable"
  72. style="width: 100%"
  73. @row-click="handleClick"
  74. >
  75. <el-table-column prop="name" label="BOM节点" >
  76. <template #default="scope">
  77. <!-- 使用 el-tooltip 包裹内容 -->
  78. <el-tooltip
  79. effect="dark"
  80. :content="`设备分类:${scope.row.deviceCategoryName || '暂无'}`"
  81. placement="top-start"
  82. :disabled="!scope.row.deviceCategoryName"
  83. >
  84. <!-- 原有显示名称 -->
  85. <span class="bom-node-name">
  86. {{ scope.row.name }}
  87. </span>
  88. </el-tooltip>
  89. </template>
  90. </el-table-column>
  91. <el-table-column prop="deviceCategoryName" label="设备分类" />
  92. <el-table-column label="维修" width="100">
  93. <template #default="scope">
  94. <el-switch
  95. :model-value="scope.row.type?.includes(1)"
  96. active-value
  97. inactive-value
  98. disabled
  99. />
  100. </template>
  101. </el-table-column>
  102. <el-table-column label="保养" width="100">
  103. <template #default="scope">
  104. <el-switch
  105. :model-value="scope.row.type?.includes(2)"
  106. active-value
  107. inactive-value
  108. disabled
  109. />
  110. </template>
  111. </el-table-column>
  112. <el-table-column prop="sort" label="排序" width="80"/>
  113. <!-- <el-table-column prop="status" label="状态" width="80">
  114. <template #default="scope">
  115. <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
  116. </template>
  117. </el-table-column> -->
  118. <!--
  119. <el-table-column
  120. label="创建时间"
  121. align="center"
  122. prop="createTime"
  123. :formatter="dateFormatter"
  124. /> -->
  125. <el-table-column prop="materials" label="物料数量" width="80"/>
  126. <el-table-column label="操作" align="center" width="300">
  127. <template #default="scope">
  128. <el-button
  129. link
  130. type="primary"
  131. @click="openForm('update', scope.row)"
  132. v-hasPermi="['rq:iot-bom:update']"
  133. >
  134. 修改
  135. </el-button>
  136. <el-button
  137. link
  138. type="primary"
  139. @click="openSelectMaterialForm(scope.row)"
  140. v-hasPermi="['rq:iot-bom:update']"
  141. >
  142. 添加物料
  143. </el-button>
  144. <el-button
  145. link
  146. type="primary"
  147. @click="handleView(scope.row)"
  148. v-hasPermi="['rq:iot-bom:update']"
  149. >
  150. 物料详情
  151. </el-button>
  152. <el-button
  153. link
  154. type="danger"
  155. @click="handleDelete(scope.row.id)"
  156. v-hasPermi="['rq:iot-bom:delete']"
  157. >
  158. 删除
  159. </el-button>
  160. </template>
  161. </el-table-column>
  162. </el-table>
  163. </ContentWrap>
  164. </el-col>
  165. </el-row>
  166. <!-- 添加或修改 Bom树节点 对话框 -->
  167. <BomForm ref="formRef" :category_id="selectedId" @success="getList" />
  168. <!-- 添加物料列表 -->
  169. <MaterialList ref="materialListRef" @choose="chooseMaterial" />
  170. <!-- 抽屉组件 -->
  171. <MaterialListDrawer
  172. :model-value="drawerVisible"
  173. @update:model-value="val => drawerVisible = val"
  174. :node-id="currentBomNodeId"
  175. ref="showDrawer"
  176. :row-info="currentRowInfo"
  177. @refresh="handleDrawerClosed"
  178. />
  179. </template>
  180. <script lang="ts" setup>
  181. import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  182. import * as BomApi from '@/api/pms/bom'
  183. import {CommonBomMaterialApi, CommonBomMaterialVO} from '@/api/pms/commonbommaterial'
  184. import BomForm from './BomForm.vue'
  185. import DeviceCategoryTree from './DeviceCategoryTree.vue'
  186. import { useTreeStore } from '@/store/modules/treeStore';
  187. import { ref, computed } from 'vue';
  188. import { handleTree } from '@/utils/tree'
  189. import MaterialList from "@/views/pms/bom/MaterialList.vue";
  190. import MaterialListDrawer from "@/views/pms/bom/MaterialListDrawer.vue";
  191. defineOptions({ name: 'Bom' })
  192. const showDrawer = ref()
  193. const drawerVisible = ref<boolean>(false)
  194. const treeStore = useTreeStore();
  195. const message = useMessage() // 消息弹窗
  196. const { t } = useI18n() // 国际化
  197. const isExpandAll = ref(true) // 是否展开,默认全部展开
  198. const loading = ref(true) // 列表的加载中
  199. const currentBomNodeId = ref() // 当前选中的bom节点
  200. const refreshTable = ref(true) // 重新渲染表格状态
  201. const list = ref() // 列表的数据
  202. // 添加存储当前行信息的变量 抽屉页面使用
  203. const currentRowInfo = ref({
  204. deviceCategoryName: '',
  205. bomNodeName: ''
  206. })
  207. const queryParams = reactive({
  208. pageNo: 1,
  209. pageSize: 10,
  210. name: undefined,
  211. status: undefined,
  212. deviceCategoryId: undefined,
  213. })
  214. const queryFormRef = ref() // 搜索的表单
  215. const selectedRow = ref<any>(null)
  216. const CommonBomMaterialData = ref({
  217. id: undefined,
  218. deviceCategoryId: undefined,
  219. bomNodeId: undefined,
  220. name: undefined,
  221. code: undefined,
  222. materialId: undefined,
  223. quantity: undefined,
  224. })
  225. // 从 Store 中获取左侧设备分类树选中的 节点ID
  226. let selectedId = computed(() => treeStore.selectedId);
  227. /** 查询 BOM树 列表 */
  228. const getList = async () => {
  229. loading.value = true
  230. try {
  231. const data = await BomApi.getBomPage(queryParams)
  232. list.value = handleTree(data)
  233. } finally {
  234. loading.value = false
  235. }
  236. }
  237. /** 选择物料操作 */
  238. const materialListRef = ref()
  239. const openSelectMaterialForm = (row: any) => {
  240. materialListRef.value.open(row)
  241. currentBomNodeId.value = row.id
  242. // 保存当前BOM节点的deviceCategoryId
  243. CommonBomMaterialData.value.deviceCategoryId = row.deviceCategoryId
  244. }
  245. /** 查看物料详情 */
  246. const handleView = async (row) => {
  247. currentBomNodeId.value = row.id
  248. // 保存当前行的信息
  249. currentRowInfo.value = {
  250. deviceCategoryName: row.deviceCategoryName || '暂无',
  251. bomNodeName: row.name || '暂无'
  252. }
  253. drawerVisible.value = true
  254. showDrawer.value.openDrawer()
  255. // 强制刷新物料数据
  256. await showDrawer.value.loadMaterials(row.id)
  257. }
  258. const chooseSingleMaterial = async(row) => {
  259. // 将物料关联到bom节点
  260. try {
  261. // CommonBomMaterialData.value.deviceCategoryId = row.deviceCategoryId
  262. CommonBomMaterialData.value.bomNodeId = currentBomNodeId.value
  263. CommonBomMaterialData.value.materialId = row.id
  264. CommonBomMaterialData.value.name = row.name
  265. CommonBomMaterialData.value.code = row.code
  266. const data = CommonBomMaterialData.value as unknown as CommonBomMaterialVO
  267. await CommonBomMaterialApi.createCommonBomMaterial(data);
  268. message.success(t('common.createSuccess'))
  269. // 保存成功后立即刷新抽屉数据
  270. showDrawer.value.loadMaterials(currentBomNodeId.value)
  271. await getList()
  272. } finally {
  273. // formLoading.value = false
  274. }
  275. }
  276. const chooseMaterial = async(selectedMaterials) => {
  277. // 将物料关联到bom节点
  278. try {
  279. // 转换数据结构(根据接口定义调整)
  280. const materialsData = selectedMaterials.map(material => ({
  281. deviceCategoryId: CommonBomMaterialData.value.deviceCategoryId,
  282. bomNodeId: currentBomNodeId.value,
  283. materialId: material.id,
  284. name: material.name,
  285. code: material.code,
  286. }))
  287. // 调用批量添加接口
  288. const resultCount = await CommonBomMaterialApi.addMaterials(materialsData)
  289. message.success(`成功添加物料数量:` + resultCount)
  290. // message.success(t('common.createSuccess'))
  291. // 保存成功后立即刷新抽屉数据
  292. showDrawer.value.loadMaterials(currentBomNodeId.value)
  293. await getList()
  294. } catch (error) {
  295. message.error('添加物料失败!')
  296. }
  297. }
  298. /** 搜索按钮操作 */
  299. const handleQuery = () => {
  300. queryParams.pageNo = 1
  301. queryParams.deviceCategoryId = selectedId.value
  302. getList()
  303. }
  304. /** 查询所有数据 */
  305. const handleAllQuery = () => {
  306. queryParams.pageNo = 1
  307. queryParams.deviceCategoryId = ''
  308. getList()
  309. }
  310. /** 重置按钮操作 */
  311. const resetQuery = () => {
  312. queryFormRef.value?.resetFields()
  313. handleQuery()
  314. }
  315. /** 处理 设备分类 被点击 */
  316. const handleDeviceCategoryTreeNodeClick = async (row) => {
  317. clearRowSelection() //清除表格行选择
  318. queryParams.deviceCategoryId = row.id
  319. await getList()
  320. }
  321. // 添加处理抽屉关闭的方法
  322. const handleDrawerClosed = () => {
  323. getList() // 刷新BOM树数据
  324. }
  325. const parentId = ref('')
  326. const handleClick = (row: any) => {
  327. parentId.value = row.id
  328. selectedRow.value = row // 存储整行数据
  329. console.log('当前行被点击了:' + selectedRow.value.deviceCategoryId)
  330. }
  331. /** 添加/修改操作 */
  332. const formRef = ref()
  333. const openForm = (type: string, row) => {
  334. // 优先使用设备分类树的选择(当selectedRow不存在时)
  335. const useTreeSelection = !selectedRow.value && selectedId.value
  336. // 新增操作时使用选中行的数据
  337. if (type === 'create' && selectedRow.value) {
  338. formRef.value.open(
  339. type,
  340. null,
  341. selectedRow.value?.id || (useTreeSelection ? undefined : undefined), // 作为 parentId
  342. selectedRow.value?.deviceCategoryId || selectedId.value // // 设备分类ID:表格行 > 设备分类树 > 无
  343. )
  344. return
  345. }
  346. // 如果是没有点击左侧设备树 直接在初始化的列表页面点击某个 BOM节点的修改 也要保存当前BOM关联的设备分类ID
  347. if(row != null) {
  348. treeStore.setSelectedId(row.deviceCategoryId)
  349. formRef.value.open(type, row.id, parentId.value)
  350. return
  351. }
  352. formRef.value.open(type, null, parentId.value)
  353. }
  354. /** 展开/折叠操作 */
  355. const toggleExpandAll = () => {
  356. refreshTable.value = false
  357. isExpandAll.value = !isExpandAll.value
  358. nextTick(() => {
  359. refreshTable.value = true
  360. })
  361. }
  362. // 清除表格行选择
  363. const clearRowSelection = () => {
  364. selectedRow.value = null
  365. parentId.value = ''
  366. }
  367. /** 删除按钮操作 */
  368. const handleDelete = async (id: number) => {
  369. try {
  370. // 删除的二次确认
  371. await message.delConfirm()
  372. // 发起删除
  373. await BomApi.deleteBomNode(id)
  374. message.success(t('common.delSuccess'))
  375. // 刷新列表
  376. await getList()
  377. } catch {}
  378. }
  379. /** 初始化 */
  380. onMounted(() => {
  381. getList()
  382. })
  383. // 自定义箭头展开位置
  384. const toggleRowExpansion = (row) => {
  385. const $table = document.querySelector('.el-table__body-wrapper') as any
  386. if ($table) {
  387. $table.__vue__.toggleRowExpansion(row)
  388. }
  389. }
  390. const isExpanded = (row) => {
  391. const $table = document.querySelector('.el-table__body-wrapper') as any
  392. return $table?.__vue__.store.states.expandRows.value.includes(row)
  393. }
  394. </script>
  395. <style scoped>
  396. /* 确保表格容器正确继承宽度 */
  397. :deep(.el-table) {
  398. width: 100% !important;
  399. }
  400. /* 操作按钮换行优化 */
  401. .flex-wrap {
  402. flex-wrap: wrap;
  403. }
  404. .gap-4px {
  405. gap: 4px;
  406. }
  407. /* BOM节点名称样式 */
  408. .bom-node-name {
  409. flex: 1;
  410. overflow: hidden;
  411. text-overflow: ellipsis;
  412. white-space: nowrap;
  413. }
  414. </style>