ouyang 3 недель назад
Родитель
Сommit
1abb0604a7

+ 398 - 15
hdApp/src/admin/goods/add.vue

@@ -29,6 +29,10 @@
           </view>
           </view>
         </tui-list-cell>
         </tui-list-cell>
 
 
+        <tui-list-cell class="line-cell category-wrap" :hover="false">
+          <view class="tui-title">使用场景</view><view class="tui-input"><block v-for="item in useCaseData" :key="item.id"><button class="admin-button-com" :class="[form.useCaseIdList.includes(item.id) ? '' : 'default']" @click="toggleUseCase(item)">{{ item.useCaseName }}</button></block></view>
+        </tui-list-cell>
+
         <tui-list-cell class="line-cell category-wrap" :hover="false">
         <tui-list-cell class="line-cell category-wrap" :hover="false">
           <view class="tui-title required">分类</view>
           <view class="tui-title required">分类</view>
           <view class="tui-input">
           <view class="tui-input">
@@ -43,8 +47,89 @@
           <view class="tui-title required">名称</view>
           <view class="tui-title required">名称</view>
           <input v-model="form.name" placeholder-class="phcolor" class="tui-input" name="name" placeholder="请输入商品名称" maxlength="50" type="text" />
           <input v-model="form.name" placeholder-class="phcolor" class="tui-input" name="name" placeholder="请输入商品名称" maxlength="50" type="text" />
         </tui-list-cell>
         </tui-list-cell>
+        <view class="spec-switch-card">
+          <view class="spec-switch-left">
+            <view class="spec-switch-icon">▦</view>
+            <view>
+              <view class="spec-switch-title">启用多规格</view>
+              <view class="spec-switch-desc">开启后可设置商品多规格属性</view>
+            </view>
+          </view>
+          <view class="spec-switch-right">
+            <switch :checked="form.specEnabled == 1" @change="toggleSpecs" color="#09C567" />
+            <text>{{ form.specEnabled == 1 ? '已开启' : '未开启' }}</text>
+          </view>
+        </view>
 
 
-        <tui-list-cell class="line-cell" :hover="false">
+        <view v-if="form.specEnabled == 1" class="spec-wrap">
+          <view v-for="(spec,index) in form.specList" :key="index" class="spec-card">
+            <view class="spec-card-head">
+              <view class="spec-card-title">规格组名称{{ index + 1 }}</view>
+              <button v-if="form.specList.length > 1" class="spec-remove" @click.stop.prevent="removeSpec(index)">删除</button>
+            </view>
+            <tui-list-cell class="line-cell spec-line" :hover="false">
+              <view class="tui-title required">规格组名称{{ index + 1 }}</view>
+              <input :value="spec.specName" @input="setSpecField(index, 'specName', $event.detail.value)" placeholder-class="phcolor" class="tui-input" placeholder="请输入规格名称" maxlength="50" type="text" />
+            </tui-list-cell>
+            <view class="spec-image-wrap">
+              <view class="module-tit">
+                <span>图片</span>
+                <span class="app-color-3">(不传则使用主商品图片,最多5张)</span>
+              </view>
+              <view class="module-det">
+                <htz-image-upload :max="5" :compress="true" :value="spec.currentImgData || []" :headers="headers"
+                @input="setSpecField(index, 'currentImgData', $event)"
+                @uploadSuccess="specImgUploadSuccess($event, index)" @imgDelete="specImgDeleteFn($event, index)" :action="getLoginInfo.imgUploadApi">
+                </htz-image-upload>
+              </view>
+              <view v-if="spec.shopImg && spec.shopImg.length > 0" class="spec-cover-row">
+                <view class="spec-cover-title">封面</view>
+                <view class="spec-cover-btns">
+                  <button class="admin-button-com" :class="[(spec.shortCover == item) ? '' : 'default']"
+                  v-for="(item, imgIndex) in spec.shopImg" :key="imgIndex"
+                  @click.stop.prevent="selSpecCoverFn(index, imgIndex)">第{{ imgIndex + 1 }}张</button>
+                </view>
+              </view>
+            </view>
+            <tui-list-cell class="line-cell spec-line" :hover="false">
+              <view class="tui-title">价格类型</view>
+              <view class="btn-group">
+                <button class="admin-button-com middle" :class="[spec.priceType == 1 ? '' : 'default']" @click.stop.prevent="setSpecPriceType(index, '1')">有价格</button>
+                <button class="admin-button-com middle" :class="[spec.priceType == 0 ? '' : 'default']" @click.stop.prevent="setSpecPriceType(index, '0')">无价格</button>
+              </view>
+            </tui-list-cell>
+            <tui-list-cell v-if="spec.priceType == 1" class="line-cell spec-line" :hover="false">
+              <view class="tui-title required">价格</view>
+              <input :value="spec.price" @input="setSpecField(index, 'price', $event.detail.value)" placeholder-class="phcolor" class="tui-input" placeholder="请输入价格" maxlength="10" type="digit" />
+            </tui-list-cell>
+            <tui-list-cell class="line-cell spec-line" :hover="false">
+              <view class="tui-title">库存</view>
+              <view class="btn-group">
+                <button class="admin-button-com middle" :class="[spec.stockSet == 0 ? '' : 'default']" @click.stop.prevent="setSpecStockSet(index, '0')">管够</button>
+                <button class="admin-button-com middle" :class="[spec.stockSet == 1 ? '' : 'default']" @click.stop.prevent="setSpecStockSet(index, '1')">自定义</button>
+              </view>
+            </tui-list-cell>
+            <tui-list-cell v-if="spec.stockSet == 1" class="line-cell spec-line" :hover="false">
+              <view class="tui-title">库存</view>
+              <input :value="spec.stock" @input="setSpecField(index, 'stock', $event.detail.value)" placeholder-class="phcolor" class="tui-input" placeholder="请输入库存数量" maxlength="10" type="number" />
+            </tui-list-cell>
+            <tui-list-cell class="line-cell spec-line" :hover="false">
+              <view class="tui-title">用花支数</view>
+              <input :value="spec.flowerNum" @input="setSpecField(index, 'flowerNum', $event.detail.value)" placeholder-class="phcolor" class="tui-input" placeholder="用于判断花束大小" maxlength="5" type="number" />
+            </tui-list-cell>
+            <tui-list-cell class="line-cell spec-line" :hover="false">
+              <view class="tui-title">重量</view>
+              <input :value="spec.weight" @input="setSpecField(index, 'weight', $event.detail.value)" placeholder-class="phcolor" class="tui-input" placeholder="公斤,可输入小数" maxlength="10" type="digit" />
+            </tui-list-cell>
+            <tui-list-cell class="line-cell spec-line" :hover="false">
+              <view class="tui-title">已售数量</view>
+              <input :value="spec.sold" @input="setSpecField(index, 'sold', $event.detail.value)" placeholder-class="phcolor" class="tui-input" placeholder="客户端展示:已售数量+实售数量" maxlength="10" type="number" />
+            </tui-list-cell>
+          </view>
+          <button class="spec-add-btn" @click.stop.prevent="addSpec">新增规格组</button>
+        </view>
+
+        <tui-list-cell v-if="form.specEnabled != 1" class="line-cell" :hover="false">
           <view class="tui-title">价格类型</view>
           <view class="tui-title">价格类型</view>
           <view class="btn-group">
           <view class="btn-group">
             <button class="admin-button-com middle" :class="[form.priceType === '1' ? '' : 'default']" @click="setPriceType('1')">有价格</button>
             <button class="admin-button-com middle" :class="[form.priceType === '1' ? '' : 'default']" @click="setPriceType('1')">有价格</button>
