New file |
| | |
| | | <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> |