index.vue 9.7 KB

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