@@ -52,12 +137,12 @@
           </view>
           </view>
         </tui-list-cell>
         </tui-list-cell>
 
 
-        <tui-list-cell v-if="form.priceType === '1'" class="line-cell" :hover="false">
+        <tui-list-cell v-if="form.specEnabled != 1 && form.priceType === '1'" class="line-cell" :hover="false">
           <view class="tui-title required">价格</view>
           <view class="tui-title required">价格</view>
           <input v-model="form.price" placeholder-class="phcolor" class="tui-input" name="price" placeholder="请输入价格" maxlength="10" type="digit" />
           <input v-model="form.price" placeholder-class="phcolor" class="tui-input" name="price" placeholder="请输入价格" maxlength="10" type="digit" />
         </tui-list-cell>
         </tui-list-cell>
 
 
-        <tui-list-cell class="line-cell" :hover="false">
+        <tui-list-cell v-if="form.specEnabled != 1" class="line-cell" :hover="false">
           <view class="tui-title">库存</view>
           <view class="tui-title">库存</view>
           <view class="btn-group">
           <view class="btn-group">
             <button class="admin-button-com middle" :class="[form.stockSet === '0' ? '' : 'default']" @click="setstockSet('0')">管够</button>
             <button class="admin-button-com middle" :class="[form.stockSet === '0' ? '' : 'default']" @click="setstockSet('0')">管够</button>
@@ -65,22 +150,22 @@
           </view>
           </view>
         </tui-list-cell>
         </tui-list-cell>
 
 
-        <tui-list-cell v-if="form.stockSet === '1'" class="line-cell" :hover="false">
+        <tui-list-cell v-if="form.specEnabled != 1 && form.stockSet === '1'" class="line-cell" :hover="false">
           <view class="tui-title">库存</view>
           <view class="tui-title">库存</view>
           <input v-model="form.stock" placeholder-class="phcolor" @focus="onStockFocus" @blur="onStockBlur" class="tui-input" name="stock" placeholder="请输入库存数量" maxlength="10" type="number" />
           <input v-model="form.stock" placeholder-class="phcolor" @focus="onStockFocus" @blur="onStockBlur" class="tui-input" name="stock" placeholder="请输入库存数量" maxlength="10" type="number" />
         </tui-list-cell>
         </tui-list-cell>
 
 
-        <tui-list-cell class="line-cell" :hover="false">
+        <tui-list-cell v-if="form.specEnabled != 1" class="line-cell" :hover="false">
           <view class="tui-title">用花支数</view>
           <view class="tui-title">用花支数</view>
           <input v-model="form.flowerNum" placeholder-class="phcolor" @focus="onFlowerNumFocus" @blur="onFlowerNumBlur" class="tui-input" name="flowerNum" placeholder="用于判断花束大小" maxlength="5" type="number" />
           <input v-model="form.flowerNum" placeholder-class="phcolor" @focus="onFlowerNumFocus" @blur="onFlowerNumBlur" class="tui-input" name="flowerNum" placeholder="用于判断花束大小" maxlength="5" type="number" />
         </tui-list-cell>
         </tui-list-cell>
 
 
-        <tui-list-cell class="line-cell" :hover="false">
+        <tui-list-cell v-if="form.specEnabled != 1" class="line-cell" :hover="false">
           <view class="tui-title">重量</view>
           <view class="tui-title">重量</view>
           <input v-model="form.weight" placeholder-class="phcolor" @focus="onWeightFocus" @blur="onWeightBlur" class="tui-input" name="weight" placeholder="公斤,可输入小数" maxlength="10" type="digit" />
           <input v-model="form.weight" placeholder-class="phcolor" @focus="onWeightFocus" @blur="onWeightBlur" class="tui-input" name="weight" placeholder="公斤,可输入小数" maxlength="10" type="digit" />
         </tui-list-cell>
         </tui-list-cell>
 
 
