绿满眶商城微信小程序-uniapp
c22e91296d532873b70cb51bf5510bf7738f3f1a..a1c289e7dfc5d9a3b8dc7ca9b05857f276c05f8d
2025-07-09 xiangpei
Merge remote-tracking branch 'origin/dev' into dev
a1c289 对比 | 目录
2025-07-09 xiangpei
上家发布商品功能
da1e3d 对比 | 目录
6个文件已修改
1个文件已添加
990 ■■■■■ 已修改文件
api/store.js 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages.json 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/goods-manager/goodsList/goodsList.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/supplier/publish-goods/chooseCategery.vue 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/supplier/publish-goods/goodsInfo.vue 877 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/supplier/publish-goods/publishGoods.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/tabbar/index/home.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
api/store.js
@@ -67,5 +67,37 @@
  });
}
/**
 * 获取物流模板
 *
 */
 export function getFreightTemplate() {
  return http.request({
    url: `/store/store/freightTemplate`,
    method: Method.GET
  });
}
/**
 * 获取计量单位
 *
 */
 export function getGoodsUnit(param) {
  return http.request({
    url: `/store/store/goods/unit`,
    method: Method.GET,
    param: param
  });
}
/**
 * 发布商品
 *
 */
 export function createGoods(data) {
  return http.request({
    url: `/store/store/goods/create`,
    method: Method.POST,
    data: data
  });
}
pages.json
@@ -240,7 +240,7 @@
            }
        },
        {
            "path": "pages/goods-manager/addGoods/addGoods",
            "path": "pages/supplier/publish-goods/publishGoods",
            "style": {
                "enablePullDownRefresh": true,
                "navigationBarTitleText": "新增商品",
@@ -366,6 +366,13 @@
                    "u-switch": "view"
                }
            }
        },
        {
            "path" : "pages/supplier/publish-goods/goodsInfo",
            "style" :
            {
                "navigationBarTitleText" : ""
            }
        }
    ],
    "subPackages": [
pages/goods-manager/goodsList/goodsList.vue
@@ -158,7 +158,7 @@
        },
        addGoods(id) {
            uni.navigateTo({
                url: `/pages/goods-manager/addGoods/addGoods${id ? "?id=" + id : ""}`,
                url: `/pages/supplier/publish-goods/publishGoods`,
            });
        }
