绿满眶商城微信小程序-uniapp
peng
2025-10-11 f1d0c08bbef3ffef0b9bbd0c181d2047c9866a7f
定制模板
4个文件已修改
662 ■■■■■ 已修改文件
pages/cart/payment/payOrder.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/order/fillorder.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/product/goods.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/product/m-buy/goods.vue 613 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cart/payment/payOrder.vue
@@ -111,6 +111,7 @@
        onLoad(val) {
            this.routerVal = val;
            this.actionParam.pageParams = JSON.stringify(val)
            console.log('获取解密的参数',JSON.stringify(val),decodeURIComponent(this.routerVal.template))
            //初始化参数
            // #ifdef APP-PLUS
            this.paymentType = "APP";
@@ -185,12 +186,19 @@
                parms.sn = this.sn;
                parms.orderType = this.orderType;
                parms.clientType = this.paymentType;
                if(!this.routerVal.template){
                const template = decodeURIComponent(this.routerVal.template)
                parms.templateId = template.templateId;
                parms.templateName = template.templateName;
                parms.chooseImage = template.chooseImage;
                parms.templateForm = template.templateForm;
                }
                API_Trade.getCashierData(parms).then((res) => {
                
                    if(res.data.success){
                    this.cashierParams = res.data.result;
                    console.log('获取订单信息------------------>',this.cashierParams.orderSns)
                    // #ifdef MP-WEIXIN
                    this.payList = res.data.result.support.filter((item) => {
                        return item != "ALIPAY";
pages/order/fillorder.vue
@@ -405,6 +405,7 @@
  onLoad: function (val) {
    this.routerVal = val;
    this.actionParam.pageParams = JSON.stringify(val)
    console.log('获取的参数-----------》',JSON.stringify(decodeURIComponent(this.routerVal.template)))
  },
  components: {
    invoices,
@@ -746,7 +747,7 @@
              });
            } else {
              this.navigateTo(
                `/pages/cart/payment/payOrder?trade_sn=${res.data.result.sn}`
                `/pages/cart/payment/payOrder?trade_sn=${res.data.result.sn}&template=${this.routerVal.template}`
              );
            }
          } else {
pages/product/goods.vue
@@ -609,6 +609,18 @@
    // #ifdef MP-WEIXIN
    // #endif
    this.goodsDetail = {};
    //如果有参数ids说明事分销短连接,需要获取参数
    if (this.routerVal.scene) {
      getMpScene(this.routerVal.scene).then((res) => {
        if (res.data.success) {
          let data = res.data.result.split(","); // skuId,goodsId,distributionId
          this.init(data[0], data[1], data[2]);
        }
      });
    } else {
      this.init(this.routerVal.id, this.routerVal.goodsId, this.routerVal.distributionId);
    }
  },
  
  onUnload() {
@@ -634,18 +646,18 @@
              userAction(param)
          }
      })    
    this.goodsDetail = {};
    //如果有参数ids说明事分销短连接,需要获取参数
    if (this.routerVal.scene) {
      getMpScene(this.routerVal.scene).then((res) => {
        if (res.data.success) {
          let data = res.data.result.split(","); // skuId,goodsId,distributionId
          this.init(data[0], data[1], data[2]);
        }
      });
    } else {
      this.init(this.routerVal.id, this.routerVal.goodsId, this.routerVal.distributionId);
    }
    // this.goodsDetail = {};
    // //如果有参数ids说明事分销短连接,需要获取参数
    // if (this.routerVal.scene) {
    //   getMpScene(this.routerVal.scene).then((res) => {
    //     if (res.data.success) {
    //       let data = res.data.result.split(","); // skuId,goodsId,distributionId
    //       this.init(data[0], data[1], data[2]);
    //     }
    //   });
    // } else {
    //   this.init(this.routerVal.id, this.routerVal.goodsId, this.routerVal.distributionId);
    // }
  },
  methods: {
pages/product/m-buy/goods.vue
@@ -1,6 +1,6 @@
<template>
    <div class="wrapper">
        <u-popup class="popup" v-model="buyMask" :height="setup.height" closeable :mode="setup.mode" :border-radius="setup.radius" @close="closeMask()">
        <u-popup class="popup" :value="buyMask" :height="setup.height" closeable :mode="setup.mode" :border-radius="setup.radius" @input="handlePopupInput" @close="closeMask()">
            <!-- 商品 -->
            <view class="goods-box bottom">
                <view class="goods-header">
@@ -102,7 +102,93 @@
                        <uni-number-box class="uNumber" :min="1" :max="999" :disabled="goodsDetail.quantity === 0"  v-model="num"></uni-number-box>
                    </view>
                    <view class="template">
                        {{JSON.stringify(consumizetemplateInfo)}}
                        <view class="template-title" v-if="consumizetemplateInfo && consumizetemplateInfo.name">
                            <text class="title-text">{{ consumizetemplateInfo.name }}</text>
                        </view>
                        <!-- 图片选择区域 -->
                        <view class="template-images" v-if="consumizetemplateInfo && consumizetemplateInfo.templateImgs && consumizetemplateInfo.templateImgs.length > 0">
                            <view class="images-grid">
                                <view
                                    v-for="(img, index) in consumizetemplateInfo.templateImgs"
                                    :key="img.id"
                                    class="image-item"
                                    :class="{ selected: selectedImages.includes(img.imgUrl) }"
                                    @click="selectImage(img.imgUrl)"
                                >
                                    <image
                                        :src="getFilePreviewUrlSync(img.imgUrl)"
                                        class="image-preview"
                                        mode="aspectFill"
                                    />
                                    <view v-if="selectedImages.includes(img.imgUrl)" class="selected-overlay">
                                        <uni-icons type="checkmarkempty" size="30" color="#fff"></uni-icons>
                                    </view>
                                </view>
                            </view>
                        </view>
                        <!-- 动态表单项 -->
                        <view class="template-form" v-if="consumizetemplateInfo && consumizetemplateInfo.templateConstomizeTitles">
                            <view
                                v-for="(item, index) in consumizetemplateInfo.templateConstomizeTitles"
                                :key="item.id"
                                class="form-item"
                            >
                                <text class="form-label">{{ item.templateTitle }}</text>
                                <!-- 文本输入框 -->
                                <input
                                    v-if="item.contentType === 'TEXT'"
                                    class="form-input"
                                    :value="getFormValue(item.id)"
                                    @input="updateFormValue(item.id, item.templateTitle, $event.target.value)"
                                    :placeholder="'请输入' + item.templateTitle"
                                />
                                <!-- 图片上传(不使用u-upload组件) -->
                                <view v-if="item.contentType === 'IMAGE'" class="form-upload">
                                    <!-- 上传按钮 -->
                                    <view class="upload-btn" @click="chooseImage(item.id)">
                                        <uni-icons type="plusempty" size="40" color="#999"></uni-icons>
                                        <text class="upload-text">点击上传</text>
                                    </view>
                                    <!-- 图片预览区域 -->
                                    <view v-if="imagePreviewUrls[item.id]" class="image-preview-container">
                                        <image
                                            :src="imagePreviewUrls[item.id]"
                                            class="uploaded-image"
                                            mode="aspectFill"
                                        />
                                        <uni-icons
                                            type="closeempty"
                                            class="delete-icon"
                                            @click="deleteImage(item.id)"
                                            size="20"
                                            color="#fff"
                                        ></uni-icons>
                                    </view>
                                    <!-- 上传进度条 -->
                                    <progress
                                        v-if="uploadProgress[item.id] && uploadProgress[item.id] > 0 && uploadProgress[item.id] < 100"
                                        :percent="uploadProgress[item.id]"
                                        active-mode="forwards"
                                        show-info
                                        stroke-width="6"
                                        :active="true"
                                        active-color="#ff573e"
                                        style="margin-top: 10rpx;"
                                    />
                                    <!-- 上传状态信息 -->
                                    <text v-if="uploadStatus[item.id]" class="upload-status">
                                        {{ uploadStatus[item.id] }}
                                    </text>
                                </view>
                            </view>
                        </view>
                    </view>
                </scroll-view>
                <!-- 按钮 -->
@@ -120,6 +206,10 @@
import * as API_trade from '@/api/trade.js';
import setup from './popup';
// import uniNumberBox from '@/components/uni-number-box'
import { getFilePreviewUrl } from "@/api/common.js";
import { getSTSToken } from "@/api/common.js";
import { getFileKey } from "@/utils/file.js";
export default {
    components: {
        // uniNumberBox
@@ -137,7 +227,23 @@
            formatList: [],
            currentSelected: [],
            skuList: '',
            isClose: false //是否可以点击遮罩关闭
            isClose: false, //是否可以点击遮罩关闭
            // 表单相关数据
            selectedImages: [], // 选中的模板图片
            formValues: {
                templateId: "", // 模板ID
                templateName: "", // 模板名称
                chooseImage: "", // 选中的图片
                templateForm: [] // 表单数组 [{id, templateTitle, value}]
            }, // 表单值
            imagePreviewUrls: {}, // 图片预览URL
            uploadProgress: {}, // 上传进度
            uploadStatus: {}, // 上传状态信息
            cosClient: null, // COS客户端
            bucket: '', // 存储桶
            region: '', // 地域
            previewUrls: {} // 文件预览地址缓存
        };
    },
    props: {
@@ -211,6 +317,7 @@
        buyType: {
            handler(val) {
                if (val) {
                    // 使用直接赋值而不是this.$set
                    this.buyType = val;
                }
            },
@@ -218,39 +325,337 @@
        },
        selectSkuList: {
            handler(val, oldval) {
                // 使用setTimeout延迟触发事件,避免影响弹窗
                setTimeout(() => {
                this.$emit('changed', val);
                }, 100);
            },
            deep: true
        },
        'goodsDetail.quantity': {
            handler(val) {
                if (val == 0) {
                    // 使用setTimeout延迟显示提示,避免影响弹窗
                    setTimeout(() => {
                    uni.showToast({
                        title: '商品已售罄',
                        duration: 2000,
                        icon: 'none'
                    })
                        });
                    }, 100);
                    this.num = 1;
                }
            }
        },
        // 监听模板信息变化
        consumizetemplateInfo: {
            handler(newVal, oldVal) {
                if (newVal) {
                    // 使用setTimeout延迟执行,避免影响弹窗
                    setTimeout(() => {
                        // 预加载模板图片的预览地址
                        this.preloadTemplateImages();
                        // 初始化模板表单数据
                        this.initTemplateFormData();
                    }, 100);
                }
            },
            immediate: true,
            deep: true
        }
    },
    methods: {
        // 初始化腾讯云COS客户端
        initCOS() {
            getSTSToken().then(res => {
                const COS = require('@/lib/cos-wx-sdk-v5.js');
                // 使用直接赋值而不是this.$set
                this.cosClient = new COS({
                    SecretId: res.data.data.tmpSecretId,
                    SecretKey: res.data.data.tmpSecretKey,
                    SecurityToken: res.data.data.sessionToken,
                    StartTime: res.data.data.stsStartTime,
                    ExpiredTime: res.data.data.stsEndTime,
                    SimpleUploadMethod: 'putObject'
                });
                this.bucket = res.data.data.bucket;
                this.region = res.data.data.region;
            }).catch(err => {
                console.error('初始化COS失败', err);
                // 使用setTimeout延迟显示提示,避免影响弹窗
                setTimeout(() => {
                    uni.showToast({
                        title: '上传服务初始化失败',
                        icon: 'none'
                    });
                }, 100);
            });
        },
        // 获取文件预览地址
        async fetchFilePreviewUrl(fileKey) {
            // 如果是完整的URL,直接返回
            if (fileKey && (fileKey.startsWith('http://') || fileKey.startsWith('https://'))) {
                return fileKey;
            }
            // 如果已经缓存过,直接返回缓存值
            if (this.previewUrls[fileKey]) {
                return this.previewUrls[fileKey];
            }
            // 调用API获取预览地址
            try {
                const res = await getFilePreviewUrl(fileKey);
                const previewUrl = res.data.data;
                // 使用直接赋值而不是this.$set
                this.previewUrls[fileKey] = previewUrl;
                return previewUrl;
            } catch (error) {
                console.error('获取文件预览地址失败', error);
                return fileKey; // 如果获取失败,返回原始值
            }
        },
        // 选择模板图片
        selectImage(imgUrl) {
            const index = this.selectedImages.indexOf(imgUrl);
            if (index > -1) {
                // 如果已选中,则取消选择
                this.selectedImages.splice(index, 1);
            } else {
                // 否则添加到选中列表
                this.selectedImages.push(imgUrl);
            }
            // 更新formValues中的chooseImage
            this.formValues.chooseImage = this.selectedImages.join(',');
        },
        // 选择图片(参考video.vue的实现)
        chooseImage(fieldId) {
            if (!this.cosClient) {
                // 使用setTimeout延迟显示提示,避免影响弹窗
                setTimeout(() => {
                    uni.showToast({
                        title: '上传服务未初始化',
                        icon: 'none'
                    });
                }, 100);
                // 重新初始化COS
                this.initCOS();
                return;
            }
            uni.chooseImage({
                count: 1,
                sizeType: ['compressed'],
                sourceType: ['album', 'camera'],
                success: (res) => {
                    const tempFilePath = res.tempFilePaths[0];
                    // 获取文件名
                    let fileName = tempFilePath.substring(tempFilePath.lastIndexOf('/') + 1);
                    // 处理安卓可能的URI编码
                    if(fileName.indexOf('%') > -1) {
                        fileName = decodeURIComponent(fileName);
                    }
                    // 生成文件key
                    const fileKey = getFileKey(fileName);
                    // 显示预览图
                    this.$set(this.imagePreviewUrls, fieldId, tempFilePath);
                    // 初始化上传状态
                    this.$set(this.uploadProgress, fieldId, 0);
                    this.$set(this.uploadStatus, fieldId, '上传中...');
                    // 强制更新视图
                    this.$forceUpdate();
                    // 执行上传
                    this.cosClient.uploadFile({
                        Bucket: this.bucket,
                        Region: this.region,
                        Key: fileKey,
                        FilePath: tempFilePath,
                        SliceSize: 1024 * 1024 * 5, // 5M
                        onProgress: (progressData) => {
                            // 更新上传进度
                            const progress = Math.round(progressData.percent * 100);
                            this.$set(this.uploadProgress, fieldId, progress);
                            // 强制更新视图
                            this.$forceUpdate();
                        }
                    }, (err, data) => {
                        // 清除上传进度
                        // 使用Vue.delete或this.$delete可能会触发组件更新,改用直接删除
                        delete this.uploadProgress[fieldId];
                        if (err) {
                            console.error('上传失败', err);
                            // 更新上传状态
                            this.$set(this.uploadStatus, fieldId, '上传失败');
                            // 清除预览图
                            this.$delete(this.imagePreviewUrls, fieldId);
                            // 注意:这里不再需要清除formValues中的字段,因为我们已经修改了数据结构
                            // 强制更新视图
                            this.$forceUpdate();
                            // 使用setTimeout延迟显示错误提示,避免影响弹窗
                            setTimeout(() => {
                                uni.showToast({
                                    title: '上传失败',
                                    icon: 'none'
                                });
                            }, 100);
                        } else {
                            // 获取当前表单项的信息
                            const currentItem = this.consumizetemplateInfo.templateConstomizeTitles.find(item => item.id === fieldId);
                            const templateTitle = currentItem ? currentItem.templateTitle : '';
                            // 上传成功,更新表单值
                            this.updateFormValue(fieldId, templateTitle, fileKey);
                            // 更新上传状态
                            this.$set(this.uploadStatus, fieldId, '上传成功');
                            // 强制更新视图
                            this.$forceUpdate();
                            // 使用setTimeout延迟显示成功提示,避免影响弹窗
                            setTimeout(() => {
                                uni.showToast({
                                    title: '上传成功',
                                    icon: 'success'
                                });
                            }, 100);
                        }
                    });
                },
                fail: (err) => {
                    console.error('选择图片失败', err);
                    // 区分用户取消操作和真正的错误
                    // 如果是用户主动取消,不显示提示
                    if (err.errMsg && err.errMsg.indexOf('cancel') === -1 && err.errMsg.indexOf('fail') !== -1) {
                        // 使用setTimeout延迟显示提示,避免影响弹窗
                        setTimeout(() => {
                            uni.showToast({
                                title: '未选择图片',
                                icon: 'none'
                            });
                        }, 100);
                    }
                }
            });
        },
        // 删除图片
        deleteImage(fieldId) {
            // 清除预览图
            this.$delete(this.imagePreviewUrls, fieldId);
            // 清除上传状态
            this.$delete(this.uploadStatus, fieldId);
            // 清除上传进度
            this.$delete(this.uploadProgress, fieldId);
            // 从formValues中移除对应的表单字段
            const index = this.formValues.templateForm.findIndex(item => item.id === fieldId);
            if (index > -1) {
                this.formValues.templateForm.splice(index, 1);
            }
            // 使用setTimeout延迟显示提示,避免影响弹窗
            setTimeout(() => {
                uni.showToast({
                    title: '删除成功',
                    icon: 'success'
                });
            }, 100);
            // 强制更新视图
            this.$forceUpdate();
        },
        // 获取表单值
        getFormValue(fieldId) {
            const field = this.formValues.templateForm.find(item => item.id === fieldId);
            return field ? field.value : '';
        },
        // 更新表单值
        updateFormValue(fieldId, templateTitle, value) {
            // 更新模板基本信息
            this.formValues.templateId = this.consumizetemplateInfo.id;
            this.formValues.templateName = this.consumizetemplateInfo.name;
            // 查找是否已存在该字段
            const existingIndex = this.formValues.templateForm.findIndex(item => item.id === fieldId);
            if (existingIndex > -1) {
                // 更新现有字段
                this.$set(this.formValues.templateForm, existingIndex, {
                    id: fieldId,
                    templateTitle: templateTitle,
                    value: value
                });
            } else {
                // 添加新字段
                this.formValues.templateForm.push({
                    id: fieldId,
                    templateTitle: templateTitle,
                    value: value
                });
            }
        },
        // 更新模板表单数据
        updateTemplateFormData(fieldId, templateTitle, value) {
            // 这个方法现在可以保持不变,因为我们已经在updateFormValue中处理了formValues的更新
            // 如果需要其他处理,可以在这里添加
        },
        // 表单提交处理
        handleFormSubmit() {
            // 使用新的formValues数据结构
            const formData = this.formValues;
            console.log('表单数据:', formData);
            // 这里可以添加实际的表单提交逻辑
            // 使用setTimeout延迟显示提示,避免影响弹窗
            setTimeout(() => {
                uni.showToast({
                    title: '表单提交成功',
                    icon: 'success'
                });
            }, 100);
        },
        numCheck(val) {
            if (this.wholesaleList && this.wholesaleList.length > 0) {
                if (this.num <= this.wholesaleList[0].num) {
                    // 使用setTimeout延迟显示提示,避免影响弹窗
                    setTimeout(() => {
                    uni.showToast({
                        title: '批发商品购买数量不能小于起批数量!',
                        duration: 2000,
                        icon: 'none'
                    });
                    }, 100);
                    this.num = this.wholesaleList[0].num;
                }
            }
        },
        closeMask() {
            this.$emit('closeBuy', false);
        },
        // 处理弹窗输入事件,避免直接修改prop
        handlePopupInput(val) {
            this.$emit('closeBuy', val);
        },
        
        /**点击规格 */
@@ -285,11 +690,14 @@
                    goodsId: this.goodsDetail.goodsId
                });
            } else {
                // 使用setTimeout延迟显示提示,避免影响弹窗
                setTimeout(() => {
                uni.showToast({
                    title: '暂无该商品!',
                    duration: 2000,
                    icon: 'none'
                });
                }, 100);
            }
        },