-       <tui-list-cell class="line-cell" :hover="false">
+       <tui-list-cell v-if="form.specEnabled != 1" class="line-cell" :hover="false">
           <view class="tui-title">已售数量</view>
           <view class="tui-title">已售数量</view>
           <input v-model="form.sold" placeholder-class="phcolor" class="tui-input" name="sold" placeholder="客户端展示:已售数量+实售数量" maxlength="10" type="number" />
           <input v-model="form.sold" placeholder-class="phcolor" class="tui-input" name="sold" placeholder="客户端展示:已售数量+实售数量" maxlength="10" type="number" />
         </tui-list-cell>
         </tui-list-cell>
@@ -132,7 +217,7 @@ import TuiListCell from "@/components/plugin/list-cell";
 import AppUploader from "@/components/app-uploader";
 import AppUploader from "@/components/app-uploader";
 const form = require("@/utils/formValidation.js");
 const form = require("@/utils/formValidation.js");
 import { getClass } from "@/api/category";
 import { getClass } from "@/api/category";
-import { addGoodsData } from "@/api/goods"
+import { addGoodsData, useCaseList } from "@/api/goods"
 import { delImage } from "@/api/pic-text";
 import { delImage } from "@/api/pic-text";
 import htzImageUpload from '@/components/htz-image-upload/htz-image-upload.vue'
 import htzImageUpload from '@/components/htz-image-upload/htz-image-upload.vue'
 import inputRestore from "@/mixins/inputRestore";
 import inputRestore from "@/mixins/inputRestore";
@@ -164,8 +249,9 @@ export default {
         cover: '1',
         cover: '1',
         shortCover: "", // 添加shortCover字段,与detail.vue保持一致
         shortCover: "", // 添加shortCover字段,与detail.vue保持一致
         setItemNum: 0,
         setItemNum: 0,
+        useCaseIdList: [], specEnabled: 0, specList: [],
       },
       },
-      categoryData:[],
+      categoryData:[], useCaseData: [],
       currentImgData:[],
       currentImgData:[],
       firstCategorySelected: false
       firstCategorySelected: false
     };
     };
@@ -266,6 +352,176 @@ export default {
           this.categoryData = res.data
           this.categoryData = res.data
         }
         }
       })
       })
