index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. <template>
  2. <div>
  3. <el-row :gutter="10">
  4. <el-col :span="4" v-if="!props.expressStatus">
  5. <el-card :class="props.selectStatus ? 'select-card' : 'box-card'">
  6. <el-input v-model="filterTree" placeholder="请输入BOM分类" />
  7. <el-tree
  8. ref="treeCategory"
  9. :data="categoryTreeData"
  10. :props="{ children: 'children', label: 'name' }"
  11. node-key="id"
  12. default-expand-all
  13. :expand-on-click-node="false"
  14. :indent="10"
  15. :filter-node-method="filterNodeMethod"
  16. @node-click="handleNodeClick" />
  17. </el-card>
  18. </el-col>
  19. <el-col :span="props.expressStatus ? 24 : 20">
  20. <el-card :class="props.selectStatus ? 'select-card' : 'box-card'">
  21. <byTable
  22. :source="sourceList.data"
  23. :pagination="sourceList.pagination"
  24. :config="config"
  25. :loading="loading"
  26. :searchConfig="searchConfig"
  27. :defaultExpandAll="props.selectStatus"
  28. highlight-current-row
  29. :action-list="[
  30. props.selectStatus
  31. ? ''
  32. : {
  33. text: '添加BOM',
  34. action: () => clickModal(),
  35. },
  36. props.selectStatus
  37. ? ''
  38. : {
  39. text: '操作日志',
  40. action: () => viewLogs(),
  41. },
  42. ]"
  43. @get-list="getList"
  44. @clickReset="clickReset">
  45. <template #typeExpand="{ item }">
  46. <div style="box-sizing: border-box">
  47. <div
  48. v-for="spec in item.bomSpecList"
  49. :key="spec.id"
  50. style="display: flex; padding: 8px 16px; align-items: center; box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1); margin-bottom: 8px">
  51. <div style="width: 80px">
  52. <div v-if="spec.mainImgUrl">
  53. <img
  54. style="width: 40px; height: 40px; object-fit: contain; vertical-align: middle; border: none; cursor: pointer"
  55. :src="spec.mainImgUrl"
  56. @click="openFile(spec.mainImgUrl)" />
  57. </div>
  58. </div>
  59. <div style="width: 140px" v-if="props.purchaseId">采购数量: {{ spec.purchaseQuantity }}</div>
  60. <div style="width: 140px" v-if="props.purchaseId">已入库数量: {{ spec.arrivalQuantity || 0 }}</div>
  61. <div style="width: 180px">
  62. {{ spec.code }}
  63. </div>
  64. <div style="flex: 1">
  65. {{ spec.name }}
  66. </div>
  67. <div style="width: 180px">
  68. {{ `${spec.length} * ${spec.width} * ${spec.height}(cm³)` }}
  69. </div>
  70. <div style="width: 60px; text-align: center" v-if="props.selectStatus && !props.priceSystemId">
  71. <el-button type="primary" text @click="selectBOM(spec)" v-preReClick>选择</el-button>
  72. </div>
  73. <div style="width: 140px; padding: 0 12px" v-if="props.priceSystemId">
  74. <el-input-number
  75. onmousewheel="return false;"
  76. v-model="spec.internalSellingPrice"
  77. placeholder="对内销售价"
  78. style="width: 100%"
  79. :controls="false"
  80. :min="0"
  81. :precision="2"
  82. @change="changePrice(spec)" />
  83. </div>
  84. <div style="width: 140px; padding: 0 12px" v-if="props.priceSystemId">
  85. <el-input-number
  86. onmousewheel="return false;"
  87. v-model="spec.externalSellingPrice"
  88. placeholder="对外销售价"
  89. style="width: 100%"
  90. :controls="false"
  91. :min="0"
  92. :precision="2"
  93. @change="changePrice(spec)" />
  94. </div>
  95. </div>
  96. </div>
  97. </template>
  98. <template #name="{ item }">
  99. <div>
  100. <a style="color: #409eff; cursor: pointer; word-break: break-all" @click="clickName(item)">{{ item.name }}</a>
  101. </div>
  102. </template>
  103. <template #priceBillingStandardId="{ item }">
  104. <div style="width: 100%">
  105. <el-select v-model="item.priceBillingStandardId" placeholder="加工报价" clearable @change="changePriceBillingStandard(item)">
  106. <el-option v-for="item in priceBillingStandardList" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
  107. </el-select>
  108. </div>
  109. </template>
  110. <template #internalSellingPrice>
  111. <div style="width: 100%"></div>
  112. </template>
  113. <template #externalSellingPrice>
  114. <div style="width: 100%"></div>
  115. </template>
  116. </byTable>
  117. </el-card>
  118. </el-col>
  119. </el-row>
  120. <el-dialog :title="modalTitle" v-if="openDialog" v-model="openDialog" width="90%">
  121. <MakeBOM :rowData="rowData" :detailStatus="detailStatus" @clickCancel="clickCancel"></MakeBOM>
  122. </el-dialog>
  123. <el-dialog title="操作日志" v-if="openLogs" v-model="openLogs" width="50%">
  124. <byTable
  125. :source="logsList.data"
  126. :pagination="logsList.pagination"
  127. :config="configLogs"
  128. :loading="loadingLogs"
  129. highlight-current-row
  130. @get-list="getLogsList">
  131. </byTable>
  132. <template #footer>
  133. <el-button @click="openLogs = false" size="large">关 闭</el-button>
  134. </template>
  135. </el-dialog>
  136. </div>
  137. </template>
  138. <script setup>
  139. import byTable from "/src/components/byTable/index";
  140. import { ElMessage, ElMessageBox } from "element-plus";
  141. import MakeBOM from "/src/components/makeBOM/index";
  142. const { proxy } = getCurrentInstance();
  143. const props = defineProps({
  144. selectStatus: Boolean,
  145. bomClassifyIdList: Array,
  146. expressStatus: Boolean,
  147. priceSystemId: String,
  148. purchaseId: String,
  149. bomClassifyId: String,
  150. });
  151. const filterTree = ref("");
  152. const treeCategory = ref(null);
  153. const categoryTreeData = ref([]);
  154. const getTreeList = () => {
  155. proxy.post("/bomClassify/tree", {}).then((res) => {
  156. if (res && res.length > 0) {
  157. if (props.bomClassifyIdList) {
  158. categoryTreeData.value = res.filter((item) => props.bomClassifyIdList.includes(item.id));
  159. } else {
  160. categoryTreeData.value = res;
  161. }
  162. }
  163. });
  164. };
  165. getTreeList();
  166. watch(filterTree, (val) => {
  167. treeCategory.value.filter(val);
  168. });
  169. const filterNodeMethod = (value, data) => {
  170. if (!value) return true;
  171. return data.name.includes(value);
  172. };
  173. const handleNodeClick = (val) => {
  174. getList({ bomClassifyId: val.id });
  175. };
  176. const sourceList = ref({
  177. data: [],
  178. pagination: {
  179. total: 0,
  180. pageNum: 1,
  181. pageSize: 10,
  182. name: "",
  183. code: "",
  184. species: "",
  185. chromatophore: "",
  186. frontGrain: "",
  187. reverseGrain: "",
  188. colour: "",
  189. bomClassifyId: "",
  190. bomClassifyIdList: [],
  191. priceSystemId: "",
  192. bomSpecCode: "",
  193. bomSpecName: "",
  194. },
  195. });
  196. const loading = ref(false);
  197. const searchConfig = computed(() => {
  198. return [
  199. {
  200. type: "input",
  201. prop: "code",
  202. label: "群组品号",
  203. },
  204. {
  205. type: "input",
  206. prop: "name",
  207. label: "群组品名",
  208. },
  209. {
  210. type: "input",
  211. prop: "bomSpecCode",
  212. label: "BOM品号",
  213. },
  214. {
  215. type: "input",
  216. prop: "bomSpecName",
  217. label: "BOM品名",
  218. },
  219. {
  220. type: "select",
  221. prop: "species",
  222. dictKey: "bom_species",
  223. label: "种类",
  224. },
  225. {
  226. type: "select",
  227. prop: "chromatophore",
  228. dictKey: "bom_chromatophore",
  229. label: "色层",
  230. },
  231. {
  232. type: "select",
  233. prop: "embossingProcess",
  234. dictKey: "bom_embossingProcess",
  235. label: "压纹工艺",
  236. },
  237. {
  238. type: "select",
  239. prop: "frontGrain",
  240. dictKey: "bom_frontGrain",
  241. label: "正面纹路",
  242. },
  243. {
  244. type: "select",
  245. prop: "reverseGrain",
  246. dictKey: "bom_reverseGrain",
  247. label: "背面纹路",
  248. },
  249. {
  250. type: "input",
  251. prop: "colour",
  252. label: "颜色",
  253. },
  254. ];
  255. });
  256. const config = computed(() => {
  257. return [
  258. {
  259. type: "expand",
  260. attrs: {
  261. label: " ",
  262. slot: "typeExpand",
  263. width: 50,
  264. },
  265. },
  266. {
  267. attrs: {
  268. label: "群组品名",
  269. slot: "name",
  270. "min-width": 240,
  271. },
  272. },
  273. {
  274. attrs: {
  275. label: "项目小类",
  276. prop: "itemSubclass",
  277. width: 120,
  278. },
  279. render(val) {
  280. return proxy.dictKeyValue(val, proxy.useUserStore().allDict["bom_itemSubclass"]);
  281. },
  282. },
  283. {
  284. attrs: {
  285. label: "种类",
  286. prop: "species",
  287. width: 120,
  288. },
  289. render(val) {
  290. return proxy.dictKeyValue(val, proxy.useUserStore().allDict["bom_species"]);
  291. },
  292. },
  293. {
  294. attrs: {
  295. label: "色层",
  296. prop: "chromatophore",
  297. width: 100,
  298. },
  299. render(val) {
  300. return proxy.dictKeyValue(val, proxy.useUserStore().allDict["bom_chromatophore"]);
  301. },
  302. },
  303. props.priceSystemId
  304. ? {
  305. attrs: {
  306. label: "加工报价",
  307. slot: "priceBillingStandardId",
  308. width: 160,
  309. },
  310. }
  311. : {
  312. attrs: {
  313. label: "压纹工艺",
  314. prop: "embossingProcess",
  315. width: 120,
  316. },
  317. render(val) {
  318. return proxy.dictKeyValue(val, proxy.useUserStore().allDict["bom_embossingProcess"]);
  319. },
  320. },
  321. props.priceSystemId
  322. ? {
  323. attrs: {
  324. label: "对内销售价(含税)",
  325. slot: "internalSellingPrice",
  326. width: 140,
  327. },
  328. }
  329. : {
  330. attrs: {
  331. label: "正面纹路",
  332. prop: "frontGrain",
  333. width: 130,
  334. },
  335. render(val) {
  336. return proxy.dictKeyValue(val, proxy.useUserStore().allDict["bom_frontGrain"]);
  337. },
  338. },
  339. props.priceSystemId
  340. ? {
  341. attrs: {
  342. label: "对内销售价(含税)",
  343. slot: "externalSellingPrice",
  344. width: 140,
  345. },
  346. }
  347. : {
  348. attrs: {
  349. label: "背面纹路",
  350. prop: "reverseGrain",
  351. width: 130,
  352. },
  353. render(val) {
  354. return proxy.dictKeyValue(val, proxy.useUserStore().allDict["bom_reverseGrain"]);
  355. },
  356. },
  357. props.priceSystemId
  358. ? {
  359. attrs: {
  360. width: 16,
  361. },
  362. }
  363. : {
  364. attrs: {
  365. label: "操作",
  366. width: 120,
  367. align: "center",
  368. fixed: "right",
  369. },
  370. renderHTML(row) {
  371. return [
  372. props.selectStatus
  373. ? {}
  374. : {
  375. attrs: {
  376. label: "编辑",
  377. type: "primary",
  378. text: true,
  379. },
  380. el: "button",
  381. click() {
  382. clickUpdate(row);
  383. },
  384. },
  385. props.selectStatus
  386. ? {}
  387. : {
  388. attrs: {
  389. label: "删除",
  390. type: "danger",
  391. text: true,
  392. },
  393. el: "button",
  394. click() {
  395. clickDelete(row);
  396. },
  397. },
  398. ];
  399. },
  400. },
  401. ];
  402. });
  403. const getList = async (req, status) => {
  404. if (status) {
  405. sourceList.value.pagination = {
  406. pageNum: sourceList.value.pagination.pageNum,
  407. pageSize: sourceList.value.pagination.pageSize,
  408. };
  409. } else {
  410. sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
  411. }
  412. if (props.bomClassifyIdList) {
  413. sourceList.value.pagination.bomClassifyIdList = props.bomClassifyIdList;
  414. }
  415. if (props.expressStatus) {
  416. if (props.bomClassifyId) {
  417. sourceList.value.pagination.bomClassifyId = props.bomClassifyId;
  418. } else {
  419. sourceList.value.pagination.bomClassifyId = "1682221528948760578";
  420. }
  421. }
  422. loading.value = true;
  423. let path = "/bom/page";
  424. if (props.priceSystemId) {
  425. path = "/priceSystem/getBomPriceDetail";
  426. sourceList.value.pagination.priceSystemId = props.priceSystemId;
  427. } else if (props.purchaseId) {
  428. path = "/purchaseBom/page";
  429. sourceList.value.pagination.purchaseId = props.purchaseId;
  430. }
  431. proxy.post(path, sourceList.value.pagination).then((res) => {
  432. sourceList.value.data = res.rows;
  433. sourceList.value.pagination.total = res.total;
  434. setTimeout(() => {
  435. loading.value = false;
  436. }, 200);
  437. });
  438. };
  439. getList();
  440. const clickReset = () => {
  441. treeCategory.value.setCurrentKey(null);
  442. getList("", true);
  443. };
  444. const modalTitle = ref("添加BOM");
  445. const openDialog = ref(false);
  446. const rowData = ref({});
  447. const detailStatus = ref(false);
  448. const clickModal = () => {
  449. modalTitle.value = "添加BOM";
  450. rowData.value = {};
  451. detailStatus.value = false;
  452. openDialog.value = true;
  453. };
  454. const clickUpdate = (row) => {
  455. modalTitle.value = "编辑BOM";
  456. rowData.value = row;
  457. detailStatus.value = false;
  458. openDialog.value = true;
  459. };
  460. const clickDelete = (row) => {
  461. ElMessageBox.confirm("你是否确认此操作", "提示", {
  462. confirmButtonText: "确定",
  463. cancelButtonText: "取消",
  464. type: "warning",
  465. })
  466. .then(() => {
  467. proxy.post("/bom/delete", { id: row.id }).then(() => {
  468. ElMessage({ message: "删除成功", type: "success" });
  469. getList();
  470. });
  471. })
  472. .catch(() => {});
  473. };
  474. const clickCancel = (status) => {
  475. openDialog.value = false;
  476. if (status) {
  477. getList();
  478. }
  479. };
  480. const openFile = (path) => {
  481. window.open(path);
  482. };
  483. const clickName = (row) => {
  484. modalTitle.value = "BOM详情";
  485. rowData.value = row;
  486. detailStatus.value = true;
  487. openDialog.value = true;
  488. };
  489. const openLogs = ref(false);
  490. const loadingLogs = ref(false);
  491. const logsList = ref({
  492. data: [],
  493. pagination: {
  494. total: 0,
  495. pageNum: 1,
  496. pageSize: 10,
  497. },
  498. });
  499. const type = ref([
  500. { dictKey: "1", dictValue: "新增" },
  501. { dictKey: "2", dictValue: "修改" },
  502. { dictKey: "3", dictValue: "删除" },
  503. ]);
  504. const configLogs = computed(() => {
  505. return [
  506. {
  507. attrs: {
  508. label: "操作时间",
  509. prop: "createTime",
  510. width: 160,
  511. align: "center",
  512. },
  513. },
  514. {
  515. attrs: {
  516. label: "操作人",
  517. prop: "operator",
  518. align: "center",
  519. },
  520. },
  521. {
  522. attrs: {
  523. label: "BOM品号",
  524. prop: "code",
  525. align: "center",
  526. },
  527. },
  528. {
  529. attrs: {
  530. label: "行为",
  531. prop: "type",
  532. width: 100,
  533. align: "center",
  534. },
  535. render(val) {
  536. return proxy.dictKeyValue(val, type.value);
  537. },
  538. },
  539. ];
  540. });
  541. const viewLogs = () => {
  542. logsList.value.data = [];
  543. logsList.value.pagination.total = 0;
  544. openLogs.value = true;
  545. getLogsList({ pageNum: 1, pageSize: 10 });
  546. };
  547. const getLogsList = async (req) => {
  548. logsList.value.pagination = { ...logsList.value.pagination, ...req };
  549. loadingLogs.value = true;
  550. proxy.post("/bomOperatingLog/page", logsList.value.pagination).then((res) => {
  551. logsList.value.data = res.rows;
  552. logsList.value.pagination.total = res.total;
  553. setTimeout(() => {
  554. loadingLogs.value = false;
  555. }, 200);
  556. });
  557. };
  558. const emit = defineEmits(["selectBOM"]);
  559. const selectBOM = (item) => {
  560. emit("selectBOM", item);
  561. };
  562. const priceBillingStandardList = ref([]);
  563. const getPriceBillingStandard = () => {
  564. proxy.post("/priceBillingStandard/list", {}).then((res) => {
  565. if (res && res.length > 0) {
  566. priceBillingStandardList.value = res.map((item) => {
  567. return {
  568. dictKey: item.id,
  569. dictValue: item.name,
  570. };
  571. });
  572. }
  573. });
  574. };
  575. if (props.priceSystemId) {
  576. getPriceBillingStandard();
  577. }
  578. const changePriceBillingStandard = (row) => {
  579. let data = {
  580. priceSystemId: props.priceSystemId,
  581. bomId: row.id,
  582. priceBillingStandardId: row.priceBillingStandardId,
  583. };
  584. proxy.post("/priceSystemBom/saveOrEdit", data).then();
  585. };
  586. const changePrice = (row) => {
  587. let data = {
  588. priceSystemId: props.priceSystemId,
  589. bomSpecId: row.id,
  590. internalSellingPrice: row.internalSellingPrice,
  591. externalSellingPrice: row.externalSellingPrice,
  592. };
  593. proxy.post("/priceSystemBomSpec/saveOrEdit", data).then();
  594. };
  595. </script>
  596. <style lang="scss" scoped>
  597. :deep(.el-dialog) {
  598. margin-top: 10px !important;
  599. margin-bottom: 10px !important;
  600. }
  601. .select-card {
  602. height: calc(100vh - 184px);
  603. overflow-y: auto;
  604. overflow-x: hidden;
  605. }
  606. </style>