| manager/src/views/order/order/orderDetail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| seller/src/views/order/order/orderDetail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| seller/src/views/template/goodsCustomizeTemplate.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
manager/src/views/order/order/orderDetail.vue
@@ -112,74 +112,6 @@ <div class="div-item-left">买家留言:</div> <div class="div-item-right">{{ orderInfo.order.remark }}</div> </div> <div class="div-item" > <div class="div-item-left">商品模板:</div> <div class="div-item-right"> <template v-if="orderInfo.orderItems[0] && orderInfo.orderItems[0].goodsCustomizeTemplateVO"> {{ orderInfo.orderItems[0].goodsCustomizeTemplateVO.templateName }} </template> <template v-else> 暂无 </template> </div> </div> <div class="div-item" > <div class="div-item-left">模板标题:</div> <div class="div-item-right"> <!-- 先逐层判断是否存在,避免报错 --> <template v-if="orderInfo.orderItems[0] && orderInfo.orderItems[0].goodsCustomizeTemplateVO && orderInfo.orderItems[0].goodsCustomizeTemplateVO.titles && orderInfo.orderItems[0].goodsCustomizeTemplateVO.titles.length"> <span v-for="(item, index) in orderInfo.orderItems[0].goodsCustomizeTemplateVO.titles" :key="index" >{{ item.templateTitle }} <span v-if="index !== orderInfo.orderItems[0].goodsCustomizeTemplateVO.titles.length - 1">、</span> </span> </template> <template v-else> 暂无 </template> </div> </div> <div class="div-item"> <div class="div-item-left">模板图片:</div> <div class="div-item-right"> <!-- 图片列表容器 --> <div v-if="hasImages" class="image-list"> <div class="image-item" v-for="(image, index) in imageList" :key="index" @click="previewImage(index)" > <img :src="image.imgUrl" :alt="'模板图片' + (index + 1)" class="small-image" loading="lazy" > </div> </div> <!-- 无图片时显示 --> <div v-else >暂无图片</div> <!-- 图片预览弹窗 --> <div v-if="isPreviewVisible" class="preview-modal" @click="closePreview"> <div class="preview-content" @click.stop> <img :src="currentPreviewImage" :alt="`预览图片 ${currentPreviewIndex + 1}`" class="preview-image" > <div class="preview-nav"> <button class="close-btn" @click="closePreview">×</button> </div> </div> </div> </div> </div> <!-- <div class="div-item" v-if="orderInfo.order.needReceipt == false"> @@ -240,6 +172,62 @@ </div> </div> </div> <Col span="24"> <!-- 外层容器:循环遍历 userCheckTemplates 集合 --> <div class="check-template-list"> <div class="template-item" v-for="(item, index) in orderInfo.userCheckTemplates" :key="item.id"> <!-- 1. 商品模板:每个循环项都显示 subName --> <div class="div-item"> <div class="div-item-left">商品模板:</div> <div class="div-item-right"> {{ item.subName || '无商品模板名称' }} <!-- 处理空值默认显示 --> </div> </div> <!-- 2. 模板标题:仅第一个循环项显示 templateName(index===0 控制) --> <div class="div-item" v-if="index === 0"> <!-- 关键:仅首项渲染 --> <div class="div-item-left">模板标题:</div> <div class="div-item-right"> {{ item.templateName || '无模板标题' }} <!-- 处理空值默认显示 --> </div> </div> <!-- 3. 文本内容:判断 content 是「图片URL」还是「纯文本」 --> <div class="div-item"> <div class="div-item-left">文本内容:</div> <div class="div-item-right"> <!-- 正则判断:content 以 http/https 开头 → 渲染图片;否则渲染文本 --> <img v-if="isUrl(item.content)" :src="item.content" alt="内容图片" class="content-img" style="max-width: 200px; max-height: 150px;" > <span v-else>{{ item.content || '无文本内容' }}</span> <!-- 纯文本/空值处理 --> </div> </div> <!-- 4. 选择图片:渲染 chooseImg 字段(处理 null/空值) --> <div class="div-item"> <div class="div-item-left">选择图片:</div> <div class="div-item-right"> <img v-if="item.chooseImg" :src="item.chooseImg" alt="选择的图片" class="selected-img" style="max-width: 200px; max-height: 150px;" > <span v-else>无选择图片</span> <!-- 无图片时默认文本 --> </div> </div> <!-- 可选:循环项分隔线,优化视觉 --> <hr v-if="index !== orderInfo.userCheckTemplates.length - 1" style="margin: 15px 0; border: none; border-top: 1px solid #eee;"> </div> </div> </Col> </Card> <Card class="mt_10"> <Table :loading="loading" border :columns="columns" :data="data" ref="table" sortable="custom"> @@ -834,40 +822,17 @@ } }, }, computed: { // 获取图片列表(处理空值情况) imageList() { console.log(this.orderInfo.orderItems[0].goodsCustomizeTemplateVO) if (this.orderInfo && this.orderInfo.orderItems && this.orderInfo.orderItems[0] && this.orderInfo.orderItems[0].goodsCustomizeTemplateVO && this.orderInfo.orderItems[0].goodsCustomizeTemplateVO.listImages && Array.isArray(this.orderInfo.orderItems[0].goodsCustomizeTemplateVO.listImages)) { return this.orderInfo.orderItems[0].goodsCustomizeTemplateVO.listImages; } return []; }, // 判断是否有图片 hasImages() { return this.imageList.length > 0; } }, methods: { previewImage(index) { if (this.imageList[index]) { this.currentPreviewIndex = index; this.currentPreviewImage = this.imageList[index].imgUrl; this.isPreviewVisible = true; // 阻止页面滚动 document.body.style.overflow = 'hidden'; } }, // 关闭预览 closePreview() { this.isPreviewVisible = false; // 恢复页面滚动 document.body.style.overflow = ''; isUrl(str) { if (!str) return false; // 空值直接返回false // 正则说明: // 1. https?:// :支持http/https协议 // 2. ([\w-]+\.)+ :允许子域名包含短横线(如lmk-1356772813) // 3. [a-zA-Z]{2,} :顶级域名(如com、cn,至少2个字母) // 4. (\w-./?%&=)* :URL路径/参数部分,支持常见字符 // 5. \.(jpg|jpeg|png|gif|bmp|webp)$ :仅匹配常见图片后缀,忽略大小写(i标志) const imgReg = /^https?:\/\/([\w-]+\.)+[a-zA-Z]{2,}(\/[\w-./?%&=]*)*\.(jpg|jpeg|png|gif|bmp|webp)$/i; return imgReg.test(str); }, gotoHomes () { return false @@ -1072,88 +1037,6 @@ }; </script> <style lang="scss"> .image-list { display: flex; gap: 8px; flex-wrap: wrap; max-width: 600px; } .image-item { cursor: pointer; border: 1px solid #eee; border-radius: 4px; overflow: hidden; transition: transform 0.2s; } .image-item:hover { transform: scale(1.02); } .small-image { width: 80px; height: 80px; object-fit: cover; } .no-image { color: #999; padding: 10px 0; } /* 预览弹窗样式 */ .preview-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.8); display: flex; align-items: center; justify-content: center; z-index: 1000; } .preview-content { position: relative; max-width: 90%; max-height: 90%; } .preview-image { max-width: 100%; object-fit: contain; } .preview-nav { position: absolute; top: -30px; right: 0; color: white; display: flex; align-items: center; gap: 15px; } .preview-count { font-size: 14px; } .close-btn { background: none; border: none; color: white; font-size: 20px; cursor: pointer; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; padding: 0; } .lineH30 { line-height: 30px; } @@ -1388,4 +1271,30 @@ height: inherit; } } .check-template-list { display: flex; flex-wrap: wrap; // 关键:多余子项自动换行 gap: 15px; // 子项之间的间距(水平+垂直) padding: 10px 0; // 上下内边距,避免贴边 width: 100%; // 占满父容器(Col span="12") } // 2. 每个模板项:控制宽度和卡片样式 .template-item { min-width: 280px; // 最小宽度,避免过窄 max-width: 350px; // 最大宽度,防止过宽 flex: 1; // 同一行子项均匀分配宽度 padding: 12px; border: 1px solid #eee; border-radius: 6px; background-color: #fafafa; box-sizing: border-box; // 防止padding撑大宽度 } .content-img, .selected-img { max-width: 100%; // 适应子项宽度 max-height: 150px; // 限制最大高度 border-radius: 4px; object-fit: cover; // 保持图片比例,不拉伸 } </style> seller/src/views/order/order/orderDetail.vue
@@ -146,73 +146,62 @@ }} </div> </div> <div class="div-item" > <div class="div-item-left">商品模板:</div> <div class="div-item-right"> <template v-if="orderInfo.orderItems[0] && orderInfo.orderItems[0].goodsCustomizeTemplateVO"> {{ orderInfo.orderItems[0].goodsCustomizeTemplateVO.templateName }} </template> <template v-else> 暂无 </template> </Col> <Col span="24"> <!-- 外层容器:循环遍历 userCheckTemplates 集合 --> <div class="check-template-list"> <div class="template-item" v-for="(item, index) in orderInfo.userCheckTemplates" :key="item.id"> <!-- 1. 商品模板:每个循环项都显示 subName --> <div class="div-item"> <div class="div-item-left">商品模板:</div> <div class="div-item-right"> {{ item.subName || '无商品模板名称' }} <!-- 处理空值默认显示 --> </div> </div> </div> <div class="div-item" > <div class="div-item-left">模板标题:</div> <div class="div-item-right"> <!-- 先逐层判断是否存在,避免报错 --> <template v-if="orderInfo.orderItems[0] && orderInfo.orderItems[0].goodsCustomizeTemplateVO && orderInfo.orderItems[0].goodsCustomizeTemplateVO.titles && orderInfo.orderItems[0].goodsCustomizeTemplateVO.titles.length"> <span v-for="(item, index) in orderInfo.orderItems[0].goodsCustomizeTemplateVO.titles" :key="index" >{{ item.templateTitle }} <span v-if="index !== orderInfo.orderItems[0].goodsCustomizeTemplateVO.titles.length - 1">、</span> </span> </template> <template v-else> 暂无 </template> <!-- 2. 模板标题:仅第一个循环项显示 templateName(index===0 控制) --> <div class="div-item" v-if="index === 0"> <!-- 关键:仅首项渲染 --> <div class="div-item-left">模板标题:</div> <div class="div-item-right"> {{ item.templateName || '无模板标题' }} <!-- 处理空值默认显示 --> </div> </div> </div> <div class="div-item"> <div class="div-item-left">模板图片:</div> <div class="div-item-right"> <!-- 图片列表容器 --> <div v-if="hasImages" class="image-list"> <div class="image-item" v-for="(image, index) in imageList" :key="index" @click="previewImage(index)" <!-- 3. 文本内容:判断 content 是「图片URL」还是「纯文本」 --> <div class="div-item"> <div class="div-item-left">文本内容:</div> <div class="div-item-right"> <!-- 正则判断:content 以 http/https 开头 → 渲染图片;否则渲染文本 --> <img v-if="isUrl(item.content)" :src="item.content" alt="内容图片" class="content-img" style="max-width: 200px; max-height: 150px;" > <img :src="image.imgUrl" :alt="'模板图片' + (index + 1)" class="small-image" loading="lazy" > </div> </div> <!-- 无图片时显示 --> <div v-else >暂无图片</div> <!-- 图片预览弹窗 --> <div v-if="isPreviewVisible" class="preview-modal" @click="closePreview"> <div class="preview-content" @click.stop> <img :src="currentPreviewImage" :alt="`预览图片 ${currentPreviewIndex + 1}`" class="preview-image" > <div class="preview-nav"> <button class="close-btn" @click="closePreview">×</button> </div> </div> <span v-else>{{ item.content || '无文本内容' }}</span> <!-- 纯文本/空值处理 --> </div> </div> <!-- 4. 选择图片:渲染 chooseImg 字段(处理 null/空值) --> <div class="div-item"> <div class="div-item-left">选择图片:</div> <div class="div-item-right"> <img v-if="item.chooseImg" :src="item.chooseImg" alt="选择的图片" class="selected-img" style="max-width: 200px; max-height: 150px;" > <span v-else>无选择图片</span> <!-- 无图片时默认文本 --> </div> </div> <!-- 可选:循环项分隔线,优化视觉 --> <hr v-if="index !== orderInfo.userCheckTemplates.length - 1" style="margin: 15px 0; border: none; border-top: 1px solid #eee;"> </div> </div> </Col> @@ -669,9 +658,9 @@ }, data () { return { isPreviewVisible: false, currentPreviewImage: '', currentPreviewIndex: 0, // isPreviewVisible: false, // currentPreviewImage: '', // currentPreviewIndex: 0, loading:false, typeList: [], @@ -1025,40 +1014,17 @@ packageTraceList: [] }; }, computed: { // 获取图片列表(处理空值情况) imageList() { console.log(this.orderInfo.orderItems[0].goodsCustomizeTemplateVO) if (this.orderInfo && this.orderInfo.orderItems && this.orderInfo.orderItems[0] && this.orderInfo.orderItems[0].goodsCustomizeTemplateVO && this.orderInfo.orderItems[0].goodsCustomizeTemplateVO.listImages && Array.isArray(this.orderInfo.orderItems[0].goodsCustomizeTemplateVO.listImages)) { return this.orderInfo.orderItems[0].goodsCustomizeTemplateVO.listImages; } return []; }, // 判断是否有图片 hasImages() { return this.imageList.length > 0; } }, methods: { previewImage(index) { if (this.imageList[index]) { this.currentPreviewIndex = index; this.currentPreviewImage = this.imageList[index].imgUrl; this.isPreviewVisible = true; // 阻止页面滚动 document.body.style.overflow = 'hidden'; } }, // 关闭预览 closePreview() { this.isPreviewVisible = false; // 恢复页面滚动 document.body.style.overflow = ''; isUrl(str) { if (!str) return false; // 空值直接返回false // 正则说明: // 1. https?:// :支持http/https协议 // 2. ([\w-]+\.)+ :允许子域名包含短横线(如lmk-1356772813) // 3. [a-zA-Z]{2,} :顶级域名(如com、cn,至少2个字母) // 4. (\w-./?%&=)* :URL路径/参数部分,支持常见字符 // 5. \.(jpg|jpeg|png|gif|bmp|webp)$ :仅匹配常见图片后缀,忽略大小写(i标志) const imgReg = /^https?:\/\/([\w-]+\.)+[a-zA-Z]{2,}(\/[\w-./?%&=]*)*\.(jpg|jpeg|png|gif|bmp|webp)$/i; return imgReg.test(str); }, // 选中 selectGroupShipGoodsMethods (selected) { @@ -1472,89 +1438,6 @@ }; </script> <style lang="scss" scoped> .image-list { display: flex; gap: 8px; flex-wrap: wrap; max-width: 600px; } .image-item { cursor: pointer; border: 1px solid #eee; border-radius: 4px; overflow: hidden; transition: transform 0.2s; } .image-item:hover { transform: scale(1.02); } .small-image { width: 80px; height: 80px; object-fit: cover; } .no-image { color: #999; padding: 10px 0; } /* 预览弹窗样式 */ .preview-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.8); display: flex; align-items: center; justify-content: center; z-index: 1000; } .preview-content { position: relative; max-width: 90%; max-height: 90%; } .preview-image { max-width: 100%; object-fit: contain; } .preview-nav { position: absolute; top: -30px; right: 0; color: white; display: flex; align-items: center; gap: 15px; } .preview-count { font-size: 14px; } .close-btn { background: none; border: none; color: white; font-size: 20px; cursor: pointer; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; padding: 0; } // 建议引入通用样式 可删除下面样式代码 // @import "@/styles/table-common.scss"; .lineH30 { @@ -1770,4 +1653,30 @@ height: inherit; } } .check-template-list { display: flex; flex-wrap: wrap; // 关键:多余子项自动换行 gap: 15px; // 子项之间的间距(水平+垂直) padding: 10px 0; // 上下内边距,避免贴边 width: 100%; // 占满父容器(Col span="12") } // 2. 每个模板项:控制宽度和卡片样式 .template-item { min-width: 280px; // 最小宽度,避免过窄 max-width: 350px; // 最大宽度,防止过宽 flex: 1; // 同一行子项均匀分配宽度 padding: 12px; border: 1px solid #eee; border-radius: 6px; background-color: #fafafa; box-sizing: border-box; // 防止padding撑大宽度 } .content-img, .selected-img { max-width: 100%; // 适应子项宽度 max-height: 150px; // 限制最大高度 border-radius: 4px; object-fit: cover; // 保持图片比例,不拉伸 } </style> seller/src/views/template/goodsCustomizeTemplate.vue
@@ -126,7 +126,11 @@ <!-- 已添加的标题列表 --> <div v-for="(title, index) in form.titles" :key="index" class="title-item"> <card class="title-text"> <Input v-model="title.templateTitle" placeholder="请输入文本标题" style="width: 300px; margin-top: 8px;"/> <div style="display: flex; align-items: center; gap: 8px;"> <span>{{ title.contentType ==='TEXT' ? '文本标题': '图片标题'}} :</span> <Input v-model="title.templateTitle" placeholder="请输入标题" style="width: 300px; margin-top: 8px;"/> </div> <Button class="delete-btn" type="text" @click="removeTitle(index)" style="color: #ff4d4f;">删除</Button> </card> <!-- <card v-if="title.contentType === 'IMAGE'" class="title-image">-->