+      useCaseList().then(res => { if (res.code == 1) this.useCaseData = res.data || [] })
+    },
+    toggleUseCase(item) { const i=this.form.useCaseIdList.indexOf(item.id); i<0?this.form.useCaseIdList.push(item.id):this.form.useCaseIdList.splice(i,1) },
+    createSpecData(data = {}) {
+      const shopImg = Array.isArray(data.shortImgList) ? data.shortImgList : (Array.isArray(data.shopImg) ? data.shopImg : []);
+      const currentImgData = Array.isArray(data.currentImgData) ? data.currentImgData : (Array.isArray(data.smallImgList) ? data.smallImgList : []);
+      const shortCover = data.shortCover || (shopImg.includes(data.cover) ? data.cover : '') || shopImg[0] || '';
+      return {
+        id: data.id || 0,
+        specName: data.specName || '',
+        price: data.price !== undefined && data.price !== null ? data.price : '',
+        priceType: String(data.priceType !== undefined && data.priceType !== null ? data.priceType : '1'),
+        stock: data.stock !== undefined && data.stock !== null ? data.stock : 999,
+        stockSet: String(data.stockSet !== undefined && data.stockSet !== null ? data.stockSet : '0'),
+        flowerNum: data.flowerNum !== undefined && data.flowerNum !== null ? data.flowerNum : 0,
+        weight: data.weight !== undefined && data.weight !== null && data.weight !== '' ? data.weight : 1,
+        sold: data.sold !== undefined && data.sold !== null ? data.sold : 0,
+        shopImg,
+        currentImgData,
+        cover: shortCover,
+        shortCover
+      }
+    },
+    addSpec() { this.form.specList.push(this.createSpecData()) },
+    removeSpec(index) { this.form.specList.splice(index,1) },
+    setSpecField(index, field, value) {
+      const spec = this.form.specList[index] || this.createSpecData();
+      const nextSpec = { ...spec, [field]: value };
+      this.$set(this.form.specList, index, nextSpec);
+    },
+    specImgUploadSuccess(res, index) {
+      const spec = this.form.specList[index] || this.createSpecData();
+      const uploadRes = JSON.parse(res.data);
+      if (uploadRes.code == 1) {
+        const currentImgData = [...(spec.currentImgData || []), uploadRes.data.smallUrl];
+        const shopImg = [...(spec.shopImg || []), uploadRes.data.shortUrl];
+        const shortCover = spec.shortCover || uploadRes.data.shortUrl;
+        this.$set(this.form.specList, index, {
+          ...spec,
+          currentImgData,
+          shopImg,
+          cover: shortCover,
+          shortCover
+        });
+      }
+    },
+    specImgDeleteFn(res, index) {
+      const spec = this.form.specList[index] || this.createSpecData();
+      const imgIndex = res.index;
+      const currentImgData = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [...(spec.currentImgData || [])];
+      const shopImg = [...(spec.shopImg || [])];
+      const shortDelPath = (shopImg[imgIndex] || this.normalizeImgPath(res.del)).replace(/^\//, '');
+      shopImg.splice(imgIndex, 1);
+      const shortCover = shopImg.includes(spec.shortCover) ? spec.shortCover : (shopImg[0] || '');
+      this.$set(this.form.specList, index, {
+        ...spec,
+        currentImgData,
+        shopImg,
+        cover: shortCover,
+        shortCover
+      });
+      if (shortDelPath) {
+        delImage({ filePath: shortDelPath }).catch(() => {});
+      }
+    },
+    selSpecCoverFn(index, imgIndex) {
+      const spec = this.form.specList[index] || this.createSpecData();
+      const shortCover = (spec.shopImg || [])[imgIndex] || '';
+      this.$set(this.form.specList, index, {
+        ...spec,
+        cover: shortCover,
+        shortCover
+      });
+    },
+    toggleSpecs(e) {
+      this.form.specEnabled=e.detail.value?1:0;
+      if(this.form.specEnabled && !this.form.specList.length) {
+        this.form.specList.push(this.createSpecData(this.form))
+      }
+    },
+    setSpecPriceType(index, type) {
+      const spec = this.form.specList[index] || this.createSpecData();
+      spec.priceType = type;
+      if (type === '0') {
+        spec.price = 0;
+      } else if (String(spec.price) === '0') {
+        spec.price = '';
+      }
+      this.$set(this.form.specList, index, { ...spec });
+    },
+    setSpecStockSet(index, status) {
+      const spec = this.form.specList[index] || this.createSpecData();
+      spec.stockSet = status;
+      spec.stock = status === '0' ? 9999 : '';
+      this.$set(this.form.specList, index, { ...spec });
+    },
+    validateSpecList() {
+      if (this.form.specEnabled != 1) return true;
+      if (!this.form.specList.length) {
+        this.$msg('请至少添加一个规格组');
+        return false;
+      }
+      for (let i = 0; i < this.form.specList.length; i++) {
+        const spec = this.form.specList[i];
+        const name = `规格组${i + 1}`;
+        if (!spec.specName || !String(spec.specName).trim()) {
+          this.$msg(`请输入${name}名称`);
+          return false;
+        }
+        if (String(spec.priceType) === '1') {
+          const price = parseFloat(spec.price);
+          if (spec.price === '' || isNaN(price) || price <= 0) {
+            this.$msg(`${name}价格必须是大于0的数字`);
+            return false;
+          }
+        }
+        if (String(spec.stockSet) === '1') {
+          const stock = parseFloat(spec.stock);
+          if (spec.stock === '' || isNaN(stock) || stock < 0) {
+            this.$msg(`${name}库存数量必须是大于等于0的数字`);
+            return false;
+          }
+        }
+        const flowerNum = parseFloat(spec.flowerNum || 0);
+        if (isNaN(flowerNum) || flowerNum < 0) {
+          this.$msg(`${name}花支数必须是大于等于0的数字`);
+          return false;
+        }
+        const weight = parseFloat(spec.weight);
+        if (spec.weight === '' || isNaN(weight) || weight < 0) {
+          this.$msg(`${name}重量必须是大于等于0的数字`);
+          return false;
+        }
+        const sold = parseFloat(spec.sold || 0);
+        if (isNaN(sold) || sold < 0) {
+          this.$msg(`${name}已售数量必须是大于等于0的数字`);
+          return false;
+        }
+      }
+      return true;
+    },
+    normalizeSpecSubmitData(formData) {
+      if (formData.specEnabled != 1) {
+        formData.specList = [];
+        return formData;
+      }
+      formData.specList = formData.specList.map(item => {
+        const spec = this.createSpecData(item);
+        spec.priceType = parseInt(spec.priceType);
+        spec.price = spec.priceType === 1 ? parseFloat(spec.price || 0) : 0;
+        spec.stockSet = parseInt(spec.stockSet);
+        spec.stock = spec.stockSet === 1 ? parseInt(spec.stock || 0) : 9999;
+        spec.flowerNum = parseInt(spec.flowerNum || 0);
+        spec.weight = parseFloat(spec.weight || 0);
+        spec.sold = parseInt(spec.sold || 0);
+        spec.shopImg = Array.isArray(spec.shopImg) ? spec.shopImg : [];
+        spec.cover = spec.shopImg.length > 0 ? (spec.shortCover || spec.cover || spec.shopImg[0]) : '';
+        delete spec.currentImgData;
+        delete spec.shortCover;
+        return spec;
+      });
+      const firstSpec = formData.specList[0];
+      formData.priceType = String(firstSpec.priceType);
+      formData.price = firstSpec.price;
+      formData.stockSet = String(firstSpec.stockSet);
+      formData.stock = firstSpec.stock;
+      formData.flowerNum = firstSpec.flowerNum;
+      formData.weight = firstSpec.weight;
+      formData.sold = firstSpec.sold;
+      return formData;
     },
     },
     // 封面选择
     // 封面选择
     selCoverFn(index) {
     selCoverFn(index) {
@@ -359,6 +615,7 @@ export default {
         } else {
         } else {
           formData.cover = '';
           formData.cover = '';
         }
         }
+        formData = this.normalizeSpecSubmitData(formData);
         if(formData.stockSet === '0'){
         if(formData.stockSet === '0'){
           formData.stock = 9999;
           formData.stock = 9999;
         }
         }
@@ -382,8 +639,12 @@ export default {
         }
         }
       ];
       ];
       
       
+      if (!this.validateSpecList()) {
+        return false;
+      }
+
       // 只有在有价格类型时才验证价格
       // 只有在有价格类型时才验证价格
