index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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. </el-form-item>
  61. </el-form>
  62. </ContentWrap>
  63. <ContentWrap>
  64. <el-table
  65. v-loading="loading"
  66. :data="list"
  67. row-key="id"
  68. :default-expand-all="isExpandAll"
  69. v-if="refreshTable"
  70. style="width: 100%"
  71. >
  72. <el-table-column prop="name" label="BOM节点" >
  73. <template #default="scope">
  74. <!-- 使用 el-tooltip 包裹内容 -->
  75. <el-tooltip
  76. effect="dark"
  77. :content="`设备分类:${scope.row.deviceCategoryName || '暂无'}`"
  78. placement="top-start"
  79. :disabled="!scope.row.deviceCategoryName"
  80. >
  81. <!-- 原有显示名称 -->
  82. <span class="bom-node-name">
  83. {{ scope.row.name }}
  84. </span>
  85. </el-tooltip>
  86. </template>
  87. </el-table-column>
  88. <!-- <el-table-column prop="deviceCategoryName" label="设备分类" /> -->
  89. <el-table-column prop="sort" label="排序" />
  90. <el-table-column prop="status" label="状态" >
  91. <template #default="scope">
  92. <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
  93. </template>
  94. </el-table-column>
  95. <!--
  96. <el-table-column
  97. label="创建时间"
  98. align="center"
  99. prop="createTime"
  100. :formatter="dateFormatter"
  101. /> -->
  102. <el-table-column prop="materials" label="物料数量" />
  103. <el-table-column label="操作" align="center" >
  104. <template #default="scope">
  105. <el-button
  106. link
  107. type="primary"
  108. @click="openForm('update', scope.row)"
  109. v-hasPermi="['rq:iot-bom:update']"
  110. >
  111. 修改
  112. </el-button>
  113. <el-button
  114. link
  115. type="primary"
  116. @click="openSelectMaterialForm(scope.row.id)"
  117. v-hasPermi="['rq:iot-bom:update']"
  118. >
  119. 添加物料
  120. </el-button>
  121. <el-button
  122. link
  123. type="primary"
  124. @click="handleView(scope.row.id)"
  125. v-hasPermi="['rq:iot-bom:update']"
  126. >
  127. 物料详情
  128. </el-button>
  129. <el-button
  130. link
  131. type="danger"
  132. @click="handleDelete(scope.row.id)"
  133. v-hasPermi="['rq:iot-bom:delete']"
  134. >
  135. 删除
  136. </el-button>
  137. </template>
  138. </el-table-column>
  139. </el-table>
  140. </ContentWrap>
  141. </el-col>
  142. </el-row>
  143. <!-- 添加或修改 Bom树节点 对话框 -->
  144. <BomForm ref="formRef" :category_id="selectedId" @success="getList" />
  145. <!-- 添加物料列表 -->
  146. <MaterialList ref="materialListRef" @choose="chooseMaterial" />
  147. <!-- 抽屉组件 -->
  148. <MaterialListDrawer
  149. :model-value="drawerVisible"
  150. @update:model-value="val => drawerVisible = val"
  151. :node-id="currentBomNodeId"
  152. ref="showDrawer"
  153. />
  154. </template>
  155. <script lang="ts" setup>
  156. import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  157. import * as BomApi from '@/api/pms/bom'
  158. import {CommonBomMaterialApi, CommonBomMaterialVO} from '@/api/pms/commonbommaterial'
  159. import BomForm from './BomForm.vue'
  160. import DeviceCategoryTree from './DeviceCategoryTree.vue'
  161. import { useTreeStore } from '@/store/modules/treeStore';
  162. import { ref, computed } from 'vue';
  163. import { handleTree } from '@/utils/tree'
  164. import MaterialList from "@/views/pms/bom/MaterialList.vue";
  165. import MaterialListDrawer from "@/views/pms/bom/MaterialListDrawer.vue";
  166. defineOptions({ name: 'Bom' })
  167. const showDrawer = ref()
  168. const drawerVisible = ref<boolean>(false)
  169. const treeStore = useTreeStore();
  170. const message = useMessage() // 消息弹窗
  171. const { t } = useI18n() // 国际化
  172. const isExpandAll = ref(true) // 是否展开,默认全部展开
  173. const loading = ref(true) // 列表的加载中
  174. const currentBomNodeId = ref() // 当前选中的bom节点
  175. const refreshTable = ref(true) // 重新渲染表格状态
  176. const list = ref() // 列表的数据
  177. const queryParams = reactive({
  178. pageNo: 1,
  179. pageSize: 10,
  180. name: undefined,
  181. status: undefined,
  182. deviceCategoryId: undefined,
  183. })
  184. const queryFormRef = ref() // 搜索的表单
  185. const CommonBomMaterialData = ref({
  186. id: undefined,
  187. deviceCategoryId: undefined,
  188. bomNodeId: undefined,
  189. name: undefined,
  190. code: undefined,
  191. materialId: undefined,
  192. quantity: undefined,
  193. })
  194. // 从 Store 中获取左侧设备分类树选中的 节点ID
  195. let selectedId = computed(() => treeStore.selectedId);
  196. /** 查询 BOM树 列表 */
  197. const getList = async () => {
  198. loading.value = true
  199. try {
  200. const data = await BomApi.getBomPage(queryParams)
  201. list.value = handleTree(data)
  202. } finally {
  203. loading.value = false
  204. }
  205. }
  206. /** 选择物料操作 */
  207. const materialListRef = ref()
  208. const openSelectMaterialForm = (id?: number) => {
  209. materialListRef.value.open(id)
  210. currentBomNodeId.value = id
  211. }
  212. /** 查看物料详情 */
  213. const handleView = async (nodeId) => {
  214. currentBomNodeId.value = nodeId
  215. drawerVisible.value = true
  216. showDrawer.value.openDrawer()
  217. // 强制刷新物料数据
  218. await showDrawer.value.loadMaterials(nodeId)
  219. }
  220. const chooseMaterial = async(row) => {
  221. // 将物料关联到bom节点
  222. try {
  223. CommonBomMaterialData.value.deviceCategoryId = selectedId.value
  224. CommonBomMaterialData.value.bomNodeId = currentBomNodeId.value
  225. CommonBomMaterialData.value.materialId = row.id
  226. CommonBomMaterialData.value.name = row.name
  227. CommonBomMaterialData.value.code = row.code
  228. const data = CommonBomMaterialData.value as unknown as CommonBomMaterialVO
  229. await CommonBomMaterialApi.createCommonBomMaterial(data);
  230. message.success(t('common.createSuccess'))
  231. // 保存成功后立即刷新抽屉数据
  232. showDrawer.value.loadMaterials(currentBomNodeId.value)
  233. await getList()
  234. } finally {
  235. // formLoading.value = false
  236. }
  237. }
  238. /** 搜索按钮操作 */
  239. const handleQuery = () => {
  240. queryParams.pageNo = 1
  241. queryParams.deviceCategoryId = selectedId.value
  242. getList()
  243. }
  244. /** 重置按钮操作 */
  245. const resetQuery = () => {
  246. queryFormRef.value?.resetFields()
  247. handleQuery()
  248. }
  249. /** 处理 设备分类 被点击 */
  250. const handleDeviceCategoryTreeNodeClick = async (row) => {
  251. queryParams.deviceCategoryId = row.id
  252. await getList()
  253. }
  254. /** 添加/修改操作 */
  255. const formRef = ref()
  256. const openForm = (type: string, row) => {
  257. // 如果是没有点击左侧设备树 直接在初始化的列表页面点击某个 BOM节点的修改 也要保存当前BOM关联的设备分类ID
  258. if(row != null) {
  259. treeStore.setSelectedId(row.deviceCategoryId)
  260. formRef.value.open(type, row.id)
  261. return
  262. }
  263. formRef.value.open(type, null)
  264. }
  265. /** 展开/折叠操作 */
  266. const toggleExpandAll = () => {
  267. refreshTable.value = false
  268. isExpandAll.value = !isExpandAll.value
  269. nextTick(() => {
  270. refreshTable.value = true
  271. })
  272. }
  273. /** 删除按钮操作 */
  274. const handleDelete = async (id: number) => {
  275. try {
  276. // 删除的二次确认
  277. await message.delConfirm()
  278. // 发起删除
  279. await BomApi.deleteBomNode(id)
  280. message.success(t('common.delSuccess'))
  281. // 刷新列表
  282. await getList()
  283. } catch {}
  284. }
  285. /** 初始化 */
  286. onMounted(() => {
  287. getList()
  288. })
  289. // 自定义箭头展开位置
  290. const toggleRowExpansion = (row) => {
  291. const $table = document.querySelector('.el-table__body-wrapper') as any
  292. if ($table) {
  293. $table.__vue__.toggleRowExpansion(row)
  294. }
  295. }
  296. const isExpanded = (row) => {
  297. const $table = document.querySelector('.el-table__body-wrapper') as any
  298. return $table?.__vue__.store.states.expandRows.value.includes(row)
  299. }
  300. </script>
  301. <style scoped>
  302. /* 确保表格容器正确继承宽度 */
  303. :deep(.el-table) {
  304. width: 100% !important;
  305. }
  306. /* 操作按钮换行优化 */
  307. .flex-wrap {
  308. flex-wrap: wrap;
  309. }
  310. .gap-4px {
  311. gap: 4px;
  312. }
  313. /* BOM节点名称样式 */
  314. .bom-node-name {
  315. flex: 1;
  316. overflow: hidden;
  317. text-overflow: ellipsis;
  318. white-space: nowrap;
  319. }
  320. </style>