addProduct.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. <template>
  2. <view class="product-add-wrapper">
  3. <view class="headers">
  4. <headersVue headerTitle="添加商品" @headerJump="headerJump"></headersVue>
  5. <view class="headers-title">
  6. <view class="content"> 该功能仅支持单规格商品快速添加,可在PC端编辑 </view>
  7. </view>
  8. </view>
  9. <view class="content-wrrapper">
  10. <uploadVue
  11. formItemTitle="商品封面"
  12. :maxLimit="1"
  13. @changeFiles="changeFilesfengmian"
  14. ></uploadVue>
  15. <uploadVue
  16. formItemTitle="商品轮播图"
  17. @changeFiles="changeFileslunbotu"
  18. ></uploadVue>
  19. <view class="form-box">
  20. <view class="form-item-box">
  21. <view class="form-box-txt">商户商品分类</view>
  22. <view
  23. class="form-box--arae"
  24. @click="handleClick"
  25. :class="[classification.name == '' ? 'classification' : '']"
  26. >
  27. {{ classification.name || "请选择商品分类" }}
  28. </view>
  29. <uni-icons
  30. type="right"
  31. size="26"
  32. class="icon-boxx"
  33. color="#333"
  34. ></uni-icons>
  35. </view>
  36. <view class="form-item-box">
  37. <view class="form-box-txt">平台商品分类</view>
  38. <view
  39. class="form-box--arae"
  40. @click="handleClickpingtai"
  41. :class="[pingtaifeilei.name == '' ? 'classification' : '']"
  42. >
  43. {{ pingtaifeilei.name || "平台商品分类" }}
  44. </view>
  45. <uni-icons
  46. type="right"
  47. size="26"
  48. class="icon-boxx"
  49. color="#333"
  50. ></uni-icons>
  51. </view>
  52. <view class="form-item-box">
  53. <view class="form-box-txt mustBox">商品名称</view>
  54. <view class="form-box--arae">
  55. <input
  56. type="text"
  57. v-model="formData.store_name"
  58. placeholder="请输入商品名称"
  59. :adjust-position="false"
  60. class="placeholder-class"
  61. />
  62. </view>
  63. </view>
  64. <view class="form-item-box">
  65. <view class="form-box-txt mustBox">商品单位</view>
  66. <view class="form-box--arae">
  67. <input
  68. type="text"
  69. v-model="formData.unit_name"
  70. placeholder="请输入单位"
  71. :adjust-position="false"
  72. class="placeholder-class"
  73. />
  74. </view>
  75. </view>
  76. <view class="form-item-box">
  77. <view class="form-box-txt">品牌选择</view>
  78. <view class="form-box--arae">
  79. <uni-data-select
  80. v-model="formData.brand_id"
  81. placeholder="请选择品牌"
  82. :localdata="brandData"
  83. :clear="true"
  84. ></uni-data-select>
  85. </view>
  86. </view>
  87. <view class="form-item-box">
  88. <view class="form-box-txt mustBox">运费模板</view>
  89. <view class="form-box--arae">
  90. <uni-data-select
  91. v-model="formData.temp_id"
  92. placeholder="请选择运费模板"
  93. :localdata="shippingList"
  94. :clear="true"
  95. ></uni-data-select>
  96. </view>
  97. </view>
  98. <view v-if="shippingList.length == 0" class="tips-messge">运费模板为空,请在后台添加运费模板</view>
  99. </view>
  100. <view
  101. class="form-box mr-t"
  102. v-for="(item, index) in attrValue"
  103. :key="index"
  104. >
  105. <view
  106. class="form-item-box"
  107. v-for="(its, idx) in item.modules"
  108. :key="idx"
  109. v-if="its.isshow"
  110. >
  111. <template v-if="its.isshow">
  112. <view class="form-box-txt" :class="[its.ismust ? 'mustBox' : '']">{{
  113. its.title
  114. }}</view>
  115. <view class="form-box--arae">
  116. <input
  117. v-if="its.form_type == 'input'"
  118. :type="its.type"
  119. v-model="item.modules[idx].value"
  120. :adjust-position="false"
  121. :placeholder="`请输入${its.title}`"
  122. class="placeholder-class"
  123. />
  124. </view>
  125. </template>
  126. </view>
  127. </view>
  128. <view class="business-box pb15">
  129. <view class="business-box-txt">商品简介 </view>
  130. <view class="business-box--arae">
  131. <textarea
  132. @blur="bindTextAreaBlur"
  133. v-model="formData.store_info"
  134. placeholder="输入商品简介"
  135. :maxlength="-1"
  136. :adjust-position="false"
  137. auto-height
  138. class="araebox"
  139. />
  140. </view>
  141. </view>
  142. <view class="business-box pb15">
  143. <view class="business-box-txt">商品详情 </view>
  144. <customEditor :value="formData.content" @input="changeTextHandle"/>
  145. </view>
  146. </view>
  147. <view class="btn-list">
  148. <!-- <view class="btn quxiao"> 保存草稿 </view> -->
  149. <view class="btn primary" @click="handleSubmit"> 发布商品 </view>
  150. </view>
  151. <cascadePicker
  152. ref="picker"
  153. :title="cascadePicker.title"
  154. :layer="cascadePicker.layer"
  155. :data="cascadePicker.data"
  156. @callback="pickerCallback"
  157. />
  158. <cascadePicker
  159. ref="pickerpingtai"
  160. :title="cascadepintaiPicker.title"
  161. :layer="cascadepintaiPicker.layer"
  162. :data="cascadepintaiPicker.data"
  163. @callback="pickerCallbackpintai"
  164. />
  165. </view>
  166. </template>
  167. <script>
  168. //添加商品
  169. import headersVue from "../components/headers/headers.vue";
  170. import uploadVue from "./components/upload.vue";
  171. import cascadePicker from "../components/cascade-picker.vue";
  172. import { reNameKeys } from "@/utils/index";
  173. import customEditor from "@/components/editor.vue";
  174. import {
  175. getGoodsClass,
  176. getBrandSelect,
  177. shippingListApi,
  178. getGoodsClassCategory,
  179. uploadFile,
  180. addProduct,
  181. } from "@/commit/product.js";
  182. export default {
  183. components: {
  184. headersVue,
  185. uploadVue,
  186. cascadePicker,
  187. customEditor,
  188. },
  189. data() {
  190. return {
  191. cascadeData: [], //商户商品分类
  192. pingtaifeiData: [], //平台商品分类:
  193. cascadePicker: {
  194. title: "选择商户商品分类",
  195. layer: null,
  196. data: [],
  197. },
  198. cascadepintaiPicker: {
  199. title: "选择平台商品分类",
  200. layer: null,
  201. data: [],
  202. },
  203. classification: {
  204. //商户商品分类
  205. name: "",
  206. id: "",
  207. },
  208. pingtaifeilei: {
  209. //平台商品分类:
  210. name: "",
  211. id: "",
  212. },
  213. formData: {
  214. store_name: "", //商品名称
  215. mer_cate_id: [], //商户商品分类:
  216. cate_id: "", //平台商品分类:
  217. brand_id: "", //品牌选择:
  218. keyword: "", //商品关键字:
  219. unit_name: "", //单位:
  220. store_info: "", //商品简介:
  221. image: "", //商品封面图:
  222. temp_id: "", //运费模板:
  223. slider_image: [], //商品轮播图:
  224. couponData: [], //优惠券:
  225. content: "", //商品详情:
  226. sort: 0, //排序:
  227. extension_type: "0", //是否单独设置:
  228. is_good: 0, //商品推荐:0 1
  229. is_gift_bag: 0, //是否包邮:0 1
  230. give_coupon_ids: [], //优惠券:
  231. // checkboxGroup: [], //商品推荐:0单规格 1 多规格
  232. stock_num: "", //商品库存
  233. unit_name: "", //单位
  234. share_num: "",
  235. shares_num: "",
  236. share_is: "0",
  237. spec_type: "0",
  238. type: 1,
  239. unit_name: "",
  240. attr: [],
  241. commission: "",
  242. pension: "",
  243. pension_is: "0",
  244. pension_num: "",
  245. },
  246. selectData: [],
  247. brandData: [],
  248. shippingList: [], //运费模板
  249. attrValue: [
  250. {
  251. modules: [
  252. {
  253. title: "商品售价",
  254. props: "price",
  255. value: "",
  256. type: "digit",
  257. ismust: true,
  258. form_type: "input",
  259. isshow: true,
  260. },
  261. {
  262. title: "商品成本价",
  263. value: "",
  264. props: "cost",
  265. type: "digit",
  266. ismust: true,
  267. form_type: "input",
  268. isshow: true,
  269. },
  270. {
  271. title: "商品原价",
  272. value: "",
  273. props: "ot_price",
  274. type: "digit",
  275. ismust: true,
  276. form_type: "input",
  277. isshow: true,
  278. },
  279. {
  280. title: "商品库存",
  281. value: "",
  282. props: "stock",
  283. type: "number",
  284. ismust: true,
  285. form_type: "input",
  286. isshow: true,
  287. },
  288. {
  289. title: "商品编号",
  290. value: "",
  291. props: "bar_code",
  292. type: "text",
  293. ismust: false,
  294. form_type: "input",
  295. isshow: true,
  296. },
  297. {
  298. title: "重量(KG)",
  299. value: "",
  300. props: "weight",
  301. type: "digit",
  302. ismust: false,
  303. form_type: "input",
  304. isshow: true,
  305. },
  306. {
  307. title: "商品体积(m³)",
  308. value: "",
  309. props: "volume",
  310. type: "digit",
  311. ismust: false,
  312. form_type: "input",
  313. isshow: true,
  314. },
  315. {
  316. title: "商品图片",
  317. value: "",
  318. props: "image",
  319. type: "",
  320. ismust: false,
  321. form_type: "file",
  322. isshow: false,
  323. },
  324. ],
  325. },
  326. ],
  327. };
  328. },
  329. async onLoad() {
  330. await this.initData();
  331. },
  332. onShow() {},
  333. methods: {
  334. async initData() {
  335. await this.getBrandSelectData();
  336. await this.getShippingListData();
  337. await this.getGoodsClassData();
  338. await this.getGoodsClassCategoryData();
  339. },
  340. async getGoodsClassData() {
  341. try {
  342. const res = await getGoodsClass();
  343. if (res.data.status == 200) {
  344. const data = res.data.data || [];
  345. this.cascadeData = reNameKeys(data);
  346. }
  347. } catch (error) {}
  348. },
  349. async getBrandSelectData() {
  350. // { value: 0, text: "篮球" },
  351. try {
  352. const res = await getBrandSelect();
  353. if (res.data.status == 200) {
  354. const data = res.data.data || [];
  355. console.log(data, "品牌数据");
  356. this.brandData = data.map((item) => {
  357. return {
  358. value: item.brand_id || "",
  359. text: item.brand_name || "",
  360. ...item,
  361. };
  362. });
  363. }
  364. } catch (error) {}
  365. },
  366. async getShippingListData() {
  367. try {
  368. const res = await shippingListApi();
  369. if (res.data.status == 200) {
  370. const data = res.data.data || [];
  371. console.log(data, "运费模板");
  372. this.shippingList = data.map((item) => {
  373. return {
  374. value: item.shipping_template_id || "",
  375. text: item.name || "",
  376. // ...item,
  377. };
  378. });
  379. }
  380. } catch (error) {}
  381. },
  382. async getGoodsClassCategoryData() {
  383. try {
  384. const res = await getGoodsClassCategory();
  385. if (res.data.status == 200) {
  386. const data = res.data.data || [];
  387. this.pingtaiData = reNameKeys(data);
  388. }
  389. } catch (error) {}
  390. },
  391. headerJump() {
  392. uni.redirectTo({
  393. url: "/shop/productManagementModule/productManagement",
  394. });
  395. },
  396. // picker多级联动回调
  397. pickerCallback(e) {
  398. const { data } = e;
  399. const lastItem = data[data.length - 1];
  400. this.selectData = data || [];
  401. console.log(lastItem, "pickerCallback回調");
  402. this.formData.mer_cate_id.push(lastItem.id);
  403. this.classification = lastItem || {};
  404. },
  405. handleClick() {
  406. const _this = this;
  407. _this.cascadePicker.data = _this.cascadeData;
  408. _this.$refs.picker.open().then(function () {
  409. console.log("打开成功");
  410. });
  411. },
  412. handleClickpingtai() {
  413. //pingtai
  414. const _this = this;
  415. _this.cascadepintaiPicker.data = _this.pingtaiData;
  416. _this.$refs.pickerpingtai.open().then(function () {
  417. console.log("打开成功");
  418. });
  419. },
  420. pickerCallbackpintai(e) {
  421. const { data } = e;
  422. const lastItem = data[data.length - 1];
  423. this.selectData = data || [];
  424. console.log(lastItem, "pickerCallbackpintai");
  425. this.pingtaifeilei = lastItem || {};
  426. },
  427. bindTextAreaBlur(e) {
  428. console.log(e.detail.value);
  429. },
  430. async handleSubmit() {
  431. if (this.formData.image == "") {
  432. uni.showToast({
  433. title: "请上传商品封面图",
  434. icon: "none",
  435. });
  436. return;
  437. }
  438. if (this.formData.slider_image.length == 0) {
  439. uni.showToast({
  440. title: "请上传商品轮播图",
  441. icon: "none",
  442. });
  443. return;
  444. }
  445. if (this.formData.store_name == "") {
  446. uni.showToast({
  447. title: "请填写商品名称",
  448. icon: "none",
  449. });
  450. return;
  451. }
  452. if (this.formData.unit_name == "") {
  453. uni.showToast({
  454. title: "请填写商品单位",
  455. icon: "none",
  456. });
  457. return;
  458. }
  459. if(!this.formData.temp_id || this.formData.temp_id == ""){
  460. uni.showToast({
  461. title: "请选择运费模板",
  462. icon: "none",
  463. });
  464. return
  465. }
  466. const dataList = (this.attrValue[0] && this.attrValue[0].modules) || [];
  467. const dataNew = dataList.map((item, index) => {
  468. return {
  469. tips: dataList[index].title,
  470. props: dataList[index].props,
  471. isEmpty:
  472. dataList[index].ismust &&
  473. (dataList[index].value == "" ||
  474. dataList[index].value == undefined ||
  475. dataList[index].value == null),
  476. value: dataList[index].value || "",
  477. };
  478. });
  479. const data = dataNew.some((item) => item.isEmpty);
  480. if (data) {
  481. uni.showToast({
  482. title: "请填写完所有必填项",
  483. icon: "none",
  484. });
  485. return;
  486. }
  487. if (this.formData.store_info == "") {
  488. uni.showToast({
  489. title: "请填写商品简介",
  490. icon: "none",
  491. });
  492. return;
  493. }
  494. let attrValueData = dataNew.map((item, index) => {
  495. let parmas = {};
  496. if ([item.props] == "image") {
  497. Object.assign(parmas, { image: this.formData.image });
  498. } else {
  499. Object.assign(parmas, { [item.props]: item.value });
  500. }
  501. return parmas;
  502. });
  503. // 使用 reduce 方法合并数组中的对象
  504. const mergedObject = attrValueData.reduce((acc, current) => {
  505. return { ...acc, ...current };
  506. }, {});
  507. // 转换为包含合并后对象的数组
  508. const result = [mergedObject];
  509. const newParams = { ...this.formData, attrValue: result };
  510. uni.showLoading({
  511. title: "数据提交中...",
  512. })
  513. try {
  514. const res = await addProduct(newParams);
  515. if(res.data.status == 200){
  516. uni.showToast({
  517. title: "数据提交成功",
  518. icon: "none",
  519. });
  520. uni.redirectTo({
  521. url:'./productManagement'
  522. })
  523. }else{
  524. uni.showToast({
  525. title: res.data.message,
  526. icon: "none",
  527. });
  528. }
  529. uni.hideLoading();
  530. uni.hideToast()
  531. } catch (error) {
  532. }finally{
  533. uni.hideLoading();
  534. }
  535. },
  536. changeFilesfengmian(data) {
  537. const pathsArr = data.fileList || [];
  538. const paths = pathsArr.map((item) => item.path).join("") || "";
  539. this.formData.image = paths;
  540. },
  541. changeFileslunbotu(data) {
  542. const pathsArr = data.fileList || [];
  543. this.formData.slider_image = [];
  544. pathsArr.forEach((item) => {
  545. this.formData.slider_image.push(item.path);
  546. });
  547. },
  548. changeTextHandle(data){
  549. // console.log(data)
  550. this.formData.content = data;
  551. }
  552. },
  553. };
  554. </script>
  555. <style>
  556. page {
  557. background-color: #f7f8fc;
  558. height: 100%;
  559. }
  560. </style>
  561. <style lang="scss" scoped>
  562. .product-add-wrapper {
  563. height: 100%;
  564. overflow: hidden;
  565. .headers {
  566. .headers-title {
  567. display: flex;
  568. align-items: center;
  569. width: 100%;
  570. background: #fff9ed;
  571. padding: 16px 20rpx;
  572. margin-top: 88rpx;
  573. .content {
  574. font-family: PingFangSC, PingFang SC;
  575. font-weight: 400;
  576. font-size: 26rpx;
  577. color: #ff6010;
  578. }
  579. }
  580. }
  581. }
  582. .content-wrrapper {
  583. height: calc(100% - 350rpx);
  584. overflow-y: auto;
  585. padding-bottom: 60rpx;
  586. .business-box {
  587. width: calc(100% - 0rpx);
  588. // border: 2rpx solid #f00;
  589. margin: 26rpx 0;
  590. display: flex;
  591. flex-direction: column;
  592. justify-content: space-between;
  593. align-items: flex-start;
  594. padding: 32rpx 32rpx 0;
  595. box-sizing: border-box;
  596. background-color: #ffffff;
  597. min-height: 250rpx;
  598. .business-box-txt {
  599. width: 100%;
  600. font-weight: 600;
  601. font-size: 30rpx;
  602. color: #333333;
  603. margin-bottom: 20rpx;
  604. &::after {
  605. content: "*";
  606. color: #f00;
  607. padding-left: 10rpx;
  608. }
  609. }
  610. .upload-boxx {
  611. padding: 19rpx 0 !important;
  612. }
  613. .business-box--arae {
  614. width: 100%;
  615. // border: 2rpx solid #f00;
  616. background: #f8f8f8;
  617. border-radius: 9rpx;
  618. min-height: 208rpx;
  619. .araebox {
  620. padding: 20rpx 18rpx 20rpx 26rpx;
  621. width: calc(100% - 46rpx);
  622. height: 100%;
  623. }
  624. }
  625. }
  626. }
  627. .btn-list {
  628. background-color: #fff;
  629. display: flex;
  630. gap: 26rpx;
  631. user-select: none;
  632. padding: 15rpx 26rpx 0rpx;
  633. height: 126rpx;
  634. .btn {
  635. flex: 1;
  636. flex-shrink: 0;
  637. height: 78rpx;
  638. border-radius: 8rpx;
  639. font-weight: 400;
  640. font-size: 30rpx;
  641. display: flex;
  642. align-items: center;
  643. justify-content: center;
  644. }
  645. .quxiao {
  646. border: 1rpx solid #999999;
  647. color: #333333;
  648. }
  649. .primary {
  650. color: #ffffff;
  651. background: #1f7cff;
  652. }
  653. }
  654. .form-box {
  655. min-height: 158rpx;
  656. background: #ffffff;
  657. // border: 2rpx solid #f00;
  658. padding: 0 32rpx;
  659. .form-item-box {
  660. display: flex;
  661. align-items: center;
  662. padding: 26rpx 0 18rpx;
  663. border-bottom: 2rpx solid #f8f8f8;
  664. position: relative;
  665. &:last-child {
  666. border-bottom: none;
  667. }
  668. .icon-boxx {
  669. position: absolute;
  670. right: -26rpx;
  671. top: 28rpx;
  672. }
  673. .form-box-txt {
  674. font-family: PingFangSC, PingFang SC;
  675. font-weight: 400;
  676. font-size: 30rpx;
  677. color: #333333;
  678. // height: 100%;
  679. line-height: 42rpx;
  680. width: 230rpx;
  681. flex-shrink: 0;
  682. }
  683. .form-box--arae {
  684. display: flex;
  685. align-items: center;
  686. justify-content: flex-end;
  687. font-weight: 400;
  688. font-size: 30rpx;
  689. color: #333333;
  690. flex: 1;
  691. padding-right: 20rpx;
  692. &.classification {
  693. color: #999;
  694. }
  695. }
  696. }
  697. }
  698. .mustBox {
  699. &::after {
  700. content: "*";
  701. color: #f00;
  702. padding-left: 10rpx;
  703. }
  704. }
  705. .placeholder-class {
  706. width: 100%;
  707. font-weight: 400;
  708. font-size: 30rpx;
  709. color: #333333;
  710. text-align: right;
  711. padding: 0rpx 0rpx 0rpx 15rpx;
  712. outline: none;
  713. border: none;
  714. background: none;
  715. /deep/.uni-input-placeholder {
  716. color: #999;
  717. }
  718. }
  719. .mr-t {
  720. margin-top: 26rpx;
  721. }
  722. .pb15 {
  723. padding-bottom: 25rpx !important;
  724. }
  725. .tips-messge{
  726. padding-bottom: 15rpx;
  727. color: #ff6010;
  728. }
  729. </style>