-      if (this.form.priceType === '1') {
+      if (this.form.specEnabled != 1 && this.form.priceType === '1') {
         rules.push({
         rules.push({
           name: "price",
           name: "price",
           rule: ["required"],
           rule: ["required"],
@@ -398,7 +659,7 @@ export default {
       }
       }
       
       
       // 验证库存数量
       // 验证库存数量
-      if (this.form.stockSet === '1' && this.form.stock !== "") {
+      if (this.form.specEnabled != 1 && this.form.stockSet === '1' && this.form.stock !== "") {
         const stockNum = parseFloat(this.form.stock);
         const stockNum = parseFloat(this.form.stock);
         if (isNaN(stockNum) || stockNum < 0) {
         if (isNaN(stockNum) || stockNum < 0) {
           this.$msg('库存数量必须是大于等于0的数字!');
           this.$msg('库存数量必须是大于等于0的数字!');
@@ -407,7 +668,7 @@ export default {
       }
       }
       
       
       // 验证已售数量
       // 验证已售数量
-      if (this.form.sold !== "") {
+      if (this.form.specEnabled != 1 && this.form.sold !== "") {
         const soldNum = parseFloat(this.form.sold);
         const soldNum = parseFloat(this.form.sold);
         if (isNaN(soldNum) || soldNum < 0) {
         if (isNaN(soldNum) || soldNum < 0) {
           this.$msg('已售数量必须是大于等于0的数字!');
           this.$msg('已售数量必须是大于等于0的数字!');
@@ -416,7 +677,7 @@ export default {
       }
       }
       
       
       // 验证花支数
       // 验证花支数
-      if (this.form.flowerNum !== "" && this.form.flowerNum !== 0) {
+      if (this.form.specEnabled != 1 && this.form.flowerNum !== "" && this.form.flowerNum !== 0) {
         const flowerNum = parseFloat(this.form.flowerNum);
         const flowerNum = parseFloat(this.form.flowerNum);
         if (isNaN(flowerNum) || flowerNum < 0) {
         if (isNaN(flowerNum) || flowerNum < 0) {
           this.$msg('花支数必须是大于0的数字!');
           this.$msg('花支数必须是大于0的数字!');
@@ -425,7 +686,7 @@ export default {
       }
       }
 
 
       // 验证重量(千克)
       // 验证重量(千克)
-      if (this.form.weight !== "") {
+      if (this.form.specEnabled != 1 && this.form.weight !== "") {
         const weightNum = parseFloat(this.form.weight);
         const weightNum = parseFloat(this.form.weight);
         if (isNaN(weightNum) || weightNum < 0) {
         if (isNaN(weightNum) || weightNum < 0) {
           this.$msg('重量必须是大于等于0的数字!');
           this.$msg('重量必须是大于等于0的数字!');
@@ -528,6 +789,128 @@ export default {
     }
     }
   }
   }
 }
 }
+.spec-switch-card {
+  margin: 0 20upx 20upx;
+  padding: 28upx 30upx;
+  border: 1px solid #e5f5ec;
+  border-radius: 12upx;
+  background: linear-gradient(90deg, #f3fff8 0%, #ffffff 100%);
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  box-sizing: border-box;
+}
+.spec-switch-left {
+  display: flex;
+  align-items: center;
+  min-width: 0;
+}
+.spec-switch-icon {
+  width: 72upx;
+  height: 72upx;
+  margin-right: 22upx;
+  border-radius: 50%;
+  background: #09C567;
+  color: #ffffff;
+  font-size: 46upx;
+  line-height: 72upx;
+  text-align: center;
+}
+.spec-switch-title {
+  font-size: 34upx;
+  color: #222222;
+  font-weight: 700;
+  line-height: 44upx;
+}
+.spec-switch-desc {
+  margin-top: 6upx;
+  font-size: 24upx;
+  color: #999999;
+}
+.spec-switch-right {
+  display: flex;
+  align-items: center;
+  flex-shrink: 0;
+  color: #999999;
+  font-size: 24upx;
+  text {
+    margin-left: 12upx;
+  }
+}
+.spec-wrap {
+  padding: 0 0 20upx;
+}
+.spec-card {
+  margin-bottom: 24upx;
+  background: #ffffff;
+  border-top: 1px solid #eeeeee;
+  border-bottom: 1px solid #eeeeee;
+}
+.spec-card-head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 20upx 30upx 0;
+}
+.spec-card-title {
+  font-size: 30upx;
+  color: #222222;
+  font-weight: 700;
+}
+.spec-remove {
+  margin: 0;
+  padding: 0 20upx;
+  height: 54upx;
+  line-height: 54upx;
+  border: 1px solid #eeeeee;
+  border-radius: 8upx;
+  background: #ffffff;
+  color: #999999;
+  font-size: 24upx;
+}
+.spec-line {
+  margin-top: 0;
+}
+.spec-image-wrap {
+  padding: 10upx 0 20upx;
+  border-bottom: 1px solid #eeeeee;
+  .module-tit {
+    padding: 20upx 30upx;
+  }
+  .module-det {
+    padding: 0 30upx;
+  }
+}
+.spec-cover-row {
+  display: flex;
+  align-items: flex-start;
+  padding: 20upx 30upx 0;
+}
+.spec-cover-title {
+  width: 200upx;
+  flex-shrink: 0;
+  font-size: 28upx;
+  color: #333333;
+  line-height: 68upx;
+}
+.spec-cover-btns {
+  flex: 1;
+  display: flex;
+  flex-wrap: wrap;
+  .admin-button-com {
+    margin-right: 30upx;
+    margin-bottom: 16upx;
+  }
+}
+.spec-add-btn {
+  margin: 20upx 24upx 0;
+  height: 88upx;
+  line-height: 88upx;
+  border-radius: 8upx;
+  background: #09C567;
+  color: #ffffff;
+  font-size: 30upx;
+}
 
 
 /* 确保所有选中的按钮都使用相同的颜色 */
 /* 确保所有选中的按钮都使用相同的颜色 */
 .admin-button-com:not(.default) {
 .admin-button-com:not(.default) {
@@ -664,4 +1047,4 @@ export default {
     }
     }
   }
   }
 }
 }
-</style>
+</style>

Разница между файлами не показана из-за своего большого размера
+ 560 - 92
hdApp/src/admin/goods/detail.vue


+ 5 - 2
hdApp/src/admin/goods/manage.vue

@@ -1186,10 +1186,12 @@ page {
 
 
 .search-type-wrap {
 .search-type-wrap {
   display: flex;
   display: flex;
+  align-items: center;
 }
 }
 
 
-.search-type-wrap > * + * {
-  margin-left: 8upx;
+/* 名称/编号切换按钮间距,避免小程序原生 button 相邻选择器不生效 */
+.search-type-wrap .small-btn:not(:last-child) {
+  margin-right: 12upx;
 }
 }
 
 
 .small-btn,
 .small-btn,
@@ -1295,6 +1297,7 @@ page {
 
 
 .tool-btn {
 .tool-btn {
   width: 112upx;
   width: 112upx;
+  margin: 0 4upx;
 }
 }
 
 
 .tool-btn--wide {
 .tool-btn--wide {

+ 1 - 0
hdApp/src/admin/home/homeMenus.js

@@ -13,6 +13,7 @@ export const APPLY_MENU_GROUPS = [
       { name: '商品管理', icon: homeIcon('bouquet'), url: '/admin/goods/manage', pf: 1 },
       { name: '商品管理', icon: homeIcon('bouquet'), url: '/admin/goods/manage', pf: 1 },
       { name: '新增花束', icon: homeIcon('add-bouquet'), url: '/admin/goods/add', pf: 1 },
       { name: '新增花束', icon: homeIcon('add-bouquet'), url: '/admin/goods/add', pf: 1 },
       { name: '分类', icon: homeIcon('category-filled'), url: '/admin/goods/categoryV2?tab=0', pf: 1 },
       { name: '分类', icon: homeIcon('category-filled'), url: '/admin/goods/categoryV2?tab=0', pf: 1 },
+      { name: '使用场景', icon: homeIcon('category-filled'), url: '/admin/useCase/list', pf: 1 },
       { name: '排序', icon: homeIcon('sort-filled'), url: '/admin/goods/categorySort', pf: 1 },
       { name: '排序', icon: homeIcon('sort-filled'), url: '/admin/goods/categorySort', pf: 1 },
       { name: '任务', icon: homeIcon('task-filled'), url: '/admin/work/list', pf: 1 },
       { name: '任务', icon: homeIcon('task-filled'), url: '/admin/work/list', pf: 1 },
       { name: '涨价设置', icon: homeIcon('price-increase'), url: '/admin/goods/price-increase', pf: 1 },
       { name: '涨价设置', icon: homeIcon('price-increase'), url: '/admin/goods/price-increase', pf: 1 },

+ 77 - 0
hdApp/src/admin/useCase/list.vue

@@ -0,0 +1,77 @@
+<template>
+  <view class="page-container">
+    <view class="list-container">
+      <block v-if="!$util.isEmpty(list.data)">
+        <view class="list-item" v-for="item in list.data" :key="item.id">
+          <view class="item-content" @click="edit(item)">
+            <view class="item-icon">场</view>
+            <view class="item-info">
+              <view class="info-main"><text class="use-case-name">{{ item.useCaseName }}</text></view>
+              <view class="info-details">
+                <text class="detail-text">序号: {{ item.inTurn }}</text>
+                <text class="detail-text" :class="item.status == 1 ? 'active' : 'disabled'">{{ item.status == 1 ? '商城显示' : '商城隐藏' }}</text>
+              </view>
+            </view>
+          </view>
+          <view class="item-actions">
+            <button class="action-btn edit-btn" @click="edit(item)">编辑</button>
+            <button class="action-btn delete-btn" @click.stop="remove(item.id)">删除</button>
+          </view>
+        </view>
+      </block>
+      <app-wrapper-empty v-else title="暂无使用场景数据" :is-empty="$util.isEmpty(list.data)" />
+    </view>
+
+    <view class="bottom-toolbar"><button class="add-category-btn" @click="add"><text class="btn-icon">+</text><text>添加使用场景</text></button></view>
+
+    <modal-module :show="modifyModal" :maskClosable="false" @cancel="modalCancel" @click="submit" width="96%" :title="modifyTitle + '使用场景'" padding="40rpx">
+      <template v-slot:content>
+        <view class="modal-form">
+          <view class="form-group"><view class="form-label"><text class="label-text">场景名称</text><text class="required-mark">*</text></view><view class="form-input"><input v-model="modifyForm.useCaseName" :adjust-position="false" class="input-field" placeholder="请输入使用场景名称" maxlength="20" /></view></view>
+          <view class="form-group"><view class="form-label"><text class="label-text">排序值</text></view><view class="form-input"><input v-model="modifyForm.inTurn" type="number" @focus="modifyForm.inTurn = ''" :adjust-position="false" class="input-field" placeholder="数值越大排序越靠前" /></view></view>
+          <view class="form-group switch-group"><view class="form-label"><text class="label-text">显示状态</text></view><view class="form-switch"><switch :checked="modifyForm.status == 1" @change="switchChange" color="#007AFF" /><text class="switch-text">{{ modifyForm.status == 1 ? '商城显示' : '商城隐藏' }}</text></view></view>
+        </view>
+      </template>
+    </modal-module>
+  </view>
+</template>
+
+<script>
+import ModalModule from '@/components/plugin/modal';
+import AppWrapperEmpty from '@/components/app-wrapper-empty';
+import { list } from '@/mixins';
+import { useCaseList, useCaseAdd, useCaseUpdate, useCaseDelete } from '@/api/goods';
+export default {
+  name: 'useCaseList', components: { ModalModule, AppWrapperEmpty }, mixins: [list],
+  data() { return { modifyModal: false, modifyTitle: '添加', modifyForm: { useCaseId: 0, useCaseName: '', inTurn: '', status: 1 } }; },
+  onPullDownRefresh() { this.resetList(); this._list().then(() => uni.stopPullDownRefresh()); },
+  onReachBottom() { uni.stopPullDownRefresh(); },
+  methods: {
+    init() { this._list(); },
+    _list() { return useCaseList().then(res => this.completes({ code: res.code, msg: res.msg, data: { list: res.data || [] } })); },
+    add() { this.modifyTitle = '新增'; this.modifyForm = { useCaseId: 0, useCaseName: '', inTurn: '', status: 1 }; this.modifyModal = true; },
+    edit(item) { this.modifyTitle = '编辑'; this.modifyForm = { useCaseId: item.id, useCaseName: item.useCaseName, inTurn: item.inTurn, status: item.status }; this.modifyModal = true; },
+    modalCancel() { this.modifyModal = false; },
+    switchChange(e) { this.modifyForm.status = e.detail.value ? 1 : 0; },
+    submit(e) {
+      if (e.index === 0) return this.modalCancel();
+      if (!this.modifyForm.useCaseName) return this.$msg('请输入使用场景名称');
+      const api = this.modifyForm.useCaseId ? useCaseUpdate : useCaseAdd;
+      api({ ...this.modifyForm }).then(res => { if (res.code == 1) { this.$msg('操作成功!'); this.modalCancel(); this.resetList(); this._list(); } });
+    },
+    remove(id) { uni.showModal({ title: '提示', content: '确认删除?', success: res => { if (res.confirm) useCaseDelete({ useCaseId: id }).then(ret => { if (ret.code == 1) { this.$msg('已删除'); this.resetList(); this._list(); } }); } }); }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.page-container { min-height: 100vh; background: #f5f5f5; padding: 20rpx 20rpx 120rpx; }
+.list-container { background: #fff; border-radius: 12rpx; overflow: hidden; box-shadow: 0 2rpx 8rpx rgba(0,0,0,.06); margin-bottom: 60upx; }
+.list-item { display:flex; align-items:center; padding:20rpx 30rpx; border-bottom:1rpx solid #f0f0f0; &:last-child{border-bottom:0;} &:active{background:#f8f8f8;} }
+.item-content { display:flex; align-items:center; flex:1; min-width:0; }
+.item-icon { width:80rpx; height:80rpx; margin-right:24rpx; border-radius:8rpx; background:#e8f7ee; color:#52c41a; display:flex; align-items:center; justify-content:center; font-size:30rpx; font-weight:600; }
+.item-info { flex:1; }.info-main { margin-bottom:8rpx; }.use-case-name { font-size:30rpx; font-weight:600; color:#333; }.info-details { display:flex; }.detail-text { margin-right:24rpx; font-size:24rpx; color:#666; &.active{color:#383a39;} &.disabled{color:#FF3B30;} }
+.item-actions { display:flex; flex-shrink:0; }.action-btn { margin-left:12rpx; padding:12rpx 20rpx; border:0; border-radius:6rpx; font-size:24rpx; min-width:80rpx; color:#fff; &.edit-btn{background:#52c41a;} &.delete-btn{background:#FF3B30;} }
+.bottom-toolbar { position:fixed; bottom:0; left:0; right:0; z-index:2; padding:20rpx 30rpx; padding-bottom:calc(20rpx + env(safe-area-inset-bottom)); background:#fff; border-top:1rpx solid #e5e5e5; box-shadow:0 -2rpx 8rpx rgba(0,0,0,.06); }.add-category-btn { width:100%; padding:24rpx; background:#52c41a; color:#fff; border:0; border-radius:8rpx; font-size:30rpx; font-weight:600; .btn-icon{margin-right:12rpx;font-size:32rpx;} }
+.form-group { margin-bottom:32rpx; &:last-child{margin-bottom:0;} }.form-label { display:flex; align-items:center; margin-bottom:16rpx; }.label-text{font-size:28rpx;color:#333;font-weight:500;}.required-mark{margin-left:6rpx;color:#FF3B30;}.input-field{width:100%;height:80rpx;padding:0 20rpx;border:1rpx solid #e5e5e5;border-radius:8rpx;font-size:28rpx;box-sizing:border-box;}.form-switch{display:flex;align-items:center;}.switch-text{margin-left:16rpx;color:#666;font-size:26rpx;}
+</style>

+ 6 - 1
hdApp/src/api/goods/index.js

@@ -209,4 +209,9 @@ export const categoryStatusB = data => {
 
 
 export const createSn = data => {
 export const createSn = data => {
 	return https.get('/goods/create-sn', data)
 	return https.get('/goods/create-sn', data)
-}
+}
+
+export const useCaseList = data => https.get('/use-case/list', data)
+export const useCaseAdd = data => https.post('/use-case/add', data)
+export const useCaseUpdate = data => https.post('/use-case/update', data)
+export const useCaseDelete = data => https.get('/use-case/delete', data)

+ 6 - 0
hdApp/src/pages.json

@@ -363,6 +363,12 @@
 				{ "path": "result", "style": { "navigationBarTitleText": "添加成功" }}
 				{ "path": "result", "style": { "navigationBarTitleText": "添加成功" }}
 			]
 			]
 		},
 		},
+		{
+			"root":"admin/useCase",
+			"pages":[
+				{"path":"list","style":{"navigationBarTitleText":"使用场景"}}
+			]
+		},
 		{
 		{
 			"root": "admin/shopAdmin",
 			"root": "admin/shopAdmin",
 			"pages": [
 			"pages": [

+ 11 - 7
mallApp/src/pages/goods/components/sel-popup.vue

@@ -5,6 +5,7 @@
       <view class="tui-popup-box">
       <view class="tui-popup-box">
         <scroll-view scroll-y class="tui-popup-scroll">
         <scroll-view scroll-y class="tui-popup-scroll">
           <view class="tui-scrolldiv-box">
           <view class="tui-scrolldiv-box">
+            <view v-if="info.specEnabled == 1" class="spec-list"><view class="tui-attr-title">规格</view><button v-for="(spec,index) in info.specList" :key="spec.id" class="spec-button" :class="{active:selIndex===index}" @click="selectSpec(index)">{{spec.specName}}</button></view>
             <view class="tui-number-box tui-bold tui-attr-title">
             <view class="tui-number-box tui-bold tui-attr-title">
               <view class="tui-attr-title">数量</view>
               <view class="tui-attr-title">数量</view>
               <view class="number-box-wrapper">
               <view class="number-box-wrapper">
@@ -74,7 +75,8 @@ export default {
   },
   },
   computed: {
   computed: {
     maxStock() {
     maxStock() {
-      return 9999;
+      const spec = this.info.specEnabled == 1 && this.info.specList ? this.info.specList[this.selIndex] : this.info;
+      return Number(spec && spec.stockSet == 1 ? spec.stock : 9999) || 9999;
     },
     },
   },
   },
   methods: {
   methods: {
@@ -85,13 +87,14 @@ export default {
       this.$emit("close");
       this.$emit("close");
     },
     },
     nextFn() {
     nextFn() {
-      if (Number(this.info.price) <= 0) {
-        this.$msg("商品还没有价格哦");
-        return false;
-      }
-      uni.setStorageSync("buyGoodsDetil", this.info);
-      this.$util.pageTo({url: "/pages/order/buy?hdId="+this.hdId+'&goodsId='+this.info.id+'&goodsNum='+this.buyNum+'&selIndex='+this.selIndex+'&account='+this.account+'&freightType='+this.info.freightType,type: 2})
+      const spec = this.info.specEnabled == 1 ? this.info.specList[this.selIndex] : null;
+      if (this.info.specEnabled == 1 && !spec) { this.$msg('请选择规格'); return; }
+      if (Number((spec || this.info).price) <= 0) { this.$msg("商品还没有价格哦"); return false; }
+      const goods = {...this.info, ...(spec || {}), buyNum:this.buyNum};
+      uni.setStorageSync("buyGoodsDetil", goods);
+      this.$util.pageTo({url: "/pages/order/buy?hdId="+this.hdId+'&goodsId='+this.info.id+'&specGoodsId='+(spec ? spec.id : '')+'&goodsNum='+this.buyNum+'&selIndex='+this.selIndex+'&account='+this.account+'&freightType='+this.info.freightType,type: 2})
     },
     },
+    selectSpec(index) { this.selIndex=index; const spec=this.info.specList[index]; this.$emit('changeSpec', spec); },
   },
   },
 };
 };
 </script>
 </script>
@@ -155,6 +158,7 @@ export default {
   .tui-scrolldiv-box {
   .tui-scrolldiv-box {
     padding: 10rpx 0;
     padding: 10rpx 0;
   }
   }
+  .spec-button{display:inline-block;margin:12rpx;padding:14rpx 24rpx;background:#f5f5f5;font-size:28rpx}.spec-button.active{background:#09C567;color:#fff}
 
 
   .tui-attr-title {
   .tui-attr-title {
     padding: 0 0 20rpx 0;
     padding: 0 0 20rpx 0;

+ 2 - 0
mallApp/src/pages/goods/detail.vue

@@ -75,6 +75,7 @@
       :hdId="hdId"
       :hdId="hdId"
       :account="account"
       :account="account"
       @change="changeNum"
       @change="changeNum"
+      @changeSpec="changeSpec"
       @close="hidePopup"
       @close="hidePopup"
     />
     />
     
     
@@ -330,6 +331,7 @@ export default {
     changeNum(e) {
     changeNum(e) {
       this.buyNum = e.value;
       this.buyNum = e.value;
     },
     },
+    changeSpec(spec) { this.data = {...this.data, price: spec.price, priceType: spec.priceType, stock: spec.stock, selectedSpecGoodsId: spec.id, selectedSpecName: spec.specName}; },
     goToShop() {
     goToShop() {
       let account = uni.getStorageSync("account");
       let account = uni.getStorageSync("account");
       let hdId = this.option.hdId ? this.option.hdId : 0;
       let hdId = this.option.hdId ? this.option.hdId : 0;

+ 2 - 1
mallApp/src/pages/order/buy.vue

@@ -191,6 +191,7 @@ export default {
       this.goodsInfo = goodsInfo
       this.goodsInfo = goodsInfo
 
 
       this.form.goodsId = this.option.goodsId || ""
       this.form.goodsId = this.option.goodsId || ""
+      this.form.specGoodsId = this.option.specGoodsId || ""
       this.form.goodsNum = goodsNum
       this.form.goodsNum = goodsNum
 
 
       const total = Number(goodsInfo.price || 0) * goodsNum
       const total = Number(goodsInfo.price || 0) * goodsNum
@@ -441,4 +442,4 @@ export default {
     margin: 10upx 0;
     margin: 10upx 0;
   }
   }
 }
 }
-</style>
+</style>

Некоторые файлы не были показаны из-за большого количества измененных файлов