pages/supplier/publish-goods/chooseCategery.vue
@@ -16,7 +16,7 @@
<script>
    import {
        getMyActivityList
    } from '@/api/merchant.js'
    } from '@/api/supplier.js'
    export default {
        data() {
            return {
@@ -27,9 +27,14 @@
            }
        },
        methods:{
            // 父组件获取选择的分类
            getCategery() {
                return this.chooseCategeryId
            },
            chooseCategery(id,name){
                this.chooseCategeryId =id
                this.categeryName = name
                this.$emit("choose", this.chooseCategeryId)
            }
        },
        async mounted() {
pages/supplier/publish-goods/goodsInfo.vue
New file
@@ -0,0 +1,877 @@
<template>
  <view class="add-product-container">
    <scroll-view scroll-y class="scroll-view">
      <view class="form-section">
        <!-- 基本信息 -->
        <view class="section-title">基本信息</view>
        <view class="form-item">
          <text class="label">商品名称</text>
          <input v-model="formData.goodsName" placeholder="请输入商品名称" class="input" />
        </view>
        <view class="form-item">
          <text class="label">商品价格</text>
          <view style="display: flex;align-items: center;">
              <input v-model="formData.price" type="number" placeholder="请输入商品价格" class="input" />
              <text class="unit">元</text>
          </view>
        </view>
        <view class="form-item">
          <text class="label">抽成比例</text>
          <view style="display: flex;align-items: center;">
              <input v-model="formData.commission" type="number" placeholder="请输入抽成比例" class="input" />
              <text class="unit">%</text>
          </view>
        </view>
        <view class="form-item">
          <text class="label">商品卖点描述</text>
          <textarea v-model="formData.sellingPoint" placeholder="请输入商品卖点描述" class="textarea" />
        </view>
        <view class="form-item">
          <text class="label">计量单位</text>
          <picker @change="onUnitChange" :value="unitIndex" :range="unitOptions" range-key="name" class="picker">
            <view class="picker-text">{{goodsUnit ? goodsUnit.name : '请选择计量单位'}}</view>
          </picker>
        </view>
        <view class="form-item">
          <text class="label">销售模式</text>
          <radio-group @change="onSalesModeChange" class="radio-group">
            <label class="radio-label">
              <radio value="RETAIL" :checked="formData.salesModel === 'RETAIL'" /> 零售
            </label>
            <label class="radio-label">
              <radio value="PRESALE" :checked="formData.salesModel === 'PRESALE'" /> 预售
            </label>
          </radio-group>
        </view>
        <view class="form-item" v-if="formData.salesModel === 'PRESALE'">
          <text class="label">预售时间段</text>
          <view class="date-range-picker">
            <view class="date-picker-item">
              <text class="date-label">开始日期</text>
              <picker mode="date" @change="onPresaleStartDateChange" class="picker">
                <view class="picker-text">{{formData.presaleStartDate || '请选择开始日期'}}</view>
              </picker>
            </view>
            <view class="date-picker-item">
              <text class="date-label">结束日期</text>
              <picker mode="date" @change="onPresaleEndDateChange" class="picker">
                <view class="picker-text">{{formData.presaleEndDate || '请选择结束日期'}}</view>
              </picker>
            </view>
          </view>
        </view>
        <!-- 商品主图 -->
        <view class="form-item">
          <text class="label">商品主图</text>
          <view class="upload-container">
            <image v-for="img in formData.goodsGalleryList" :key="img" :src="endpoint + '/' + img" class="uploaded-image" mode="aspectFill" />
            <view class="upload-btn" @click="uploadMainImage">
              <uni-icons type="plusempty" size="30" color="#999"></uni-icons>
              <text class="upload-text">{{formData.goodsGalleryList.length < 1 ? '上传主图' : '重新上传'}}</text>
            </view>
          </view>
        </view>
        <!-- 商品视频 -->
        <view class="form-item">
          <text class="label">商品视频</text>
          <view class="upload-container">
            <video v-show="formData.goodsVideo" :src="endpoint + '/' + formData.goodsVideo" class="uploaded-video" controls></video>
            <view class="upload-btn" @click="uploadVideo">
              <uni-icons type="videocam" size="30" color="#999"></uni-icons>
              <text class="upload-text">{{formData.goodsVideo ? '重新上传' : '上传视频'}}</text>
            </view>
            <progress v-show="videoUploading" style="width: 100%;" :percent="videoUploadProgress" active-mode="forwards" show-info stroke-width="6" :active="true" active-color="#ff573e" />
          </view>
        </view>
        <!-- 规格设置 -->
        <view class="section-title">规格设置</view>
        <view class="specs-container">
          <view class="spec-item" v-for="(spec, specIndex) in formData.specs" :key="specIndex">
            <view class="spec-header">
              <input v-model="spec.name" placeholder="规格项名称" class="spec-input" />
              <uni-icons type="trash" size="20" color="#f56c6c" @click="removeSpec(specIndex)"></uni-icons>
            </view>
            <view class="spec-values">
              <view class="spec-value-tag" v-for="(value, valueIndex) in spec.values" :key="valueIndex">
                <input v-model="spec.values[valueIndex]" placeholder="规格值" class="value-input" />
                <uni-icons type="clear" size="16" color="#999" @click="removeSpecValue(specIndex, valueIndex)"></uni-icons>
              </view>
              <view class="add-value-btn" @click="addSpecValue(specIndex)">
                <uni-icons type="plusempty" size="16" color="#409eff"></uni-icons>
                <text>添加规格值</text>
              </view>
            </view>
          </view>
          <view class="add-spec-btn" @click="addSpec">
            <uni-icons type="plusempty" size="16" color="#409eff"></uni-icons>
            <text>添加规格项</text>
          </view>
        </view>
        <!-- 规格组合 -->
        <view class="section-title" v-if="hasSpecs">规格组合</view>
        <view class="sku-container" v-if="hasSpecs">
          <view class="sku-item" v-for="(sku, skuIndex) in formData.skuList" :key="skuIndex">
            <view class="sku-title">{{sku.specValues.join(' / ')}}</view>
            <view class="sku-form">
              <view class="sku-form-item">
                <text class="sku-label">价格</text>
                <view style="display: flex;align-items: center;">
                    <input v-model="sku.price" type="number" placeholder="价格" class="sku-input" />
                    <text class="sku-unit">元</text>
                </view>
              </view>
              <view class="sku-form-item">
                <text class="sku-label">重量</text>
                <view style="display: flex;align-items: center;">
                    <input v-model="sku.weight" type="number" placeholder="重量" class="sku-input" />
                    <text class="sku-unit">克</text>
                </view>
              </view>
              <view class="sku-form-item">
                <text class="sku-label">库存</text>
                <view style="display: flex;align-items: center;">
                    <input v-model="sku.quantity" type="number" placeholder="库存" class="sku-input" />
                    <text class="sku-unit">件</text>
                </view>
              </view>
              <view class="sku-form-item">
                <text class="sku-label">货号</text>
                <input v-model="sku.sn" placeholder="货号" class="sku-input" />
              </view>
            </view>
          </view>
        </view>
        <!-- 商品描述 -->
        <view class="section-title">商品描述</view>
        <view class="form-item">
          <editor
            id="editor"
            class="editor"
            placeholder="请输入商品详细描述"
            @ready="onEditorReady"
            @input="onEditorInput">
          </editor>
        </view>
        <!-- 物流模板 -->
        <view class="section-title">物流模板</view>
        <view class="form-item">
          <picker @change="onTemplateChange" :value="templateIndex" :range="templateOptions" range-key="name" class="picker">
            <view class="picker-text">{{shippingTemplate ? shippingTemplate.name : '请选择物流模板'}}</view>
          </picker>
        </view>
      </view>
      <view class="submit-btn-container">
        <button type="primary" class="submit-btn" @click="submitForm">提交商品</button>
      </view>
    </scroll-view>
  </view>
</template>
<script>
    import { getFreightTemplate, getGoodsUnit } from "@/api/store.js"
    import { getSTSToken } from "@/api/common.js";
    import { getFileKey } from "@/utils/file.js";
export default {
  data() {
    return {
      formData: {
        goodsType: "PHYSICAL_GOODS",
        updateSku: true,
        regeneratorSkuFlag: true,
        goodsName: '', // 商品名称
        price: '', // 商品价格
        commission: '', // 抽成比例
        sellingPoint: '', // 卖点描述
        goodsUnit: '', // 计量单位
        salesModel: 'RETAIL', // 销售模式
        preSaleTime: [], // 预售时间
        presaleStartDate: '', // 预售开始日期
        presaleEndDate: '', // 预售结束日期
        goodsGalleryList: [], // 主图
        goodsVideo: '', // 视频
        specs: [], // 规格项
        skuList: [], // SKU列表
        mobileIntro: '', // 商品描述
        templateId: null, // 物流模板
        release: true, // 立即发布
        recommend: false,
      },
      shippingTemplate: null, // 选中的物流模板
      goodsUnit: null, // 选中的计量单位
      unitOptions: [],
      unitIndex: -1,
      templateOptions: [], // 物流模板选项
      templateIndex: -1,
      editorCtx: null,
      cosClient: null,
      bucket: '',
      region: '',
      endpoint: '',
      videoUploadProgress: 0,
      videoUploading: false
    }
  },
  computed: {
    hasSpecs() {
      return this.formData.specs.length > 0
    }
  },
  mounted() {
    this.loadShippingTemplates()
    this.loadGoodsUnit()
    this.initCOS()
  },
  methods: {
    // 初始化腾讯云cos客户端
    async initCOS() {
          // 调用后端获取sts临时访问凭证
          getSTSToken().then(res => {
              const COS = require('@/lib/cos-wx-sdk-v5.js'); // 开发时使用
              // const COS = require('./lib/cos-wx-sdk-v5.min.js'); // 上线时使用压缩包
              // console.log(COS.version);  sdk 版本需要不低于 1.7.2
              this.cosClient = new COS({
                  SecretId: res.data.data.tmpSecretId, // sts 服务下发的临时 secretId
                  SecretKey: res.data.data.tmpSecretKey, // sts 服务下发的临时 secretKey
                  SecurityToken: res.data.data.sessionToken, // sts 服务下发的临时 SessionToken
                  StartTime: res.data.data.stsStartTime, // 建议传入服务端时间,可避免客户端时间不准导致的签名错误
                  ExpiredTime: res.data.data.stsEndTime, // 临时密钥过期时间
                  SimpleUploadMethod: 'putObject', // 强烈建议,高级上传、批量上传内部对小文件做简单上传时使用 putObject,sdk 版本至少需要v1.3.0
               });
               this.bucket = res.data.data.bucket
               this.region = res.data.data.region
               this.endpoint = res.data.data.endpoint
          })
    },
    // 加载物流模板
    async loadShippingTemplates() {
      getFreightTemplate().then(res => {
          this.templateOptions = res.data.result
      })
    },
    // 加载计量单位
    async loadGoodsUnit() {
        getGoodsUnit({pageNumber: 1, pageSize: 1000}).then(res => {
            console.log("计量单位结果", res.data.result);
            this.unitOptions = res.data.result.records
        })
    },
    // 计量单位选择
    onUnitChange(e) {
      this.unitIndex = e.detail.value
      this.goodsUnit = this.unitOptions[this.unitIndex]
    },
    // 销售模式选择
    onSalesModeChange(e) {
      this.formData.salesModel = e.detail.value
    },
    // 预售开始日期选择
    onPresaleStartDateChange(e) {
      this.formData.presaleStartDate = e.detail.value
      this.validateDateRange()
    },
    // 预售结束日期选择
    onPresaleEndDateChange(e) {
      this.formData.presaleEndDate = e.detail.value
      this.validateDateRange()
    },
    // 验证日期范围
    validateDateRange() {
      if (this.formData.presaleStartDate && this.formData.presaleEndDate) {
        const start = new Date(this.formData.presaleStartDate)
        const end = new Date(this.formData.presaleEndDate)
        if (start > end) {
          uni.showToast({
            title: '结束日期不能早于开始日期',
            icon: 'none'
          })
          this.formData.presaleEndDate = ''
        }
      }
    },
    // 上传主图
    uploadMainImage() {
      uni.chooseImage({
        count: 9,
        sizeType: ['compressed'],
        sourceType: ['album', 'camera'],
        success: (res) => {
          this.formData.goodsGalleryList = []
          res.tempFilePaths.forEach(tmpImg => {
              let fileName = tmpImg.substring(tmpImg.lastIndexOf('/') + 1);
              // 处理安卓可能的URI编码
              if(fileName.indexOf('%') > -1) {
                fileName = decodeURIComponent(fileName);
              }
              const fileKey = getFileKey(fileName);
              this.cosClient.uploadFile({
                   Bucket: this.bucket,
                   Region: this.region,
                   Key: fileKey,
                   FilePath: tmpImg,
                   SliceSize: 1024 * 1024 * 5     /* 触发分块上传的阈值,5M */
               }, (err, data) => {
                   if (err) {
                     console.log('上传失败', err);
                   } else {
                     this.formData.goodsGalleryList.push(fileKey);
                   }
               });
          })
        }
      })
    },
    // 上传视频
    uploadVideo() {
      uni.chooseVideo({
        sourceType: ['album', 'camera'],
        compressed: true,
        maxDuration: 30,
        success: (res) => {
          const tempFilePath = res.tempFilePath
          let fileName = tempFilePath.substring(tempFilePath.lastIndexOf('/') + 1);
          const fileKey = getFileKey(fileName);
          this.videoUploading = true
          this.cosClient.uploadFile({
               Bucket: this.bucket,
               Region: this.region,
               Key: fileKey,
               FilePath: tempFilePath,
               SliceSize: 1024 * 1024 * 5,     /* 触发分块上传的阈值,5M */
               onProgress: (progressData) => {
                    console.log(progressData.percent);
                    this.videoUploadProgress = progressData.percent * 100
               }
           }, (err, data) => {
               if (err) {
                console.log('上传失败', err);
               } else {
                  this.formData.goodsVideo = fileKey
               }
               this.videoUploading = false
           });
        }
      })
    },
    // 添规格项
    addSpec() {
      this.formData.specs.push({
        name: '',
        values: ['']
      })
    },
    // 删除规格项
    removeSpec(index) {
      this.formData.specs.splice(index, 1)
      this.generateSkuList()
    },
    // 添加规格值
    addSpecValue(specIndex) {
      this.formData.specs[specIndex].values.push('')
    },
    // 删除规格值
    removeSpecValue(specIndex, valueIndex) {
      this.formData.specs[specIndex].values.splice(valueIndex, 1)
      if (this.formData.specs[specIndex].values.length === 0) {
        this.removeSpec(specIndex)
      } else {
        this.generateSkuList()
      }
    },
    // 生成SKU列表
    generateSkuList() {
      // 先过滤掉没有名称的规格项和没有值的规格值
      const validSpecs = this.formData.specs.filter(spec =>
        spec.name && spec.values.filter(v => v).length > 0
      )
      if (validSpecs.length === 0) {
        this.formData.skuList = []
        return
      }
      // 生成所有可能的组合
      let combinations = [{
        price: '',
        weight: '',
        quantity: '',
        sn: '',
        specValues: []  // 保留specValues数组
      }]
      validSpecs.forEach(spec => {
        const newCombinations = []
        combinations.forEach(combination => {
          spec.values.filter(v => v).forEach(value => {
            // 创建新的组合对象
            const newCombination = {
              ...combination,
              [spec.name]: value,  // 添加规格键值对
              specValues: [...combination.specValues, value]  // 保留规格值数组
            }
            newCombinations.push(newCombination)
          })
        })
        combinations = newCombinations
      })
      // 保留已有的SKU数据
      const existingSkus = this.formData.skuList || []
      const newSkuList = combinations.map(comb => {
        // 查找匹配的现有SKU(同时检查specValues和规格键值对)
        const existingSku = existingSkus.find(sku => {
          // 检查specValues数组是否匹配
          const specValuesMatch = sku.specValues && comb.specValues &&
            sku.specValues.length === comb.specValues.length &&
            sku.specValues.every((v, i) => v === comb.specValues[i])
          // 检查所有规格键是否匹配
          const specsMatch = validSpecs.every(spec => {
            return sku[spec.name] === comb[spec.name]
          })
          return specValuesMatch && specsMatch
        })
        return existingSku || comb
      })
      this.formData.skuList = newSkuList
    },
    // 物流模板选择
    onTemplateChange(e) {
      this.templateIndex = e.detail.value
      console.log("选中的物流模板", this.templateOptions[this.templateIndex]);
      this.shippingTemplate = this.templateOptions[this.templateIndex]
    },
    // 编辑器准备就绪
    onEditorReady() {
      uni.createSelectorQuery().select('#editor').context((res) => {
        this.editorCtx = res.context
      }).exec()
    },
    // 编辑器内容变化
    onEditorInput(e) {
      this.formData.mobileIntro = e.detail.html
    },
    // 表单验证
    validateForm() {
      if (!this.formData.goodsName) {
        uni.showToast({ title: '请输入商品名称', icon: 'none' })
        return false
      }
      if (!this.formData.price) {
        uni.showToast({ title: '请输入商品价格', icon: 'none' })
        return false
      }
      if (!this.formData.commission) {
        uni.showToast({ title: '请输入抽成比例', icon: 'none' })
        return false
      }
      if (!this.goodsUnit) {
        uni.showToast({ title: '请选择计量单位', icon: 'none' })
        return false
      }
      if (this.formData.salesModel === 'PRESALE' && !this.formData.presaleEndDate) {
        uni.showToast({ title: '请选择预售截止日期', icon: 'none' })
        return false
      }
      if (!this.formData.goodsGalleryList) {
        uni.showToast({ title: '请上传商品主图', icon: 'none' })
        return false
      }
      if (!this.formData.goodsVideo) {
        uni.showToast({ title: '请上传商品视频', icon: 'none' })
        return false
      }
      // 验证规格项
      for (const spec of this.formData.specs) {
        if (!spec.name) {
          uni.showToast({ title: '请填写规格项名称', icon: 'none' })
          return false
        }
        if (spec.values.filter(v => v).length === 0) {
          uni.showToast({ title: '每个规格项至少需要一个规格值', icon: 'none' })
          return false
        }
      }
      // 验证SKU
      if (this.formData.skuList.length > 0) {
        for (const sku of this.formData.skuList) {
          if (!sku.price) {
            uni.showToast({ title: '请填写所有规格的价格', icon: 'none' })
            return false
          }
          if (!sku.quantity) {
            uni.showToast({ title: '请填写所有规格的库存', icon: 'none' })
            return false
          }
        }
      }
      if (!this.shippingTemplate) {
        uni.showToast({ title: '请选择物流模板', icon: 'none' })
        return false
      }
      return true
    },
    // 提交表单
    submitForm() {
      if (!this.validateForm()) return
      const data = this.formData
      if (data.skuList) {
          data.skuList.forEach(sku => {
              sku['cost'] = 1
              sku['images'] = []
          })
      }
      data.preSaleTime = [this.formData.presaleStartDate, this.formData.presaleEndDate]
      data.templateId = this.shippingTemplate.id
      data.goodsUnit = this.goodsUnit.name
      this.$emit("submit", data)
    }
  },
  watch: {
    'formData.specs': {
      deep: true,
      handler() {
        this.generateSkuList()
      }
    }
  }
}
</script>
<style scoped>
.add-product-container {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: #f5f5f5;
}
.scroll-view {
  flex: 1;
  height: 100%;
}
.form-section {
  padding: 20rpx 0rpx;
}
.section-title {
  font-size: 32rpx;
  font-weight: bold;
  margin: 30rpx 0 20rpx;
  color: #333;
  padding-left: 10rpx;
  border-left: 6rpx solid #409eff;
}
.form-item {
  background-color: #fff;
  padding: 25rpx;
  margin-bottom: 20rpx;
  border-radius: 12rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.label {
  display: block;
  font-size: 28rpx;
  color: #666;
  margin-bottom: 15rpx;
}
.input {
  height: 80rpx;
  font-size: 28rpx;
  border: 1rpx solid #eee;
  border-radius: 8rpx;
  padding: 0 20rpx;
  background-color: #f9f9f9;
}
.textarea {
  height: 150rpx;
  font-size: 28rpx;
  border: 1rpx solid #eee;
  border-radius: 8rpx;
  padding: 20rpx;
  background-color: #f9f9f9;
}
.unit {
  margin-left: 10rpx;
  color: #999;
}
.picker {
  height: 80rpx;
  line-height: 80rpx;
  font-size: 28rpx;
  border: 1rpx solid #eee;
  border-radius: 8rpx;
  padding: 0 20rpx;
  background-color: #f9f9f9;
}
.picker-text {
  color: #333;
}
.date-range-picker {
  display: flex;
  justify-content: space-between;
}
.date-picker-item {
  width: 48%;
}
.date-label {
  display: block;
  font-size: 26rpx;
  color: #666;
  margin-bottom: 10rpx;
}
.date-range-hint {
  font-size: 24rpx;
  color: #999;
  margin-top: 10rpx;
  text-align: right;
}
.radio-group {
  display: flex;
}
.radio-label {
  margin-right: 40rpx;
  font-size: 28rpx;
}
.upload-container {
  margin-top: 15rpx;
}
.upload-btn {
  width: 200rpx;
  height: 200rpx;
  border: 1rpx dashed #ccc;
  border-radius: 8rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: #999;
}
.upload-text {
  margin-top: 10rpx;
  font-size: 24rpx;
}
.uploaded-image {
  width: 200rpx;
  height: 200rpx;
  border-radius: 8rpx;
}
.uploaded-video {
  width: 100%;
  height: 400rpx;
  border-radius: 8rpx;
}
.specs-container {
  margin-bottom: 30rpx;
}
.spec-item {
  background-color: #fff;
  padding: 20rpx;
  margin-bottom: 20rpx;
  border-radius: 12rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.spec-header {
  display: flex;
  align-items: center;
  margin-bottom: 20rpx;
}
.spec-input {
  flex: 1;
  height: 70rpx;
  font-size: 28rpx;
  border: 1rpx solid #eee;
  border-radius: 8rpx;
  padding: 0 20rpx;
  background-color: #f9f9f9;
  margin-right: 20rpx;
}
.spec-values {
  display: flex;
  flex-wrap: wrap;
}
.spec-value-tag {
  display: flex;
  align-items: center;
  background-color: #f5f7fa;
  padding: 10rpx 20rpx;
  border-radius: 40rpx;
  margin-right: 15rpx;
  margin-bottom: 15rpx;
}
.value-input {
  width: 120rpx;
  font-size: 26rpx;
  background-color: transparent;
  margin-right: 10rpx;
}
.add-value-btn {
  display: flex;
  align-items: center;
  color: #409eff;
  font-size: 26rpx;
  padding: 10rpx 15rpx;
}
.add-spec-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #409eff;
  font-size: 28rpx;
  padding: 20rpx;
  border: 1rpx dashed #409eff;
  border-radius: 8rpx;
  margin-top: 10rpx;
}
.sku-container {
  margin-bottom: 30rpx;
}
.sku-item {
  background-color: #fff;
  padding: 20rpx;
  margin-bottom: 20rpx;
  border-radius: 12rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.sku-title {
  font-size: 28rpx;
  color: #333;
  margin-bottom: 20rpx;
  font-weight: bold;
}
.sku-form {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}
.sku-form-item {
  width: 48%;
  margin-bottom: 15rpx;
}
.sku-label {
  display: block;
  font-size: 26rpx;
  color: #666;
  margin-bottom: 10rpx;
}
.sku-input {
  height: 70rpx;
  font-size: 26rpx;
  border: 1rpx solid #eee;
  border-radius: 8rpx;
  padding: 0 20rpx;
  background-color: #f9f9f9;
  width: 100%;
}
.sku-unit {
  margin-left: 10rpx;
  color: #999;
  font-size: 26rpx;
}
.editor {
  height: 400rpx;
  background-color: #f9f9f9;
  border: 1rpx solid #eee;
  border-radius: 8rpx;
  padding: 20rpx;
}
.submit-btn-container {
  padding-bottom: 150px;
  background-color: #fff;
}
.submit-btn {
  width: 100%;
  height: 90rpx;
  line-height: 90rpx;
  font-size: 32rpx;
  border-radius: 45rpx;
  background-color: #409eff;
}
</style>
pages/supplier/publish-goods/publishGoods.vue
@@ -1,30 +1,78 @@
<template>
    <view class="container">
        <uni-steps :options="publishSteps" :active="currentStep" active-color='#f31947'></uni-steps>
        <choose-categery v-if="currentStep===0"></choose-categery>
        <choose-categery @choose="chooseCategrey" v-show="currentStep === 0"></choose-categery>
        <goods-info @submit="submit" v-show="currentStep === 1"></goods-info>
        <view style="position: fixed;bottom: 80px;display: flex;width: calc(100% - 70rpx);">
            <button :disabled="currentStep === 0" @click="lastStep" size="mini" style="flex: 1;font-size: 32rpx;margin-right: 3rpx;" type="primary">上一步</button>
            <button :disabled="currentStep === 1" @click="nextStep" size="mini" style="flex: 1;font-size: 32rpx;margin-left: 3rpx;" type="primary">下一步</button>
        </view>
    </view>
</template>
<script>
    import chooseCategery from './chooseCategery.vue';
    import goodsInfo from './goodsInfo.vue';
    import {createGoods} from "@/api/store.js"
    export default {
        components: { chooseCategery },
        components: { chooseCategery, goodsInfo },
        data() {
            return {
                goods: {
                },
                categoryPath: '',
                currentStep:0,
                publishSteps: [{
                        title: ' 选择商品品类'
                    },
                    {
                        title: ' 填写商品详情'
                    },
                    {
                        title: ' 商品发布成功'
                    }
                ],
            }
        },
        methods: {
            // 获取选择的商品分类
            chooseCategrey(categreyId) {
                this.categoryPath = categreyId + ",,"
            },
            submit(data) {
                if (! this.categoryPath) {
                    uni.showToast({ title: '请选中商品分类', icon: 'none' })
                    return
                }
                data['categoryPath'] = this.categoryPath
                if (data.skuList) {
                    data.skuList.forEach(sku => {
                        delete sku.specValues
                    })
                }
                delete data.specs
                // 发布商品
                console.log("商品数据", data);
                createGoods(data).then(res => {
                    uni.showToast({ title: '商品已提交审核', icon: 'success' })
                    setTimeout(() => {
                        uni.navigateBack({
                            delta: 1
                        });
                    }, 2000)
                })
            },
            // 上一步
            lastStep() {
                if (this.currentStep > 0) {
                    this.currentStep--
                }
            },
            // 下一步
            nextStep() {
                if (this.currentStep < 1) {
                    this.currentStep++
                }
            }
        }
    }
</script>
pages/tabbar/index/home.vue
@@ -367,6 +367,9 @@
      // } else {
      //       this.loadVideos();
      // }
      if (this.videoList.length < 1) {
          this.loadVideos();
      }
      // 如果视频按下暂停后切换页面再回到页面时,只算暂停时间(因为暂停时间和离开页面时间是重复的,只算一个)
      if(this.startHidenTime !== 0 && this.currentVideoIsPlaying) {
          const duration = Date.now() - this.startHidenTime