index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. <template>
  2. <div class="user">
  3. <div class="tree">
  4. <treeList
  5. title="产品分类"
  6. submitType="1"
  7. :data="treeListData"
  8. v-model="sourceList.pagination.productClassifyId"
  9. @change="treeChange"
  10. @changeTreeList="getTreeList"
  11. >
  12. </treeList>
  13. </div>
  14. <div class="content">
  15. <byTable
  16. :source="sourceList.data"
  17. :pagination="sourceList.pagination"
  18. :config="config"
  19. :loading="loading"
  20. highlight-current-row
  21. :selectConfig="selectConfig"
  22. :table-events="{
  23. //element talbe事件都能传
  24. select: select,
  25. }"
  26. :action-list="[
  27. props.selectStatus
  28. ? {}
  29. : {
  30. text: 'Excel导入',
  31. action: () => openExcel(),
  32. disabled: false,
  33. },
  34. props.selectStatus
  35. ? {}
  36. : {
  37. text: '添加',
  38. action: () => openModal('add'),
  39. disabled: false,
  40. },
  41. ]"
  42. @get-list="getList"
  43. >
  44. <template #pic="{ item }">
  45. <div v-if="item.fileList.length > 0">
  46. <img
  47. :src="item.fileList[0].fileUrl"
  48. class="pic"
  49. @click="handleClickFile(item.fileList[0])"
  50. />
  51. </div>
  52. <div v-else></div>
  53. </template>
  54. <template #size="{ item }">
  55. <div>
  56. <span>{{ item.productLong }}cm</span>*
  57. <span>{{ item.productWide }}cm</span>*
  58. <span>{{ item.productHigh }}cm</span>
  59. </div>
  60. </template>
  61. <template #price="{ item }">
  62. <div>
  63. <span v-if="item.price">{{ item.currency }} {{ item.price }}</span>
  64. </div>
  65. </template>
  66. <template #costPrice="{ item }">
  67. <div>
  68. <span v-if="item.costPrice"
  69. >{{ item.costCurrency }} {{ item.costPrice }}</span
  70. >
  71. </div>
  72. </template>
  73. </byTable>
  74. </div>
  75. <el-dialog
  76. :title="modalType == 'add' ? '添加产品' : '编辑产品'"
  77. v-model="dialogVisible"
  78. width="600"
  79. v-loading="submitLoading"
  80. destroy-on-close
  81. >
  82. <div class="public_height_dialog">
  83. <byForm
  84. :formConfig="formConfig"
  85. :formOption="formOption"
  86. v-model="formData.data"
  87. :rules="rules"
  88. ref="byform"
  89. >
  90. <template #productPic>
  91. <div>
  92. <el-upload
  93. v-model:fileList="fileList"
  94. action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
  95. :data="uploadData"
  96. list-type="picture-card"
  97. :on-remove="handleRemove"
  98. :before-upload="handleBeforeUpload"
  99. accept=".gif, .jpeg, .jpg, .png"
  100. >
  101. <el-icon><Plus /></el-icon>
  102. </el-upload>
  103. </div>
  104. </template>
  105. </byForm>
  106. </div>
  107. <template #footer>
  108. <el-button @click="dialogVisible = false" size="large">取 消</el-button>
  109. <el-button
  110. type="primary"
  111. @click="submitForm('byform')"
  112. size="large"
  113. :loading="submitLoading"
  114. >确 定</el-button
  115. >
  116. </template>
  117. </el-dialog>
  118. <el-dialog
  119. title="导入产品"
  120. v-model="openExcelDialog"
  121. width="400"
  122. v-loading="excelLoading"
  123. >
  124. <el-upload
  125. :action="actionUrl + '/productInfo/excelImportByEhsd'"
  126. :headers="headers"
  127. :on-success="handleSuccess"
  128. :on-progress="handleProgress"
  129. :show-file-list="false"
  130. :on-error="handleError"
  131. accept=".xlsx"
  132. >
  133. <el-button type="primary">点击导入</el-button>
  134. </el-upload>
  135. <template #footer>
  136. <el-button @click="openExcelDialog = false" size="large"
  137. >取 消</el-button
  138. >
  139. </template>
  140. </el-dialog>
  141. </div>
  142. </template>
  143. <script setup>
  144. import { ElMessage, ElMessageBox } from "element-plus";
  145. import byTable from "@/components/byTable/index";
  146. import byForm from "@/components/byForm/index";
  147. import treeList from "@/components/product/treeList";
  148. import { getToken } from "@/utils/auth";
  149. const headers = ref({ Authorization: "Bearer " + getToken() });
  150. const actionUrl = import.meta.env.VITE_APP_BASE_API;
  151. const loading = ref(false);
  152. const submitLoading = ref(false);
  153. const treeListData = ref([]);
  154. const innerMethon = ref([]);
  155. const outsideMethon = ref([]);
  156. const productUnit = ref([]);
  157. const accountCurrency = ref([]);
  158. const sourceList = ref({
  159. data: [],
  160. pagination: {
  161. total: 3,
  162. pageNum: 1,
  163. pageSize: 10,
  164. type: "",
  165. productClassifyId: "",
  166. keyword: "",
  167. definition: "1",
  168. },
  169. });
  170. let dialogVisible = ref(false);
  171. let openExcelDialog = ref(false);
  172. let excelLoading = ref(false);
  173. let modalType = ref("add");
  174. let rules = ref({
  175. productClassifyId: [
  176. { required: true, message: "请选择产品分类", trigger: "change" },
  177. ],
  178. name: [{ required: true, message: "请输入产品名称", trigger: "blur" }],
  179. productLong: [
  180. { required: true, message: "请输入长 (cm)", trigger: "blur" },
  181. ],
  182. productWide: [
  183. { required: true, message: "请输入宽 (cm)", trigger: "blur" },
  184. ],
  185. productHigh: [
  186. { required: true, message: "请输入高 (cm)", trigger: "blur" },
  187. ],
  188. innerPackMethod: [
  189. { required: true, message: "请选择内包装方式", trigger: "change" },
  190. ],
  191. outerPackMethod: [
  192. { required: true, message: "请选择外包装方式", trigger: "change" },
  193. ],
  194. });
  195. const { proxy } = getCurrentInstance();
  196. const props = defineProps({
  197. selectStatus: Boolean,
  198. });
  199. const selectConfig = reactive([
  200. // {
  201. // label: "产品类型",
  202. // prop: "type",
  203. // data: [],
  204. // },
  205. ]);
  206. const config = computed(() => {
  207. return [
  208. {
  209. attrs: {
  210. label: "图片",
  211. slot: "pic",
  212. align: "center",
  213. width: 100,
  214. },
  215. },
  216. {
  217. attrs: {
  218. label: "产品编码",
  219. prop: "code",
  220. },
  221. },
  222. {
  223. attrs: {
  224. label: "产品名称",
  225. prop: "name",
  226. },
  227. },
  228. {
  229. attrs: {
  230. label: "尺寸",
  231. slot: "size",
  232. },
  233. },
  234. {
  235. attrs: {
  236. label: "销售指导价",
  237. slot: "price",
  238. width: 150,
  239. },
  240. },
  241. {
  242. attrs: {
  243. label: "成本价",
  244. slot: "costPrice",
  245. width: 150,
  246. },
  247. },
  248. {
  249. attrs: {
  250. label: "操作",
  251. width: "120",
  252. align: "center",
  253. },
  254. // 渲染 el-button,一般用在最后一列。
  255. renderHTML(row) {
  256. return [
  257. props.selectStatus
  258. ? {
  259. attrs: {
  260. label: "选择",
  261. type: "primary",
  262. text: true,
  263. },
  264. el: "button",
  265. click() {
  266. clickSelect(row);
  267. },
  268. }
  269. : {
  270. attrs: {
  271. label: "修改",
  272. type: "primary",
  273. text: true,
  274. },
  275. el: "button",
  276. click() {
  277. getDtl(row);
  278. },
  279. },
  280. props.selectStatus
  281. ? {}
  282. : {
  283. attrs: {
  284. label: "删除",
  285. type: "danger",
  286. text: true,
  287. },
  288. el: "button",
  289. click() {
  290. // 弹窗提示是否删除
  291. ElMessageBox.confirm(
  292. "此操作将永久删除该数据, 是否继续?",
  293. "提示",
  294. {
  295. confirmButtonText: "确定",
  296. cancelButtonText: "取消",
  297. type: "warning",
  298. }
  299. ).then(() => {
  300. // 删除
  301. proxy
  302. .post("/productInfo/delete", {
  303. id: row.id,
  304. })
  305. .then((res) => {
  306. ElMessage({
  307. message: "删除成功",
  308. type: "success",
  309. });
  310. getList();
  311. });
  312. });
  313. },
  314. },
  315. ];
  316. },
  317. },
  318. ];
  319. });
  320. const uploadData = ref({});
  321. const fileList = ref([]);
  322. const fileListCopy = ref([]);
  323. let formData = reactive({
  324. data: {},
  325. });
  326. const formOption = reactive({
  327. disabled: false,
  328. inline: true,
  329. labelWidth: 100,
  330. itemWidth: 100,
  331. rules: [],
  332. });
  333. const byform = ref(null);
  334. const formConfig = computed(() => {
  335. return [
  336. {
  337. type: "title",
  338. title: "基本信息",
  339. },
  340. {
  341. type: "treeSelect",
  342. prop: "productClassifyId",
  343. label: "产品分类",
  344. data: treeListData.value,
  345. itemWidth: 100,
  346. disabled: false,
  347. style: {
  348. width: "100%",
  349. },
  350. },
  351. {
  352. type: "input",
  353. prop: "name",
  354. label: "产品名称",
  355. itemWidth: 100,
  356. disabled: false,
  357. },
  358. {
  359. type: "input",
  360. prop: "nameEnglish",
  361. label: "英文名",
  362. itemWidth: 100,
  363. disabled: false,
  364. },
  365. {
  366. type: "slot",
  367. slotName: "productPic",
  368. prop: "fileList",
  369. label: "产品图片",
  370. },
  371. {
  372. type: "title",
  373. title: "价格信息",
  374. },
  375. {
  376. type: "selectInput",
  377. prop: "price",
  378. selectProp: "currency",
  379. label: "销售指导价",
  380. itemWidth: 50,
  381. style: {
  382. width: "100%",
  383. },
  384. data: accountCurrency.value,
  385. },
  386. {
  387. type: "selectInput",
  388. prop: "costPrice",
  389. selectProp: "costCurrency",
  390. label: "成本价",
  391. itemWidth: 50,
  392. style: {
  393. width: "100%",
  394. },
  395. data: accountCurrency.value,
  396. },
  397. {
  398. type: "title",
  399. title: "属性信息",
  400. },
  401. {
  402. type: "input",
  403. prop: "spec",
  404. label: "规格型号",
  405. itemWidth: 100,
  406. disabled: false,
  407. },
  408. {
  409. type: "input",
  410. prop: "productLong",
  411. label: "尺寸",
  412. itemWidth: 33.33,
  413. placeholder: "长(cm)",
  414. disabled: false,
  415. },
  416. {
  417. type: "input",
  418. prop: "productWide",
  419. label: " ",
  420. itemWidth: 33.33,
  421. placeholder: "宽(cm)",
  422. disabled: false,
  423. },
  424. {
  425. type: "input",
  426. prop: "productHigh",
  427. label: " ",
  428. itemWidth: 33.33,
  429. placeholder: "高(cm)",
  430. disabled: false,
  431. },
  432. {
  433. type: "select",
  434. prop: "innerPackMethod",
  435. label: "内包装方式",
  436. required: true,
  437. itemWidth: 50,
  438. multiple: true,
  439. data: innerMethon.value,
  440. placeholder: "内包装方式",
  441. style: {
  442. width: "100%",
  443. },
  444. disabled: false,
  445. },
  446. {
  447. type: "select",
  448. prop: "outerPackMethod",
  449. label: "外包装方式",
  450. required: true,
  451. itemWidth: 50,
  452. multiple: true,
  453. data: outsideMethon.value,
  454. placeholder: "外包装方式",
  455. style: {
  456. width: "100%",
  457. },
  458. disabled: false,
  459. },
  460. {
  461. type: "input",
  462. prop: "netWeight",
  463. label: "净重(kg)",
  464. itemWidth: 100,
  465. style: {
  466. width: "30%",
  467. },
  468. },
  469. {
  470. type: "input",
  471. prop: "hsCode",
  472. label: "海关编码",
  473. itemWidth: 100,
  474. style: {
  475. width: "30%",
  476. },
  477. disabled: false,
  478. },
  479. {
  480. type: "input",
  481. itemType: "textarea",
  482. prop: "remark",
  483. label: "备注",
  484. itemWidth: 100,
  485. },
  486. ];
  487. });
  488. const getList = async (req) => {
  489. sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
  490. loading.value = true;
  491. proxy
  492. .post("/productInfo/getConditionProductList", sourceList.value.pagination)
  493. .then(
  494. (message) => {
  495. sourceList.value.data = message.rows.map((x) => ({
  496. ...x,
  497. fileList: [],
  498. ...JSON.parse(x.ehsdJson),
  499. }));
  500. sourceList.value.pagination.total = message.total;
  501. setTimeout(() => {
  502. loading.value = false;
  503. }, 200);
  504. const productIdList = message.rows.map((x) => x.id);
  505. // 请求文件数据并回显
  506. if (productIdList.length > 0) {
  507. proxy
  508. .post("/fileInfo/getList", {
  509. businessIdList: productIdList,
  510. })
  511. .then((fileObj) => {
  512. for (let i = 0; i < sourceList.value.data.length; i++) {
  513. const e = sourceList.value.data[i];
  514. for (const key in fileObj) {
  515. if (e.id === key) {
  516. e.fileList = fileObj[key];
  517. }
  518. }
  519. }
  520. });
  521. }
  522. },
  523. (err) => {
  524. loading.value = false;
  525. }
  526. );
  527. };
  528. const treeChange = (e) => {
  529. sourceList.value.pagination.productClassifyId = e.id;
  530. getList({ productClassifyId: e.id });
  531. };
  532. const openModal = () => {
  533. dialogVisible.value = true;
  534. modalType.value = "add";
  535. formData.data = {
  536. definition: "1",
  537. outerPackMethod: [],
  538. innerPackMethod: [],
  539. fileList: [],
  540. fileListCopy: [],
  541. currency: "",
  542. costCurrency: "",
  543. };
  544. if (accountCurrency.value && accountCurrency.value.length > 0) {
  545. formData.data.currency = accountCurrency.value[0].value;
  546. formData.data.costCurrency = accountCurrency.value[0].value;
  547. }
  548. fileList.value = [];
  549. fileListCopy.value = [];
  550. };
  551. const openExcel = () => {
  552. openExcelDialog.value = true;
  553. };
  554. const needAtt = [
  555. "productClassifyId",
  556. "name",
  557. "spec",
  558. "remark",
  559. "fileList",
  560. "id",
  561. "unit",
  562. "definition",
  563. ];
  564. const submitForm = () => {
  565. byform.value.handleSubmit((valid) => {
  566. // if (!fileListCopy.value.length > 0) {
  567. // return ElMessage({
  568. // message: "请上传产品图片",
  569. // type: "info",
  570. // });
  571. // }
  572. let jsonObj = {};
  573. formData.data.fileList = fileListCopy.value.map((x) => ({
  574. id: x.id,
  575. fileName: x.fileName,
  576. }));
  577. for (const key in formData.data) {
  578. if (needAtt.includes(key)) {
  579. } else {
  580. jsonObj[key] = formData.data[key];
  581. delete formData.data[key];
  582. }
  583. }
  584. jsonObj.innerPackMethod = jsonObj.innerPackMethod.join(",");
  585. jsonObj.outerPackMethod = jsonObj.outerPackMethod.join(",");
  586. jsonObj.type = "1"; //1为公司产品库
  587. formData.data.ehsdJson = JSON.stringify(jsonObj);
  588. submitLoading.value = true;
  589. proxy.post(`/productInfo/${modalType.value}ByEhsd`, formData.data).then(
  590. (res) => {
  591. ElMessage({
  592. message: modalType.value == "add" ? "添加成功" : "编辑成功",
  593. type: "success",
  594. });
  595. dialogVisible.value = false;
  596. submitLoading.value = false;
  597. getList();
  598. },
  599. (err) => {
  600. for (const key in jsonObj) {
  601. formData.data[key] = jsonObj[key];
  602. }
  603. formData.data.innerPackMethod =
  604. formData.data.innerPackMethod.split(",");
  605. formData.data.outerPackMethod =
  606. formData.data.outerPackMethod.split(",");
  607. submitLoading.value = false;
  608. }
  609. );
  610. });
  611. };
  612. const getTreeList = () => {
  613. proxy
  614. .post("/productClassify/tree", { parentId: "", name: "", definition: "1" })
  615. .then((message) => {
  616. treeListData.value = message;
  617. });
  618. };
  619. const getDtl = (row) => {
  620. modalType.value = "edit";
  621. proxy.post("/productInfo/detailByEhsd", { id: row.id }).then((res) => {
  622. res.definition = "1"; //产品
  623. let jsonObj = JSON.parse(res.ehsdJson);
  624. res = {
  625. ...res,
  626. currency: jsonObj.currency
  627. ? jsonObj.currency
  628. : accountCurrency.value[0].value,
  629. costCurrency: jsonObj.costCurrency
  630. ? jsonObj.costCurrency
  631. : accountCurrency.value[0].value,
  632. ...jsonObj,
  633. };
  634. if (res.innerPackMethod) {
  635. res.innerPackMethod = res.innerPackMethod.split(",");
  636. } else {
  637. res.innerPackMethod = [];
  638. }
  639. if (res.outerPackMethod) {
  640. res.outerPackMethod = res.outerPackMethod.split(",");
  641. } else {
  642. res.outerPackMethod = [];
  643. }
  644. formData.data = res;
  645. dialogVisible.value = true;
  646. proxy
  647. .post("/fileInfo/getList", { businessIdList: [row.id] })
  648. .then((fileObj) => {
  649. if (fileObj[row.id]) {
  650. fileList.value = fileObj[row.id].map((x) => ({
  651. ...x,
  652. url: x.fileUrl,
  653. }));
  654. fileListCopy.value = fileObj[row.id].map((x) => ({
  655. ...x,
  656. url: x.fileUrl,
  657. }));
  658. } else {
  659. fileList.value = [];
  660. fileListCopy.value = [];
  661. }
  662. });
  663. });
  664. };
  665. const isdisabled = ["price", "costPrice", "remark", "netWeight"];
  666. watch(modalType, (val) => {
  667. if (val) {
  668. for (let i = 0; i < formConfig.value.length; i++) {
  669. const element = formConfig.value[i];
  670. if (element.type != "title" || element.type != "slot") {
  671. if (!isdisabled.includes(element.prop)) {
  672. element.disabled = val == "edit" ? true : false;
  673. }
  674. }
  675. }
  676. }
  677. });
  678. const handleBeforeUpload = async (file) => {
  679. const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
  680. uploadData.value = res.uploadBody;
  681. fileListCopy.value.push({
  682. id: res.id,
  683. fileName: res.fileName,
  684. path: res.fileUrl,
  685. url: res.fileUrl,
  686. uid: file.uid,
  687. });
  688. };
  689. const handleRemove = (file) => {
  690. const index = fileListCopy.value.findIndex(
  691. (x) => x.uid === file.uid || x.id === file.id
  692. );
  693. fileListCopy.value.splice(index, 1);
  694. };
  695. const handleClickFile = (file) => {
  696. window.open(file.fileUrl, "_blank");
  697. };
  698. const handleProgress = () => {
  699. excelLoading.value = true;
  700. };
  701. const handleError = (err) => {
  702. ElMessage({
  703. message: `${err},请重试!`,
  704. type: "info",
  705. });
  706. openExcelDialog.value = false;
  707. excelLoading.value = false;
  708. };
  709. const handleSuccess = (res) => {
  710. if (res.code != 200) {
  711. return ElMessage({
  712. message: `${res.msg},请重试!`,
  713. type: "info",
  714. });
  715. } else {
  716. ElMessage({
  717. message: "导入成功!",
  718. type: "success",
  719. });
  720. openExcelDialog.value = false;
  721. excelLoading.value = false;
  722. getList();
  723. }
  724. };
  725. const getDict = () => {
  726. proxy
  727. .getDictOne([
  728. "inner_packaging_method_ehsd",
  729. "outside_packaging_method_ehsd",
  730. "unit",
  731. "account_currency",
  732. ])
  733. .then((res) => {
  734. innerMethon.value = res["inner_packaging_method_ehsd"].map((x) => ({
  735. label: x.dictValue,
  736. value: x.dictKey,
  737. }));
  738. outsideMethon.value = res["outside_packaging_method_ehsd"].map((x) => ({
  739. label: x.dictValue,
  740. value: x.dictKey,
  741. }));
  742. productUnit.value = res["unit"].map((x) => ({
  743. label: x.dictValue,
  744. value: x.dictKey,
  745. }));
  746. accountCurrency.value = res["account_currency"].map((x) => ({
  747. label: x.dictValue,
  748. value: x.dictKey,
  749. }));
  750. });
  751. };
  752. getDict();
  753. getTreeList();
  754. getList();
  755. const clickSelect = (item) => {
  756. proxy.$emit("selectProduct", item);
  757. };
  758. </script>
  759. <style lang="scss" scoped>
  760. .user {
  761. padding: 20px;
  762. display: flex;
  763. justify-content: space-between;
  764. .tree {
  765. width: 300px;
  766. }
  767. .content {
  768. width: calc(100% - 320px);
  769. }
  770. }
  771. .pic {
  772. object-fit: contain;
  773. width: 50px;
  774. height: 50px;
  775. cursor: pointer;
  776. vertical-align: middle;
  777. }
  778. </style>