@@ -299,9 +707,12 @@
        buy(data) {
            API_trade.addToCart(data).then(res => {
                if (res.data.success) {
                    // 使用setTimeout延迟跳转,避免影响弹窗
                    setTimeout(() => {
                    uni.navigateTo({
                        url: `/pages/order/fillorder?way=${data.cartType}&addr=${''}&parentOrder=${encodeURIComponent(JSON.stringify(this.parentOrder))}`
                    });
                    }, 100);
                }
            });
        },
@@ -310,11 +721,15 @@
         * 添加到购物车或购买
         */
        addToCartOrBuy(val) {
            console.log(JSON.stringify(this.formValues))
            if (!this.selectSkuList) {
                // 使用setTimeout延迟显示提示,避免影响弹窗
                setTimeout(() => {
                uni.showToast({
                    title: '请选择规格商品',
                    icon: 'none'
                });
                }, 100);
                return;
            }
            let data = {
@@ -325,10 +740,13 @@
            if (val == 'cart') {
                API_trade.addToCart(data).then(res => {
                    if (res.data.code == 200) {
                        // 使用setTimeout延迟显示提示,避免影响弹窗
                        setTimeout(() => {
                        uni.showToast({
                            title: '商品已添加到购物车',
                            icon: 'none'
                        });
                        }, 100);
                        this.$emit('queryCart');
                        this.closeMask();
@@ -346,9 +764,12 @@
                API_trade.addToCart(data).then(res => {
                    if (res.data.code == 200) {
                        // 使用setTimeout延迟跳转,避免影响弹窗
                        setTimeout(() => {
                        uni.navigateTo({
                            url: `/pages/order/fillorder?way=${data.cartType}&addr=${this.addr.id || ''}&parentOrder=${encodeURIComponent(JSON.stringify(this.parentOrder))}`
                                url: `/pages/order/fillorder?way=${data.cartType}&addr=${this.addr.id || ''}&template=${encodeURIComponent(JSON.stringify(this.formValues))}&parentOrder=${encodeURIComponent(JSON.stringify(this.parentOrder))}`
                        });
                        }, 100);
                    }
                });
            }
@@ -380,6 +801,7 @@
                                return i.value === values.value;
                            })
                        ) {
                            // 使用直接赋值而不是this.$set
                            arrItem.values.push(values);
                        }
@@ -387,6 +809,7 @@
                            return key.name;
                        });
                        if (!keys.includes(name)) {
                            // 使用直接赋值而不是this.$set
                            arr.push({
                                name: name,
                                values: [values]
@@ -397,6 +820,7 @@
            });
            arr.shift();
            // 使用直接赋值而不是this.$set
            this.formatList = arr;
            list.forEach(item => {
@@ -405,6 +829,7 @@
                    item.specValues
                        .filter(i => i.specName !== 'images')
                        .forEach((value, _index) => {
                            // 使用直接赋值而不是this.$set
                            this.currentSelected[_index] = value.specValue;
                            this.selectName = value.specValue;
@@ -417,15 +842,60 @@
                }
            });
            // 使用直接赋值而不是this.$set
            this.skuList = list;
            // console.log(" this.skuList", this.skuList)
        },
        // 预加载模板图片的预览地址
        async preloadTemplateImages() {
            if (this.consumizetemplateInfo && this.consumizetemplateInfo.templateImgs) {
                for (const img of this.consumizetemplateInfo.templateImgs) {
                    await this.fetchFilePreviewUrl(img.imgUrl);
                }
            }
        },
        // 获取文件预览地址(同步版本)
        getFilePreviewUrlSync(fileKey) {
            // 如果是完整的URL,直接返回
            if (fileKey && (fileKey.startsWith('http://') || fileKey.startsWith('https://'))) {
                return fileKey;
            }
            // 如果有预览地址,返回预览地址
            if (this.previewUrls && this.previewUrls[fileKey]) {
                return this.previewUrls[fileKey];
            }
            // 否则返回原始值
            return fileKey;
        },
        // 初始化模板表单数据
        initTemplateFormData() {
            if (this.consumizetemplateInfo && this.consumizetemplateInfo.templateConstomizeTitles) {
                // 初始化formValues的基本信息
                this.formValues.templateId = this.consumizetemplateInfo.id;
                this.formValues.templateName = this.consumizetemplateInfo.name;
                this.formValues.chooseImage = "";
                this.formValues.templateForm = [];
            }
        }
    },
    mounted() {
        // 使用setTimeout延迟执行,避免影响弹窗
        setTimeout(() => {
        this.formatSku(this.goodsSpec);
            this.initCOS(); // 初始化COS客户端
            // 预加载模板图片的预览地址
            this.preloadTemplateImages();
            // 初始化模板表单数据
            this.initTemplateFormData();
        console.log("goodsDetail",this.goodsDetail)
        }, 100);
    }
};
</script>
@@ -580,4 +1050,137 @@
        color: #333;
    }
}
// 模板样式
.template {
    padding: 20rpx;
}
.template-title {
    margin-bottom: 30rpx;
    text-align: center;
}
.title-text {
    font-size: 36rpx;
    font-weight: bold;
    color: #333;
}
// 图片选择区域
.template-images {
    margin-bottom: 40rpx;
}
.images-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 20rpx;
}
.image-item {
    width: calc((100% - 40rpx) / 3);
    height: 200rpx;
    border: 2rpx solid #e0e0e0;
    border-radius: 10rpx;
    overflow: hidden;
    position: relative;
    &.selected {
        border-color: #ff6b00;
        box-shadow: 0 0 10rpx rgba(255, 107, 0, 0.5);
    }
}
.selected-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.3);
    display: flex;
    align-items: center;
    justify-content: center;
}
.image-preview {
    width: 100%;
    height: 100%;
}
// 表单区域
.template-form {
}
.form-item {
    margin-bottom: 30rpx;
}
.form-label {
    display: block;
    margin-bottom: 15rpx;
    font-size: 28rpx;
    color: #333;
}
.form-input {
    width: 100%;
    height: 80rpx;
    padding: 0 20rpx;
    border: 2rpx solid #e0e0e0;
    border-radius: 10rpx;
    font-size: 28rpx;
    box-sizing: border-box;
}
.form-upload {
    margin-top: 15rpx;
}
// 图片上传样式
.upload-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 150rpx;
    height: 150rpx;
    border: 2rpx dashed #ccc;
    border-radius: 10rpx;
    background-color: #f8f8f8;
}
.upload-text {
    font-size: 24rpx;
    color: #999;
    margin-top: 10rpx;
}
.image-preview-container {
    position: relative;
    width: 150rpx;
    height: 150rpx;
    margin-top: 10rpx;
}
.uploaded-image {
    width: 100%;
    height: 100%;
    border-radius: 10rpx;
}
.delete-icon {
    position: absolute;
    top: -10rpx;
    right: -10rpx;
    background-color: #ff6b00;
    border-radius: 50%;
    width: 30rpx;
    height: 30rpx;
    display: flex;
    align-items: center;
    justify-content: center;
}
</style>