index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. <template>
  2. <div style="height: calc(100vh - 114px); overflow-y: auto; overflow-x: hidden">
  3. <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="submit">
  4. <template #skuClassifyId>
  5. <div style="width: 100%">
  6. <el-cascader
  7. v-model="formData.data.skuClassifyId"
  8. :options="classifyList"
  9. :props="{ checkStrictly: true, value: 'id', label: 'name', emitPath: false }"
  10. clearable
  11. style="width: 100%" />
  12. </div>
  13. </template>
  14. <template #brand>
  15. <div style="width: 100%">{{ formData.data.brand }}</div>
  16. </template>
  17. <template #specification>
  18. <div style="width: 100%">
  19. <div>
  20. <el-icon style="cursor: pointer; transform: translateY(4px); font-size: 24px; margin-left: 10px" @click="addSpecification"><Plus /></el-icon>
  21. </div>
  22. <el-table :data="formData.data.skuSpecList" :row-style="{ height: '35px' }" header-row-class-name="tableHeader">
  23. <el-table-column label="规格图" align="center" width="100">
  24. <template #default="{ row, $index }">
  25. <el-form-item :prop="'skuSpecList.' + $index + '.specImgUrl'">
  26. <el-upload
  27. class="avatar-uploader"
  28. action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
  29. :data="uploadData"
  30. :show-file-list="false"
  31. :on-success="
  32. (response, uploadFile) => {
  33. return handleSuccess(uploadFile, $index);
  34. }
  35. "
  36. :before-upload="uploadFile">
  37. <el-image v-if="row.specImgUrl" :src="row.specImgUrl" fit="scale-down" class="avatar" />
  38. <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
  39. </el-upload>
  40. </el-form-item>
  41. </template>
  42. </el-table-column>
  43. <!-- <el-table-column label="设计图" align="center" width="100">
  44. <template #default="{ row, $index }">
  45. <el-form-item :prop="'skuSpecList.' + $index + '.designImgUrl'">
  46. <el-upload
  47. class="avatar-uploader"
  48. action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
  49. :data="uploadDesignData"
  50. :show-file-list="false"
  51. :on-success="
  52. (response, uploadFile) => {
  53. return handleDesignSuccess(uploadFile, $index);
  54. }
  55. "
  56. :before-upload="uploadDesignFile">
  57. <el-image v-if="row.designImgUrl" :src="row.designImgUrl" fit="scale-down" class="avatar" />
  58. <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
  59. </el-upload>
  60. </el-form-item>
  61. </template>
  62. </el-table-column> -->
  63. <el-table-column label="品名" min-width="220">
  64. <template #default="{ row, $index }">
  65. <el-form-item :prop="'skuSpecList.' + $index + '.name'" :rules="rulesSpec.name" :inline-message="true" style="width: 100%">
  66. <el-input v-model="row.name" placeholder="请输入品名" />
  67. </el-form-item>
  68. </template>
  69. </el-table-column>
  70. <el-table-column label="品号" width="160">
  71. <template #default="{ row, $index }">
  72. <el-form-item :prop="'skuSpecList.' + $index + '.code'" :rules="rulesSpec.code" :inline-message="true" style="width: 100%">
  73. <el-input v-model="row.code" placeholder="请输入品号" />
  74. </el-form-item>
  75. </template>
  76. </el-table-column>
  77. <el-table-column label="图稿文件" align="center" width="160">
  78. <template #default="{ row, $index }">
  79. <el-button type="primary" @click="clickDrawingFile($index)" text>选择</el-button>
  80. </template>
  81. </el-table-column>
  82. <el-table-column label="加工版面" width="140">
  83. <template #default="{ row, $index }">
  84. <el-form-item :prop="'skuSpecList.' + $index + '.machinedPanel'" style="width: 100%">
  85. <el-select v-model="row.machinedPanel" placeholder="加工版面" clearable>
  86. <el-option v-for="item in useUserStore().allDict['processing_layout']" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
  87. </el-select>
  88. </el-form-item>
  89. </template>
  90. </el-table-column>
  91. <el-table-column label="尺寸(长宽高,cm)" align="center" width="280">
  92. <template #default="{ row, $index }">
  93. <el-row>
  94. <el-col :span="8">
  95. <el-form-item :prop="'skuSpecList.' + $index + '.length'" :rules="rulesSpec.length" :inline-message="true" style="width: 100%">
  96. <el-input-number
  97. onmousewheel="return false;"
  98. v-model="row.length"
  99. placeholder="长"
  100. style="width: 100%"
  101. :controls="false"
  102. :min="0"
  103. :precision="2" />
  104. </el-form-item>
  105. </el-col>
  106. <el-col :span="8">
  107. <el-form-item :prop="'skuSpecList.' + $index + '.width'" :rules="rulesSpec.width" :inline-message="true" style="width: 100%">
  108. <el-input-number
  109. onmousewheel="return false;"
  110. v-model="row.width"
  111. placeholder="宽"
  112. style="width: 100%"
  113. :controls="false"
  114. :min="0"
  115. :precision="2" />
  116. </el-form-item>
  117. </el-col>
  118. <el-col :span="8">
  119. <el-form-item :prop="'skuSpecList.' + $index + '.height'" :rules="rulesSpec.height" :inline-message="true" style="width: 100%">
  120. <el-input-number
  121. onmousewheel="return false;"
  122. v-model="row.height"
  123. placeholder="高"
  124. style="width: 100%"
  125. :controls="false"
  126. :min="0"
  127. :precision="2" />
  128. </el-form-item>
  129. </el-col>
  130. </el-row>
  131. </template>
  132. </el-table-column>
  133. <el-table-column label="净重(g)" width="120">
  134. <template #default="{ row, $index }">
  135. <el-form-item :prop="'skuSpecList.' + $index + '.netWeight'" :rules="rulesSpec.netWeight" :inline-message="true" style="width: 100%">
  136. <el-input-number
  137. onmousewheel="return false;"
  138. v-model="row.netWeight"
  139. placeholder="净重"
  140. style="width: 100%"
  141. :controls="false"
  142. :min="0"
  143. :precision="2" />
  144. </el-form-item>
  145. </template>
  146. </el-table-column>
  147. <el-table-column label="BOM" align="left" width="250">
  148. <template #default="{ row, $index }">
  149. <el-button
  150. v-if="!row.bomSpecId"
  151. type="primary"
  152. size="small"
  153. @click="handleOpen($index)"
  154. style="margin: 5px 0; background-color: #43b214; border-color: #43b214">
  155. 选择BOM
  156. </el-button>
  157. <div style="width: 100%; display: flex; align-items: center" v-else>
  158. <el-icon style="font-size: 16px; cursor: pointer" @click="clickRemoveBOM($index)"><Remove /></el-icon>
  159. <span>{{ row.bomSpecName }}</span>
  160. </div>
  161. </template>
  162. </el-table-column>
  163. <el-table-column label="操作" align="center" fixed="right" width="60">
  164. <template #default="{ $index }">
  165. <el-button type="primary" @click="clickDelete($index)" text>删除</el-button>
  166. </template>
  167. </el-table-column>
  168. </el-table>
  169. </div>
  170. </template>
  171. <template #mainImgUrl>
  172. <div style="width: 100%">
  173. <div style="color: #aaaaaa">图片不超过5M,支持JPEG、JPG、PNG格式;</div>
  174. <div style="color: #aaaaaa">建议主图大于640*640,主题鲜明、图片清晰、提升买家满意度;</div>
  175. <el-upload
  176. class="avatar-uploader-main"
  177. action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
  178. :data="uploadMainData"
  179. :show-file-list="false"
  180. :on-success="handleMainSuccess"
  181. :before-upload="uploadMainFile">
  182. <el-image v-if="formData.data.mainImgUrl" :src="formData.data.mainImgUrl" fit="scale-down" class="avatar" />
  183. <el-icon v-else class="avatar-uploader-main-icon"><Plus /></el-icon>
  184. </el-upload>
  185. </div>
  186. </template>
  187. <template #detailText>
  188. <div style="width: 100%">
  189. <div v-if="props.detailStatus">
  190. <div v-html="getStyle(formData.data.detailText)"></div>
  191. </div>
  192. <Editor v-else :value="formData.data.detailText" @updateValue="updateValue" ref="editor" />
  193. </div>
  194. </template>
  195. </byForm>
  196. <div style="width: 100%; text-align: center; margin: 10px">
  197. <el-button v-if="props.detailStatus" @click="clickCancel()" size="large">关 闭</el-button>
  198. <el-button v-if="!props.detailStatus" @click="clickCancel()" size="large">取 消</el-button>
  199. <el-button type="primary" @click="submitForm()" v-if="!props.detailStatus" size="large" v-preReClick>确 定</el-button>
  200. </div>
  201. <el-dialog title="选择BOM" v-if="openBOM" v-model="openBOM" width="84%">
  202. <SelectBOM :selectStatus="true" :bomClassifyIdList="[1]" @selectBOM="selectBOM"></SelectBOM>
  203. <template #footer>
  204. <el-button @click="openBOM = false" size="large">关 闭</el-button>
  205. </template>
  206. </el-dialog>
  207. <el-dialog title="选择BOM" v-if="openDrawingFile" v-model="openDrawingFile" width="70%">
  208. <SelectPicture @selectPic="selectPic"></SelectPicture>
  209. <template #footer>
  210. <el-button @click="openDrawingFile = false" size="large">关 闭</el-button>
  211. </template>
  212. </el-dialog>
  213. </div>
  214. </template>
  215. <script setup>
  216. import byForm from "@/components/byForm/index";
  217. import { ElMessage } from "element-plus";
  218. import Editor from "@/components/Editor/index.vue";
  219. import SelectBOM from "@/views/group/BOM/management/index";
  220. import SelectPicture from "@/components/select-picture/index.vue";
  221. const { proxy } = getCurrentInstance();
  222. const emit = defineEmits(["clickCancel"]);
  223. const submit = ref(null);
  224. const formOption = reactive({
  225. inline: true,
  226. labelWidth: "120px",
  227. itemWidth: 100,
  228. rules: [],
  229. labelPosition: "right",
  230. });
  231. const formData = reactive({
  232. data: {
  233. brand: "胜德科技",
  234. type: 1,
  235. source: 1,
  236. craftProductionLineId: "1",
  237. mainImgUrl: "",
  238. detailText: "",
  239. skuSpecList: [
  240. {
  241. specImgUrl: "",
  242. designImgUrl: "",
  243. name: "",
  244. code: "",
  245. barCode: "",
  246. machinedPanel: "",
  247. length: undefined,
  248. width: undefined,
  249. height: undefined,
  250. netWeight: undefined,
  251. sharedFolder: "",
  252. bomSpecId: "",
  253. remark: "",
  254. packagingMaterialList: [],
  255. expressPackingList: [],
  256. },
  257. ],
  258. },
  259. });
  260. const formConfig = computed(() => {
  261. return [
  262. {
  263. type: "title",
  264. title: "基本信息",
  265. label: "",
  266. },
  267. {
  268. type: "slot",
  269. prop: "skuClassifyId",
  270. slotName: "skuClassifyId",
  271. label: "类目",
  272. itemWidth: 50,
  273. },
  274. {
  275. type: "input",
  276. prop: "barCode",
  277. label: "条码",
  278. itemType: "text",
  279. itemWidth: 50,
  280. },
  281. {
  282. type: "input",
  283. prop: "groupItemNumber",
  284. label: "群组品号",
  285. itemType: "text",
  286. itemWidth: 50,
  287. },
  288. {
  289. type: "input",
  290. prop: "code",
  291. label: "编码",
  292. itemType: "text",
  293. itemWidth: 50,
  294. },
  295. {
  296. type: "input",
  297. prop: "name",
  298. label: "名称",
  299. itemType: "text",
  300. },
  301. {
  302. type: "title",
  303. title: "材质特征",
  304. label: "",
  305. },
  306. {
  307. type: "slot",
  308. slotName: "brand",
  309. label: "品牌",
  310. },
  311. {
  312. type: "input",
  313. prop: "modelNumber",
  314. label: "型号",
  315. itemType: "text",
  316. itemWidth: 50,
  317. },
  318. {
  319. type: "input",
  320. prop: "material",
  321. label: "材质",
  322. itemType: "text",
  323. itemWidth: 50,
  324. },
  325. {
  326. type: "slot",
  327. slotName: "specification",
  328. label: "规格",
  329. },
  330. {
  331. type: "title",
  332. title: "工艺路线",
  333. label: "",
  334. },
  335. {
  336. type: "select",
  337. label: "工艺路线",
  338. prop: "craftProductionLineId",
  339. data: [],
  340. itemWidth: 50,
  341. },
  342. {
  343. type: "title",
  344. title: "图片介绍",
  345. label: "",
  346. },
  347. {
  348. type: "slot",
  349. slotName: "mainImgUrl",
  350. label: "主图",
  351. },
  352. {
  353. type: "slot",
  354. slotName: "detailText",
  355. label: "详情描述",
  356. },
  357. ];
  358. });
  359. const rules = ref({
  360. groupItemNumber: [{ required: true, message: "请输入群组品号", trigger: "blur" }],
  361. code: [{ required: true, message: "请输入编码", trigger: "blur" }],
  362. name: [{ required: true, message: "请输入名称", trigger: "blur" }],
  363. });
  364. const rulesSpec = ref({
  365. code: [{ required: true, message: "请输入品号", trigger: "blur" }],
  366. name: [{ required: true, message: "请输入品名", trigger: "blur" }],
  367. length: [{ required: true, message: "请输入长", trigger: "blur" }],
  368. width: [{ required: true, message: "请输入宽", trigger: "blur" }],
  369. height: [{ required: true, message: "请输入高", trigger: "blur" }],
  370. netWeight: [{ required: true, message: "请输入净重", trigger: "blur" }],
  371. });
  372. const classifyList = ref([]);
  373. const getBomClassify = () => {
  374. proxy.post("/skuClassify/tree", {}).then((res) => {
  375. classifyList.value = res;
  376. });
  377. };
  378. getBomClassify();
  379. const addSpecification = () => {
  380. if (!props.detailStatus) {
  381. if (formData.data.skuSpecList && formData.data.skuSpecList.length > 0) {
  382. formData.data.skuSpecList.push({
  383. specImgUrl: "",
  384. designImgUrl: "",
  385. name: "",
  386. code: "",
  387. barCode: "",
  388. machinedPanel: "",
  389. length: undefined,
  390. width: undefined,
  391. height: undefined,
  392. netWeight: undefined,
  393. sharedFolder: "",
  394. bomSpecId: "",
  395. remark: "",
  396. packagingMaterialList: [],
  397. expressPackingList: [],
  398. });
  399. } else {
  400. formData.data.skuSpecList = [
  401. {
  402. specImgUrl: "",
  403. designImgUrl: "",
  404. name: "",
  405. code: "",
  406. barCode: "",
  407. machinedPanel: "",
  408. length: undefined,
  409. width: undefined,
  410. height: undefined,
  411. netWeight: undefined,
  412. sharedFolder: "",
  413. bomSpecId: "",
  414. remark: "",
  415. packagingMaterialList: [],
  416. expressPackingList: [],
  417. },
  418. ];
  419. }
  420. }
  421. };
  422. const uploadData = ref({});
  423. const uploadFile = async (file) => {
  424. const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
  425. uploadData.value = res.uploadBody;
  426. file.id = res.id;
  427. file.fileName = res.fileName;
  428. file.fileUrl = res.fileUrl;
  429. return true;
  430. };
  431. const handleSuccess = (uploadFile, index) => {
  432. formData.data.skuSpecList[index].specImgUrl = uploadFile.raw.fileUrl;
  433. };
  434. // const uploadDesignData = ref({});
  435. // const uploadDesignFile = async (file) => {
  436. // const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
  437. // uploadDesignData.value = res.uploadBody;
  438. // file.id = res.id;
  439. // file.fileName = res.fileName;
  440. // file.fileUrl = res.fileUrl;
  441. // return true;
  442. // };
  443. // const handleDesignSuccess = (uploadFile, index) => {
  444. // formData.data.skuSpecList[index].designImgUrl = uploadFile.raw.fileUrl;
  445. // };
  446. const clickDelete = (index) => {
  447. formData.data.skuSpecList.splice(index, 1);
  448. };
  449. const editor = ref(null);
  450. const updateValue = (val) => {
  451. formData.data.detailText = val;
  452. };
  453. const uploadMainData = ref({});
  454. const uploadMainFile = async (file) => {
  455. const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
  456. uploadMainData.value = res.uploadBody;
  457. file.id = res.id;
  458. file.fileName = res.fileName;
  459. file.fileUrl = res.fileUrl;
  460. return true;
  461. };
  462. const handleMainSuccess = (response, uploadFile) => {
  463. formData.data.mainImgUrl = uploadFile.raw.fileUrl;
  464. };
  465. const submitForm = () => {
  466. submit.value.handleSubmit(() => {
  467. if (formData.data.skuSpecList && formData.data.skuSpecList.length > 0) {
  468. let type = "add";
  469. if (formData.data.id) {
  470. type = "edit";
  471. }
  472. proxy.post("/sku/" + type, formData.data).then(() => {
  473. ElMessage({
  474. message: type == "add" ? "添加成功" : "编辑成功",
  475. type: "success",
  476. });
  477. emit("clickCancel", true);
  478. });
  479. } else {
  480. return ElMessage("请添加规格");
  481. }
  482. });
  483. };
  484. const clickCancel = () => {
  485. emit("clickCancel", false);
  486. };
  487. const props = defineProps({
  488. rowData: Object,
  489. detailStatus: Boolean,
  490. });
  491. onMounted(() => {
  492. formOption.disabled = props.detailStatus;
  493. if (props.rowData && props.rowData.id) {
  494. proxy.post("/sku/detail", { id: props.rowData.id }).then((res) => {
  495. for (var text in res) {
  496. formData.data[text] = res[text];
  497. }
  498. if (!props.detailStatus) {
  499. editor.value.changeHtml(formData.data.detailText);
  500. }
  501. });
  502. }
  503. });
  504. const getStyle = (text) => {
  505. if (text) {
  506. return text.replace(/\n|\r\n/g, "<br>");
  507. } else {
  508. return "";
  509. }
  510. };
  511. const rowIndex = ref(null);
  512. const openBOM = ref(false);
  513. const handleOpen = (index) => {
  514. rowIndex.value = index;
  515. openBOM.value = true;
  516. };
  517. const selectBOM = (item) => {
  518. if (item.id) {
  519. formData.data.skuSpecList[rowIndex.value].bomSpecId = item.id;
  520. formData.data.skuSpecList[rowIndex.value].bomSpecName = item.name;
  521. formData.data.skuSpecList[rowIndex.value].length = item.length;
  522. formData.data.skuSpecList[rowIndex.value].width = item.width;
  523. formData.data.skuSpecList[rowIndex.value].height = item.height;
  524. formData.data.skuSpecList[rowIndex.value].netWeight = item.netWeight;
  525. ElMessage({ message: "选择完成", type: "success" });
  526. openBOM.value = false;
  527. }
  528. };
  529. const clickRemoveBOM = (index) => {
  530. formData.data.skuSpecList[index].bomSpecId = "";
  531. formData.data.skuSpecList[index].bomSpecName = "";
  532. };
  533. const drawingFileIndex = ref(0);
  534. const openDrawingFile = ref(false);
  535. const clickDrawingFile = (index) => {
  536. drawingFileIndex.value = index;
  537. openDrawingFile.value = true;
  538. };
  539. const selectPic = (row) => {
  540. console.log(row);
  541. ElMessage({ message: "选择完成", type: "success" });
  542. openDrawingFile.value = false;
  543. };
  544. </script>
  545. <style lang="scss" scoped>
  546. .avatar-uploader .avatar {
  547. width: 50px;
  548. height: 50px;
  549. display: block;
  550. background-color: black;
  551. }
  552. .avatar-uploader .el-upload {
  553. border: 1px dashed var(--el-border-color);
  554. border-radius: 6px;
  555. cursor: pointer;
  556. position: relative;
  557. overflow: hidden;
  558. transition: var(--el-transition-duration-fast);
  559. }
  560. .avatar-uploader .el-upload:hover {
  561. border-color: var(--el-color-primary);
  562. }
  563. .el-icon.avatar-uploader-icon {
  564. font-size: 28px;
  565. color: #8c939d;
  566. width: 50px;
  567. height: 50px;
  568. text-align: center;
  569. border: 1px dashed var(--el-border-color);
  570. }
  571. ::v-deep(.el-input-number .el-input__inner) {
  572. text-align: left;
  573. }
  574. .avatar-uploader-main .avatar {
  575. width: 148px;
  576. height: 148px;
  577. display: block;
  578. background-color: black;
  579. }
  580. .avatar-uploader-main .el-upload {
  581. border: 1px dashed var(--el-border-color);
  582. border-radius: 6px;
  583. cursor: pointer;
  584. position: relative;
  585. overflow: hidden;
  586. transition: var(--el-transition-duration-fast);
  587. }
  588. .avatar-uploader-main .el-upload:hover {
  589. border-color: var(--el-color-primary);
  590. }
  591. .el-icon.avatar-uploader-main-icon {
  592. font-size: 28px;
  593. color: #8c939d;
  594. width: 148px;
  595. height: 148px;
  596. text-align: center;
  597. border: 1px dashed var(--el-border-color);
  598. }
  599. :deep(.el-dialog) {
  600. margin-top: 10px !important;
  601. margin-bottom: 10px !important;
  602. }
  603. </style>