|
|
@@ -0,0 +1,705 @@
|
|
|
+<template>
|
|
|
+ <view class="page">
|
|
|
+ <u-navbar :title="isEdit ? '编辑菜品' : '新建菜品'" :autoBack="true" :placeholder="true"></u-navbar>
|
|
|
+
|
|
|
+ <view class="card">
|
|
|
+ <view class="section-title">基本信息</view>
|
|
|
+ <view class="row">
|
|
|
+ <text class="label required">商品归属</text>
|
|
|
+ <u-radio-group v-model="form.belong" :disabled="isEdit" @change="onBelongChange">
|
|
|
+ <u-radio name="store" active-color="#449AFF">门店</u-radio>
|
|
|
+ <u-radio v-if="showGroupBelong" name="group" active-color="#449AFF">团餐</u-radio>
|
|
|
+ </u-radio-group>
|
|
|
+ </view>
|
|
|
+ <view class="row" @click="pickCategory">
|
|
|
+ <text class="label required">商品分类</text>
|
|
|
+ <text class="value">{{ categoryName || '请选择' }}</text>
|
|
|
+ <u-icon name="arrow-right" color="#C7C6CA"></u-icon>
|
|
|
+ </view>
|
|
|
+ <view class="row">
|
|
|
+ <text class="label required">商品名称</text>
|
|
|
+ <input class="input" v-model="form.name" placeholder="请输入" />
|
|
|
+ </view>
|
|
|
+ <view class="row" @click="pickProductType">
|
|
|
+ <text class="label required">商品类型</text>
|
|
|
+ <text class="value">{{ productTypeName || '请选择' }}</text>
|
|
|
+ <u-icon name="arrow-right" color="#C7C6CA"></u-icon>
|
|
|
+ </view>
|
|
|
+ <view class="upload-row">
|
|
|
+ <text class="label required">商品封面图</text>
|
|
|
+ <view class="tip">支持jpg、png格式,建议尺寸750px*750px</view>
|
|
|
+ <view class="upload-box" @click="pickImage('cover')">
|
|
|
+ <image v-if="form.cover" :src="form.cover" mode="aspectFill"></image>
|
|
|
+ <template v-else>
|
|
|
+ <text class="plus">+</text>
|
|
|
+ <text>上传图片</text>
|
|
|
+ </template>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="upload-row">
|
|
|
+ <text class="label required">商品详情图</text>
|
|
|
+ <view class="tip">支持jpg、png格式</view>
|
|
|
+ <view class="upload-box" @click="pickImage('detail')">
|
|
|
+ <image v-if="form.detail" :src="form.detail" mode="aspectFill"></image>
|
|
|
+ <template v-else>
|
|
|
+ <text class="plus">+</text>
|
|
|
+ <text>上传图片</text>
|
|
|
+ </template>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="card">
|
|
|
+ <view class="row">
|
|
|
+ <text class="label required">是否为套餐</text>
|
|
|
+ <u-radio-group v-model="form.isCombo" @change="onComboChange">
|
|
|
+ <u-radio :name="1" active-color="#449AFF">是</u-radio>
|
|
|
+ <u-radio :name="0" active-color="#449AFF">否</u-radio>
|
|
|
+ </u-radio-group>
|
|
|
+ </view>
|
|
|
+ <view class="row" v-if="Number(form.isCombo) === 1" @click="goCombo">
|
|
|
+ <text class="label">套餐</text>
|
|
|
+ <text class="value link">{{ comboCount ? `共${comboCount}项` : '去添加' }}</text>
|
|
|
+ <u-icon name="arrow-right" color="#C7C6CA"></u-icon>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="card">
|
|
|
+ <view class="section-title">销售规格</view>
|
|
|
+ <view class="row">
|
|
|
+ <text class="label required">销售规格</text>
|
|
|
+ <u-radio-group v-model="form.specMode">
|
|
|
+ <u-radio name="single" active-color="#449AFF">单规格</u-radio>
|
|
|
+ <u-radio name="multi" active-color="#449AFF" :disabled="specLocked">多规格</u-radio>
|
|
|
+ </u-radio-group>
|
|
|
+ </view>
|
|
|
+ <view class="spec-tip" v-if="specLocked">当前归属为团餐或已设为套餐,仅支持单规格</view>
|
|
|
+
|
|
|
+ <!-- 单规格价格 -->
|
|
|
+ <template v-if="form.specMode === 'single'">
|
|
|
+ <view class="row">
|
|
|
+ <text class="label required">售价 (元)</text>
|
|
|
+ <input class="input" type="digit" v-model="form.price" placeholder="请输入" />
|
|
|
+ </view>
|
|
|
+ <view class="row">
|
|
|
+ <text class="label required">原价 (元)</text>
|
|
|
+ <input class="input" type="digit" v-model="form.originPrice" placeholder="请输入" />
|
|
|
+ </view>
|
|
|
+ <view class="row">
|
|
|
+ <text class="label required">可售库存</text>
|
|
|
+ <input class="input" type="number" v-model="form.stock" placeholder="请输入" />
|
|
|
+ </view>
|
|
|
+ <view class="row">
|
|
|
+ <text class="label">成本价 (元)</text>
|
|
|
+ <input class="input" type="digit" v-model="form.costPrice" placeholder="请输入 (选填)" />
|
|
|
+ </view>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 多规格 -->
|
|
|
+ <template v-else>
|
|
|
+ <view class="spec-block" v-for="(spec, si) in form.specs" :key="spec.id">
|
|
|
+ <view class="spec-head">
|
|
|
+ <text>{{ spec.name }}</text>
|
|
|
+ <text class="more" @click="openSpecMenu(si)">···</text>
|
|
|
+ </view>
|
|
|
+ <view class="tags">
|
|
|
+ <view
|
|
|
+ v-for="(v, vi) in spec.values"
|
|
|
+ :key="v.id"
|
|
|
+ :class="['tag', v.selected ? 'on' : '']"
|
|
|
+ @click="v.selected = !v.selected"
|
|
|
+ >{{ v.name }}</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="add-spec" @click="addSpec">+ 添加规格</view>
|
|
|
+ </template>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="footer">
|
|
|
+ <u-button type="primary" shape="circle" :disabled="!canNext" @click="onNext">下一步</u-button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 规格操作菜单 -->
|
|
|
+ <u-action-sheet :list="specMenuList" v-model="specMenuShow" @click="onSpecMenu"></u-action-sheet>
|
|
|
+
|
|
|
+ <!-- 编辑规格名 -->
|
|
|
+ <view class="mask" v-if="nameModal" @click="nameModal = false">
|
|
|
+ <view class="dialog" @click.stop>
|
|
|
+ <view class="dialog-title">
|
|
|
+ 编辑规格名
|
|
|
+ <text class="dialog-close" @click="nameModal = false">×</text>
|
|
|
+ </view>
|
|
|
+ <input class="dialog-input" v-model="editingName" placeholder="请输入规格名称" />
|
|
|
+ <view class="dialog-footer">
|
|
|
+ <view class="dialog-btn" @click="nameModal = false">取消</view>
|
|
|
+ <view class="dialog-btn primary" @click="saveSpecName">确认</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 编辑规格值:列表 -->
|
|
|
+ <view class="mask" v-if="valueListShow" @click="valueListShow = false">
|
|
|
+ <view class="dialog list-dialog" @click.stop>
|
|
|
+ <view class="dialog-title">
|
|
|
+ 编辑规格值
|
|
|
+ <text class="dialog-close" @click="valueListShow = false">×</text>
|
|
|
+ </view>
|
|
|
+ <view class="vp-item" v-for="(v, i) in editingValues" :key="v.id" @click="editOneValue(i)">
|
|
|
+ <text>{{ v.name }}</text>
|
|
|
+ <u-icon name="edit-pen" color="#999" size="32"></u-icon>
|
|
|
+ </view>
|
|
|
+ <view class="vp-confirm" @click="confirmValues">确定</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 编辑规格值:单条 -->
|
|
|
+ <view class="mask top-mask" v-if="valueEditModal" @click="valueEditModal = false">
|
|
|
+ <view class="dialog" @click.stop>
|
|
|
+ <view class="dialog-title">
|
|
|
+ 编辑规格值
|
|
|
+ <text class="dialog-close" @click="valueEditModal = false">×</text>
|
|
|
+ </view>
|
|
|
+ <input class="dialog-input" v-model="editingValueName" placeholder="请输入规格值" focus />
|
|
|
+ <view class="dialog-footer">
|
|
|
+ <view class="dialog-btn" @click="valueEditModal = false">取消</view>
|
|
|
+ <view class="dialog-btn primary" @click="saveOneValue">确认</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <u-action-sheet :list="categorySheet" v-model="categoryShow" @click="onCategoryPick"></u-action-sheet>
|
|
|
+ <u-action-sheet :list="typeSheet" v-model="typeShow" @click="onTypePick"></u-action-sheet>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { createMockDishes, buildSpecCombos } from '../mock.js';
|
|
|
+
|
|
|
+const DISH_KEY = 'merchantDish_list';
|
|
|
+const DRAFT_KEY = 'merchantDish_draft';
|
|
|
+
|
|
|
+const CATEGORY_OPTIONS = [
|
|
|
+ { label: '时令美味', value: 'seasonal' },
|
|
|
+ { label: '家常菜', value: 'home' },
|
|
|
+ { label: '主食', value: 'staple' }
|
|
|
+];
|
|
|
+
|
|
|
+const PRODUCT_TYPES = [
|
|
|
+ { label: '早', value: 'breakfast' },
|
|
|
+ { label: '午', value: 'lunch' },
|
|
|
+ { label: '晚', value: 'dinner' }
|
|
|
+];
|
|
|
+
|
|
|
+const DEFAULT_SPECS = [
|
|
|
+ {
|
|
|
+ id: 's1',
|
|
|
+ name: '份量',
|
|
|
+ values: [
|
|
|
+ { id: 'v1', name: '小份', selected: true },
|
|
|
+ { id: 'v2', name: '中份', selected: true },
|
|
|
+ { id: 'v3', name: '大份', selected: false }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 's2',
|
|
|
+ name: '口味',
|
|
|
+ values: [
|
|
|
+ { id: 'v4', name: '微辣', selected: false },
|
|
|
+ { id: 'v5', name: '中辣', selected: true },
|
|
|
+ { id: 'v6', name: '特辣', selected: false }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 's3',
|
|
|
+ name: '温度',
|
|
|
+ values: [
|
|
|
+ { id: 'v7', name: '温', selected: false },
|
|
|
+ { id: 'v8', name: '冰', selected: false },
|
|
|
+ { id: 'v9', name: '热', selected: false }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+];
|
|
|
+
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ isEdit: false,
|
|
|
+ editId: null,
|
|
|
+ // 是否开通团餐功能(审核通过后可用团餐归属)
|
|
|
+ groupMealOpened: false,
|
|
|
+ form: {
|
|
|
+ belong: 'store',
|
|
|
+ category: 'seasonal',
|
|
|
+ name: '',
|
|
|
+ productType: 'breakfast',
|
|
|
+ cover: '',
|
|
|
+ detail: '',
|
|
|
+ isCombo: 0,
|
|
|
+ comboItems: [],
|
|
|
+ specMode: 'single',
|
|
|
+ price: '',
|
|
|
+ originPrice: '',
|
|
|
+ stock: '',
|
|
|
+ costPrice: '',
|
|
|
+ specs: JSON.parse(JSON.stringify(DEFAULT_SPECS))
|
|
|
+ },
|
|
|
+ categoryShow: false,
|
|
|
+ typeShow: false,
|
|
|
+ categorySheet: CATEGORY_OPTIONS.map((i) => ({ text: i.label, value: i.value })),
|
|
|
+ typeSheet: PRODUCT_TYPES.map((i) => ({ text: i.label, value: i.value })),
|
|
|
+ specMenuShow: false,
|
|
|
+ specMenuList: [{ text: '编辑规格名' }, { text: '编辑规格值' }],
|
|
|
+ activeSpecIndex: 0,
|
|
|
+ nameModal: false,
|
|
|
+ editingName: '',
|
|
|
+ valueListShow: false,
|
|
|
+ editingValues: [],
|
|
|
+ valueEditModal: false,
|
|
|
+ editingValueIndex: 0,
|
|
|
+ editingValueName: ''
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ /** 开通团餐才展示「团餐」归属选项 */
|
|
|
+ showGroupBelong() {
|
|
|
+ return this.groupMealOpened;
|
|
|
+ },
|
|
|
+ categoryName() {
|
|
|
+ const hit = CATEGORY_OPTIONS.find((i) => i.value === this.form.category);
|
|
|
+ return hit ? hit.label : '';
|
|
|
+ },
|
|
|
+ productTypeName() {
|
|
|
+ const hit = PRODUCT_TYPES.find((i) => i.value === this.form.productType);
|
|
|
+ return hit ? hit.label : '';
|
|
|
+ },
|
|
|
+ specLocked() {
|
|
|
+ // 归属团餐 → 强制单规格;套餐同样仅单规格
|
|
|
+ return this.form.belong === 'group' || Number(this.form.isCombo) === 1;
|
|
|
+ },
|
|
|
+ comboCount() {
|
|
|
+ return (this.form.comboItems || []).reduce((n, c) => n + (c.items || []).filter((i) => i.name).length, 0);
|
|
|
+ },
|
|
|
+ canNext() {
|
|
|
+ const f = this.form;
|
|
|
+ if (!f.belong || !f.category || !f.name || !f.productType || !f.cover || !f.detail) return false;
|
|
|
+ if (Number(f.isCombo) === 1 && !this.comboCount) return false;
|
|
|
+ if (this.specLocked && f.specMode !== 'single') return false;
|
|
|
+ if (f.specMode === 'single') {
|
|
|
+ return f.price !== '' && f.originPrice !== '' && f.stock !== '';
|
|
|
+ }
|
|
|
+ const hasSelected = (f.specs || []).some((s) => (s.values || []).some((v) => v.selected));
|
|
|
+ return hasSelected;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad(query) {
|
|
|
+ this.initGroupMealFlag();
|
|
|
+ if (query.id) {
|
|
|
+ this.isEdit = true;
|
|
|
+ this.editId = Number(query.id);
|
|
|
+ this.loadEdit();
|
|
|
+ } else {
|
|
|
+ // 新建默认门店;未开通团餐时不可选团餐
|
|
|
+ this.form.belong = 'store';
|
|
|
+ }
|
|
|
+ this.applySpecLock();
|
|
|
+ },
|
|
|
+ onShow() {
|
|
|
+ this.initGroupMealFlag();
|
|
|
+ const combo = uni.getStorageSync('merchantDish_combo_temp');
|
|
|
+ if (combo) {
|
|
|
+ this.form.comboItems = combo;
|
|
|
+ uni.removeStorageSync('merchantDish_combo_temp');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ initGroupMealFlag() {
|
|
|
+ // 未报名/审核中/驳回:商品归属只显示「门店」;审核通过(1) 才显示「团餐」
|
|
|
+ let applyStatus = 0;
|
|
|
+ const cache = uni.getStorageSync('merchantDish_applyStatus');
|
|
|
+ if (cache !== '' && cache !== undefined && cache !== null) {
|
|
|
+ applyStatus = Number(cache);
|
|
|
+ }
|
|
|
+ this.groupMealOpened = applyStatus === 1;
|
|
|
+ if (!this.groupMealOpened && this.form.belong === 'group' && !this.isEdit) {
|
|
|
+ this.form.belong = 'store';
|
|
|
+ }
|
|
|
+ },
|
|
|
+ loadEdit() {
|
|
|
+ const list = uni.getStorageSync(DISH_KEY) || createMockDishes();
|
|
|
+ const item = list.find((i) => i.id === this.editId);
|
|
|
+ if (!item) return;
|
|
|
+ this.form.belong = item.belong;
|
|
|
+ this.form.category = item.category;
|
|
|
+ this.form.name = item.name;
|
|
|
+ this.form.productType = item.productType;
|
|
|
+ this.form.cover = item.image;
|
|
|
+ this.form.detail = item.image;
|
|
|
+ this.form.isCombo = item.isCombo ? 1 : 0;
|
|
|
+ this.form.price = String(item.price);
|
|
|
+ this.form.originPrice = String(item.originPrice);
|
|
|
+ this.form.stock = String(item.stock);
|
|
|
+ this.form.specMode = 'single';
|
|
|
+ this.applySpecLock();
|
|
|
+ },
|
|
|
+ onBelongChange(val) {
|
|
|
+ const belong = val || this.form.belong;
|
|
|
+ this.form.belong = belong;
|
|
|
+ this.applySpecLock();
|
|
|
+ },
|
|
|
+ onComboChange(val) {
|
|
|
+ this.form.isCombo = val === undefined ? this.form.isCombo : val;
|
|
|
+ this.applySpecLock();
|
|
|
+ },
|
|
|
+ applySpecLock() {
|
|
|
+ if (this.specLocked) {
|
|
|
+ this.form.specMode = 'single';
|
|
|
+ }
|
|
|
+ },
|
|
|
+ pickCategory() {
|
|
|
+ this.categoryShow = true;
|
|
|
+ },
|
|
|
+ onCategoryPick(e) {
|
|
|
+ this.form.category = e.value;
|
|
|
+ },
|
|
|
+ pickProductType() {
|
|
|
+ this.typeShow = true;
|
|
|
+ },
|
|
|
+ onTypePick(e) {
|
|
|
+ this.form.productType = e.value;
|
|
|
+ },
|
|
|
+ pickImage(field) {
|
|
|
+ uni.chooseImage({
|
|
|
+ count: 1,
|
|
|
+ success: (res) => {
|
|
|
+ this.form[field] = res.tempFilePaths[0];
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ goCombo() {
|
|
|
+ uni.setStorageSync('merchantDish_combo_editing', this.form.comboItems || []);
|
|
|
+ uni.navigateTo({ url: '/pages/merchantDish/dish/combo' });
|
|
|
+ },
|
|
|
+ openSpecMenu(si) {
|
|
|
+ this.activeSpecIndex = si;
|
|
|
+ this.specMenuShow = true;
|
|
|
+ },
|
|
|
+ onSpecMenu(e) {
|
|
|
+ const index = typeof e === 'object' ? e.index : e;
|
|
|
+ const spec = this.form.specs[this.activeSpecIndex];
|
|
|
+ if (!spec) return;
|
|
|
+ if (index === 0) {
|
|
|
+ this.editingName = spec.name || '';
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.nameModal = true;
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.editingValues = JSON.parse(JSON.stringify(spec.values || []));
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.valueListShow = true;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ saveSpecName() {
|
|
|
+ if (!this.editingName.trim()) {
|
|
|
+ uni.showToast({ title: '请输入规格名称', icon: 'none' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.form.specs[this.activeSpecIndex].name = this.editingName.trim();
|
|
|
+ this.nameModal = false;
|
|
|
+ },
|
|
|
+ editOneValue(i) {
|
|
|
+ this.editingValueIndex = i;
|
|
|
+ this.editingValueName = (this.editingValues[i] && this.editingValues[i].name) || '';
|
|
|
+ this.valueEditModal = true;
|
|
|
+ },
|
|
|
+ saveOneValue() {
|
|
|
+ if (!this.editingValueName.trim()) {
|
|
|
+ uni.showToast({ title: '请输入规格值', icon: 'none' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.$set(this.editingValues[this.editingValueIndex], 'name', this.editingValueName.trim());
|
|
|
+ this.valueEditModal = false;
|
|
|
+ },
|
|
|
+ confirmValues() {
|
|
|
+ this.form.specs[this.activeSpecIndex].values = this.editingValues;
|
|
|
+ this.valueListShow = false;
|
|
|
+ },
|
|
|
+ addSpec() {
|
|
|
+ const id = `s${Date.now()}`;
|
|
|
+ this.form.specs.push({
|
|
|
+ id,
|
|
|
+ name: '新规格',
|
|
|
+ values: [
|
|
|
+ { id: `${id}_1`, name: '选项1', selected: true },
|
|
|
+ { id: `${id}_2`, name: '选项2', selected: false }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onNext() {
|
|
|
+ if (!this.canNext) {
|
|
|
+ uni.showToast({ title: '请完善必填信息', icon: 'none' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const draft = {
|
|
|
+ ...this.form,
|
|
|
+ isEdit: this.isEdit,
|
|
|
+ editId: this.editId,
|
|
|
+ combos: this.form.specMode === 'multi' ? buildSpecCombos(this.form.specs) : []
|
|
|
+ };
|
|
|
+ uni.setStorageSync(DRAFT_KEY, draft);
|
|
|
+ if (this.form.specMode === 'multi') {
|
|
|
+ uni.navigateTo({ url: '/pages/merchantDish/dish/spec-detail' });
|
|
|
+ } else {
|
|
|
+ uni.navigateTo({ url: '/pages/merchantDish/dish/sale-time?from=single' });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.page {
|
|
|
+ min-height: 100vh;
|
|
|
+ background: #f7f8fc;
|
|
|
+ padding-bottom: 160rpx;
|
|
|
+}
|
|
|
+.card {
|
|
|
+ background: #fff;
|
|
|
+ margin: 20rpx 24rpx;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 8rpx 24rpx 16rpx;
|
|
|
+}
|
|
|
+.section-title {
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #222;
|
|
|
+ padding: 20rpx 0 8rpx;
|
|
|
+}
|
|
|
+.row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ min-height: 96rpx;
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
+ .label {
|
|
|
+ width: 200rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ flex-shrink: 0;
|
|
|
+ &.required::before {
|
|
|
+ content: '*';
|
|
|
+ color: #ff4d4f;
|
|
|
+ margin-right: 4rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .value {
|
|
|
+ flex: 1;
|
|
|
+ text-align: right;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #666;
|
|
|
+ &.link {
|
|
|
+ color: #449aff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .input {
|
|
|
+ flex: 1;
|
|
|
+ text-align: right;
|
|
|
+ font-size: 28rpx;
|
|
|
+ }
|
|
|
+}
|
|
|
+.spec-tip {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+ padding: 0 0 16rpx;
|
|
|
+}
|
|
|
+.upload-row {
|
|
|
+ padding: 20rpx 0;
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
+ .label {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ &.required::before {
|
|
|
+ content: '*';
|
|
|
+ color: #ff4d4f;
|
|
|
+ margin-right: 4rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .tip {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+ margin: 8rpx 0 16rpx;
|
|
|
+ }
|
|
|
+ .upload-box {
|
|
|
+ width: 160rpx;
|
|
|
+ height: 160rpx;
|
|
|
+ background: #f7f8fc;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+ overflow: hidden;
|
|
|
+ .plus {
|
|
|
+ font-size: 48rpx;
|
|
|
+ line-height: 1;
|
|
|
+ margin-bottom: 4rpx;
|
|
|
+ }
|
|
|
+ image {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.spec-block {
|
|
|
+ padding: 16rpx 0;
|
|
|
+ border-bottom: 1rpx solid #f5f5f5;
|
|
|
+ .spec-head {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+ .more {
|
|
|
+ letter-spacing: 2rpx;
|
|
|
+ color: #999;
|
|
|
+ padding: 0 8rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .tags {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16rpx;
|
|
|
+ }
|
|
|
+ .tag {
|
|
|
+ padding: 10rpx 28rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ background: #f5f5f5;
|
|
|
+ color: #666;
|
|
|
+ &.on {
|
|
|
+ background: rgba(68, 154, 255, 0.12);
|
|
|
+ color: #449aff;
|
|
|
+ border: 1rpx solid #449aff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.add-spec {
|
|
|
+ padding: 24rpx 0;
|
|
|
+ text-align: center;
|
|
|
+ color: #449aff;
|
|
|
+ font-size: 28rpx;
|
|
|
+}
|
|
|
+.footer {
|
|
|
+ position: fixed;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ padding: 20rpx 40rpx calc(20rpx + env(safe-area-inset-bottom));
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+.modal-body {
|
|
|
+ padding: 30rpx;
|
|
|
+}
|
|
|
+.modal-input {
|
|
|
+ border: 1rpx solid #eee;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+}
|
|
|
+.mask {
|
|
|
+ position: fixed;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ top: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.45);
|
|
|
+ z-index: 999;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ &.top-mask {
|
|
|
+ z-index: 1001;
|
|
|
+ }
|
|
|
+}
|
|
|
+.dialog {
|
|
|
+ width: 560rpx;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ .dialog-title {
|
|
|
+ position: relative;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #222;
|
|
|
+ padding: 36rpx 48rpx 20rpx;
|
|
|
+ .dialog-close {
|
|
|
+ position: absolute;
|
|
|
+ right: 24rpx;
|
|
|
+ top: 24rpx;
|
|
|
+ font-size: 40rpx;
|
|
|
+ color: #999;
|
|
|
+ line-height: 1;
|
|
|
+ font-weight: 400;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .dialog-input {
|
|
|
+ margin: 10rpx 40rpx 30rpx;
|
|
|
+ height: 80rpx;
|
|
|
+ padding: 0 24rpx;
|
|
|
+ border: 1rpx solid #e5e5e5;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+ .dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ border-top: 1rpx solid #eee;
|
|
|
+ .dialog-btn {
|
|
|
+ flex: 1;
|
|
|
+ height: 96rpx;
|
|
|
+ line-height: 96rpx;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 30rpx;
|
|
|
+ color: #333;
|
|
|
+ &:first-child {
|
|
|
+ border-right: 1rpx solid #eee;
|
|
|
+ }
|
|
|
+ &.primary {
|
|
|
+ color: #449aff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.list-dialog {
|
|
|
+ padding-bottom: 24rpx;
|
|
|
+ .vp-item {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 28rpx 40rpx;
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ .vp-confirm {
|
|
|
+ margin: 30rpx 40rpx 10rpx;
|
|
|
+ height: 80rpx;
|
|
|
+ line-height: 80rpx;
|
|
|
+ text-align: center;
|
|
|
+ background: #ff4d4f;
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ font-size: 30rpx;
|
|
|
+ }
|
|
|
+}
|
|
|
+.value-popup {
|
|
|
+ width: 600rpx;
|
|
|
+ padding: 30rpx;
|
|
|
+}
|
|
|
+::v-deep .u-radio {
|
|
|
+ margin-right: 24rpx;
|
|
|
+}
|
|
|
+</style>
|