index.vue 16 KB

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