save.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. <template>
  2. <div class="container">
  3. <el-tabs v-model="activeName" class="tabs" :before-leave="confirmLeave">
  4. <!-- 基础设置 -->
  5. <el-tab-pane label="基础设置" name="base">
  6. <el-form ref="base" :model="baseForm" :rules="rules" label-width="100px" style="width: 95%">
  7. <el-form-item label="商品名称" prop="name">
  8. <el-input v-model="baseForm.name" placeholder="请输入商品名称"/>
  9. </el-form-item>
  10. <el-form-item label="卖点">
  11. <el-input v-model="baseForm.sellPoint" placeholder="请输入卖点"/>
  12. </el-form-item>
  13. <el-form-item label="分类id" prop="categoryIds">
  14. <el-cascader
  15. v-model="baseForm.categoryIds"
  16. placeholder="请输入分类id"
  17. style="width: 100%"
  18. :options="categoryList"
  19. :props="propName"
  20. clearable
  21. ></el-cascader>
  22. </el-form-item>
  23. <el-form-item label="商品轮播图" prop="picUrls">
  24. <ImageUpload v-model="baseForm.picUrls" :value="baseForm.picUrls" :limit="10"/>
  25. </el-form-item>
  26. <el-form-item label="排序字段">
  27. <el-input v-model="baseForm.sort" placeholder="请输入排序字段" oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"/>
  28. </el-form-item>
  29. </el-form>
  30. </el-tab-pane>
  31. <!-- 价格库存 -->
  32. <el-tab-pane label="价格库存" name="rates" class="rates">
  33. <el-form ref="rates" :model="ratesForm" :rules="rules">
  34. <el-form-item label="商品规格">
  35. <el-radio-group v-model="ratesForm.spec" @change="changeRadio">
  36. <el-radio :label="1">单规格</el-radio>
  37. <el-radio :label="2">多规格</el-radio>
  38. </el-radio-group>
  39. </el-form-item>
  40. <!-- 动态添加规格属性 -->
  41. <div v-show="ratesForm.spec === 2">
  42. <div
  43. v-for="(specs, index) in dynamicSpec"
  44. :key="index"
  45. class="dynamic-spec"
  46. >
  47. <!-- 删除按钮 -->
  48. <el-button
  49. type="danger"
  50. icon="el-icon-delete"
  51. circle
  52. class="spec-delete"
  53. @click="removeSpec(index)"
  54. ></el-button>
  55. <div class="spec-header">
  56. 规格项:
  57. <el-select v-model="specs.specId" filterable placeholder="请选择" @change="changeSpec">
  58. <el-option
  59. v-for="item in propertyPageList"
  60. :key="item.id"
  61. :label="item.name"
  62. :value="item.id">
  63. </el-option>
  64. </el-select>
  65. </div>
  66. <div class="spec-values">
  67. <template v-for="(v, i) in specs.specValue">
  68. <el-input v-model="v.name" class="spec-value" :key="i" disabled/>
  69. </template>
  70. </div>
  71. </div>
  72. <el-button type="primary" @click="dynamicSpec.push({specValue: []}); ratesForm.rates = []"
  73. >添加规格项目
  74. </el-button
  75. >
  76. </div>
  77. <!-- 规格明细 -->
  78. <el-form-item label="规格明细">
  79. <el-table :data="ratesForm.rates" border style="width: 100%" ref="ratesTable">
  80. <template v-if="ratesForm.spec == 2">
  81. <el-table-column :key="index" v-for="(item, index) in dynamicSpec.filter(v=>v.specName != undefined)"
  82. :label="item.specName">
  83. <template slot-scope="scope">
  84. <el-input
  85. v-if="scope.row.spec"
  86. v-model="scope.row.spec[index]"
  87. disabled
  88. ></el-input>
  89. </template>
  90. </el-table-column>
  91. </template>
  92. <el-table-column label="规格图片" width="120px" :render-header="addRedStar" key="90">
  93. <template slot-scope="scope">
  94. <ImageUpload v-model="scope.row.picUrl" :limit="1" :isShowTip="false"
  95. style="width: 100px; height: 50px"/>
  96. </template>
  97. </el-table-column>
  98. <template v-if="ratesForm.spec == 2">
  99. <el-table-column label="sku名称" :render-header="addRedStar" key="91">
  100. <template slot-scope="scope">
  101. <el-form-item :prop="'rates.'+ scope.$index + '.name'" :rules="[{required: true, trigger: 'change'}]">
  102. <el-input
  103. v-model="scope.row.name"
  104. ></el-input>
  105. </el-form-item>
  106. </template>
  107. </el-table-column>
  108. </template>
  109. <el-table-column label="市场价(元)" :render-header="addRedStar" key="92">
  110. <template slot-scope="scope">
  111. <el-form-item :prop="'rates.'+ scope.$index + '.marketPrice'" :rules="[{required: true, trigger: 'change'}]">
  112. <el-input
  113. v-model="scope.row.marketPrice"
  114. oninput="value= value.match(/\d+(\.\d{0,2})?/) ? value.match(/\d+(\.\d{0,2})?/)[0] : ''"
  115. ></el-input>
  116. </el-form-item>
  117. </template>
  118. </el-table-column>
  119. <el-table-column label="销售价(元)" :render-header="addRedStar" key="93">
  120. <template slot-scope="scope">
  121. <el-form-item :prop="'rates.'+ scope.$index + '.price'" :rules="[{required: true, trigger: 'change'}]">
  122. <el-input v-model="scope.row.price" oninput="value= value.match(/\d+(\.\d{0,2})?/) ? value.match(/\d+(\.\d{0,2})?/)[0] : ''"></el-input>
  123. </el-form-item>
  124. </template>
  125. </el-table-column>
  126. <el-table-column label="成本价" :render-header="addRedStar" key="94">
  127. <template slot-scope="scope">
  128. <el-form-item :prop="'rates.'+ scope.$index + '.costPrice'" :rules="[{required: true, trigger: 'change'}]">
  129. <el-input
  130. v-model="scope.row.costPrice"
  131. oninput="value= value.match(/\d+(\.\d{0,2})?/) ? value.match(/\d+(\.\d{0,2})?/)[0] : ''"
  132. ></el-input>
  133. </el-form-item>
  134. </template>
  135. </el-table-column>
  136. <el-table-column label="库存" :render-header="addRedStar" key="95">
  137. <template slot-scope="scope">
  138. <el-form-item :prop="'rates.'+ scope.$index + '.stock'" :rules="[{required: true, trigger: 'change'}]">
  139. <el-input v-model="scope.row.stock" oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"></el-input>
  140. </el-form-item>
  141. </template>
  142. </el-table-column>
  143. <el-table-column label="预警库存" key="96">
  144. <template slot-scope="scope">
  145. <el-input v-model="scope.row.warnStock" oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"></el-input>
  146. </template>
  147. </el-table-column>
  148. <el-table-column label="体积" key="97">
  149. <template slot-scope="scope">
  150. <el-input v-model="scope.row.volume" ></el-input>
  151. </template>
  152. </el-table-column>
  153. <el-table-column label="重量" key="98">
  154. <template slot-scope="scope">
  155. <el-input v-model="scope.row.weight" ></el-input>
  156. </template>
  157. </el-table-column>
  158. <el-table-column label="条码" key="99">
  159. <template slot-scope="scope">
  160. <el-input v-model="scope.row.barCode"></el-input>
  161. </template>
  162. </el-table-column>
  163. <template v-if="ratesForm.spec == 2">
  164. <el-table-column
  165. fixed="right"
  166. label="操作"
  167. width="50"
  168. key="100">
  169. <template slot-scope="scope">
  170. <el-button @click="scope.row.status = 1" type="text" size="small" v-show="scope.row.status == undefined || scope.row.status == 0 ">禁用</el-button>
  171. <el-button @click="scope.row.status = 0" type="text" size="small" v-show="scope.row.status == 1">启用</el-button>
  172. </template>
  173. </el-table-column>
  174. </template>
  175. </el-table>
  176. </el-form-item>
  177. </el-form>
  178. </el-tab-pane>
  179. <!-- 商品详情 -->
  180. <el-tab-pane label="商品描述" name="third">
  181. <el-form ref="third" :model="baseForm" :rules="rules">
  182. <el-form-item prop="description">
  183. <editor v-model="baseForm.description" :min-height="380"/>
  184. </el-form-item>
  185. </el-form>
  186. </el-tab-pane>
  187. <!-- 销售设置 -->
  188. <el-tab-pane label="销售设置" name="fourth">
  189. <el-form ref="fourth" :model="baseForm" :rules="rules" label-width="100px" style="width: 95%">
  190. <el-form-item label="虚拟销量" prop="virtualSalesCount">
  191. <el-input v-model="baseForm.virtualSalesCount" placeholder="请输入虚拟销量" oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"/>
  192. </el-form-item>
  193. <el-form-item label="是否展示库存" prop="showStock">
  194. <el-radio-group v-model="baseForm.showStock">
  195. <el-radio :label="true">是</el-radio>
  196. <el-radio :label="false">否</el-radio>
  197. </el-radio-group>
  198. </el-form-item>
  199. <el-form-item label="商品状态" prop="status">
  200. <el-radio-group v-model="baseForm.status">
  201. <el-radio :label="0">上架</el-radio>
  202. <el-radio :label="1">下架</el-radio>
  203. </el-radio-group>
  204. </el-form-item>
  205. </el-form>
  206. </el-tab-pane>
  207. </el-tabs>
  208. <div class="buttons">
  209. <el-button type="info" round @click="cancel">取消</el-button>
  210. <el-button type="success" round @click="submit">确认</el-button>
  211. </div>
  212. </div>
  213. </template>
  214. <script>
  215. import {getProductCategoryList} from "@/api/mall/product/category";
  216. import {createSpu,} from "@/api/mall/product/spu";
  217. import {getPropertyPage,} from "@/api/mall/product/property";
  218. import Editor from "@/components/Editor";
  219. import ImageUpload from "@/components/ImageUpload";
  220. export default {
  221. components: {
  222. Editor,
  223. ImageUpload
  224. },
  225. data() {
  226. return {
  227. activeName: "base",
  228. propName: {
  229. checkStrictly: true,
  230. label: "name",
  231. value: "id",
  232. },
  233. // 基础设置
  234. baseForm: {
  235. name: null,
  236. sellPoint: null,
  237. categoryIds: null,
  238. sort: null,
  239. description: null,
  240. picUrls: null,
  241. status: 0,
  242. virtualSalesCount: 0,
  243. showStock: true,
  244. },
  245. categoryList: [],
  246. // 价格库存
  247. ratesForm: {
  248. spec: 1,
  249. // 规格明细
  250. rates: [{}]
  251. },
  252. dynamicSpec: [
  253. // {
  254. // specId: 86,
  255. // specName: "颜色",
  256. // specValue:[{
  257. // name: "红色",
  258. // id: 225,
  259. // }]
  260. // },
  261. ],
  262. propertyPageList: [],
  263. specValue: null,
  264. // 表单校验
  265. rules: {
  266. name:[{required: true, message: "商品名称不能为空", trigger: "blur"},],
  267. description: [{required: true, message: "描述不能为空", trigger: "blur"},],
  268. categoryIds: [{required: true, message: "分类id不能为空", trigger: "blur"},],
  269. status: [{required: true, message: "商品状态不能为空", trigger: "blur"}],
  270. picUrls: [{required: true, message: "商品轮播图地址不能为空", trigger: "blur"}],
  271. virtualSalesCount: [{required: true, message: "虚拟销量不能为空", trigger: "blur"}],
  272. },
  273. };
  274. },
  275. created() {
  276. this.getListCategory();
  277. this.getPropertyPageList();
  278. },
  279. methods: {
  280. removeSpec(index){
  281. this.dynamicSpec.splice(index, 1);
  282. this.changeRadio()
  283. },
  284. async confirmLeave(active, old){
  285. await this.$refs[old].validate((valid) => {
  286. console.log(valid)
  287. if (!valid) {
  288. return reject();
  289. }
  290. });
  291. },
  292. // 必选标识
  293. addRedStar(h, { column }) {
  294. return [
  295. h('span', { style: 'color: #F56C6C' }, '*'),
  296. h('span', ' ' + column.label)
  297. ];
  298. },
  299. changeRadio() {
  300. this.$refs.ratesTable.doLayout();
  301. if (this.ratesForm.spec == 1) {
  302. this.ratesForm.rates = [{}]
  303. } else {
  304. this.ratesForm.rates = []
  305. if (this.dynamicSpec.length > 0) {
  306. console.log( this.dynamicSpec)
  307. this.buildRatesFormRates()
  308. }
  309. }
  310. },
  311. // 构建规格明细笛卡尔积
  312. buildRatesFormRates() {
  313. let rates = [];
  314. this.dynamicSpec.map(v => v.specValue.map(m => m.name))
  315. .reduce((last, current) => {
  316. const array = [];
  317. last.forEach(par1 => {
  318. current.forEach(par2 => {
  319. let v
  320. if (par1 instanceof Array) {
  321. v = par1.concat(par2)
  322. } else {
  323. v = [par1, par2];
  324. }
  325. array.push(v)
  326. });
  327. });
  328. return array;
  329. })
  330. .forEach(v => {
  331. rates.push({spec: v, status: 0, name: Array.of(v).join()})
  332. });
  333. this.ratesForm.rates = rates
  334. },
  335. /** 查询分类 */
  336. getListCategory() {
  337. // 执行查询
  338. getProductCategoryList().then((response) => {
  339. this.categoryList = this.handleTree(response.data, "id", "parentId");
  340. });
  341. },
  342. cancel() {
  343. this.$emit("closeDialog");
  344. },
  345. submit() {
  346. this.$refs[this.activeName].validate((valid) => {
  347. if (!valid) {
  348. return;
  349. }
  350. let rates = this.ratesForm.rates;
  351. // 动态规格调整字段
  352. if (this.ratesForm.spec == 2) {
  353. rates.forEach(r => {
  354. let properties = []
  355. Array.of(r.spec).forEach(s => {
  356. Array.of(s).forEach((v, i) => {
  357. console.log(this.dynamicSpec, r, s, v, i)
  358. let specValue = this.dynamicSpec[i].specValue.find(o => o.name == v);
  359. console.log(specValue)
  360. let propertie = {};
  361. propertie.propertyId = this.dynamicSpec[i].specId;
  362. propertie.valueId = specValue.id;
  363. properties.push(propertie);
  364. })
  365. })
  366. r.properties = properties;
  367. })
  368. }else{
  369. rates[0].name = this.baseForm.name;
  370. rates[0].status = this.baseForm.status;
  371. }
  372. console.log(rates)
  373. let form = this.baseForm
  374. if(form.picUrls instanceof Array){
  375. form.picUrls = form.picUrls.flatMap(m=>m.split(','))
  376. }else if(form.picUrls.split(',') instanceof Array){
  377. form.picUrls = form.picUrls.split(',').flatMap(m=>m.split(','))
  378. }else{
  379. form.picUrls = Array.of(form.picUrls)
  380. }
  381. form.skus = rates;
  382. form.specType = this.ratesForm.spec;
  383. form.categoryId = form.categoryIds[this.baseForm.categoryIds.length - 1];
  384. createSpu(form).then((response) => {
  385. console.log(response)
  386. this.$modal.msgSuccess("新增成功");
  387. this.$emit("closeDialog");
  388. }).catch((err) => {
  389. console.log(this.baseForm.picUrls)
  390. });
  391. });
  392. },
  393. /** 查询规格 */
  394. getPropertyPageList() {
  395. // 执行查询
  396. getPropertyPage().then((response) => {
  397. this.propertyPageList = response.data.list;
  398. });
  399. },
  400. changeSpec(val) {
  401. let obj = this.propertyPageList.find(o => o.id == val);
  402. let dynamicSpec = this.dynamicSpec;
  403. let spec = dynamicSpec.find(o => o.specId == val)
  404. spec.specId = obj.id;
  405. spec.specName = obj.name;
  406. spec.specValue = obj.propertyValueList;
  407. this.dynamicSpec = dynamicSpec;
  408. this.buildRatesFormRates();
  409. }
  410. },
  411. };
  412. </script>
  413. <style lang="scss">
  414. .spec-dialog {
  415. width: 400px;
  416. height: 300px;
  417. }
  418. .dynamic-spec {
  419. background-color: #f2f2f2;
  420. width: 85%;
  421. margin: auto;
  422. margin-bottom: 10px;
  423. .spec-header {
  424. padding: 30px;
  425. padding-bottom: 20px;
  426. .spec-name {
  427. display: inline;
  428. input {
  429. width: 30%;
  430. }
  431. }
  432. }
  433. .spec-values {
  434. width: 84%;
  435. padding: 25px;
  436. margin: auto;
  437. padding-top: 5px;
  438. .spec-value {
  439. display: inline-block;
  440. margin-right: 10px;
  441. margin-bottom: 10px;
  442. width: 13%;
  443. }
  444. }
  445. .spec-delete {
  446. float: right;
  447. margin-top: 10px;
  448. margin-right: 10px;
  449. }
  450. }
  451. .tabs {
  452. height: 500px;
  453. border-bottom: 2px solid #f2f2f2;
  454. .el-tab-pane {
  455. height: 445px;
  456. overflow-y: auto;
  457. }
  458. }
  459. // 库存价格图片样式修改
  460. .rates {
  461. .component-upload-image {
  462. margin: auto;
  463. }
  464. .el-upload--picture-card {
  465. width: 100px;
  466. height: 50px;
  467. line-height: 60px;
  468. margin: auto;
  469. }
  470. .el-upload-list__item {
  471. width: 100px !important;
  472. height: 50px !important;
  473. }
  474. }
  475. .buttons {
  476. margin-top: 20px;
  477. height: 36px;
  478. button {
  479. float: right;
  480. margin-left: 15px;
  481. }
  482. }
  483. </style>