<template>
|
<div class="coupon-management">
|
<!-- 搜索区域 -->
|
<Card class="filter-container">
|
<div class="filter-header">
|
<Icon type="ios-search" size="18" />
|
<span class="filter-title">筛选搜索</span>
|
<div class="filter-actions">
|
<Button type="primary" @click="getList" size="small">查询搜索</Button>
|
<Button @click="handleResetSearch" size="small" style="margin-left: 10px;">重置</Button>
|
<Button type="primary" @click="addModal" size="small" style="margin-left: 10px;">新增</Button>
|
</div>
|
</div>
|
<div class="filter-content">
|
<Form
|
:model="listQuery"
|
:label-width="90"
|
class="search-form"
|
inline
|
>
|
<FormItem label="模板名称">
|
<Input
|
v-model="listQuery.templateName"
|
style="width: 180px;"
|
clearable
|
placeholder="请输入模板名称"
|
>
|
</Input>
|
</FormItem>
|
<FormItem label="启用状态:">
|
<Select
|
v-model="listQuery.status"
|
placeholder="全部状态"
|
clearable
|
style="width: 180px"
|
>
|
<Option value="DISABLE">未启用</Option>
|
<Option value="ENABLE">已起用</Option>
|
</Select>
|
</FormItem>
|
|
|
</Form>
|
</div>
|
</Card>
|
|
<!-- 表格区域 -->
|
<Card class="table-container">
|
<Table
|
:loading="listLoading"
|
border
|
:columns="tableColumns"
|
:data="list"
|
ref="table"
|
class="coupon-table"
|
>
|
<template slot-scope="{ row }" slot="action">
|
<Button
|
type="primary"
|
size="small"
|
style="margin-right: 5px"
|
@click="delTemplate(row)"
|
>删除</Button
|
>
|
<Button
|
type="primary"
|
size="small"
|
style="margin-right: 5px"
|
@click="updateTemplate(row)"
|
>修改</Button
|
>
|
<Button
|
type="primary"
|
size="small"
|
style="margin-right: 5px"
|
@click="changeStatus(row)"
|
>{{ row.status ==='ENABLE' ? '禁用':'开启'}}</Button
|
>
|
</template>
|
</Table>
|
</Card>
|
|
<!-- 分页区域 -->
|
<div class="pagination-container">
|
<Page
|
:current="listQuery.pageNumber"
|
:page-size="listQuery.pageSize"
|
:total="total"
|
:page-size-opts="[10, 20, 30, 50]"
|
show-elevator
|
show-sizer
|
show-total
|
@on-change="handleCurrentChange"
|
@on-page-size-change="handleSizeChange"
|
/>
|
</div>
|
|
<Modal
|
v-model="showModal"
|
:title="modalTitle"
|
width="800"
|
:mask-closable="false"
|
>
|
<Form
|
ref="form"
|
:model="form"
|
:label-width="90"
|
class="search-form"
|
:rules="formRules"
|
inline>
|
<Row>
|
<Col :span="24">
|
<FormItem label="模板名称" prop="templateName">
|
<Input v-model="form.templateName" autocomplete="off"/>
|
</FormItem>
|
</Col>
|
<Col :span="24">
|
<FormItem label="选择新增标题">
|
<Button type="primary" @click="addTitle('TEXT')">添加文字标题</Button>
|
<Button type="primary" style="margin-left: 10px" @click="addTitle('IMAGE')">添加图片标题</Button>
|
</FormItem>
|
</Col>
|
|
<Col :span="24">
|
|
<!-- 已添加的标题列表 -->
|
<div v-for="(title, index) in form.titles" :key="index" class="title-item">
|
<card class="title-text">
|
<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">-->
|
<!-- <Upload-->
|
<!-- v-if="!title.imgTempUrl"-->
|
<!-- :before-upload="(file) => handleBeforeUpload(file,title)"-->
|
<!-- :format="['jpg','jpeg','png','gif']"-->
|
<!-- :max-size="20480"-->
|
<!-- action=""-->
|
<!-- accept="image/*"-->
|
<!-- >-->
|
<!-- <Button icon="ios-cloud-upload-outline">上传封面图片</Button>-->
|
<!-- </Upload>-->
|
<!-- <div v-else class="upload-file-info">-->
|
|
<!-- <Col :span="24">-->
|
<!-- <img :src="title.imgTempUrl" alt="活动图片" class="preview-image-limit">-->
|
<!-- </Col>-->
|
<!-- <Button type="text" @click="handleRemoveImage(title)">删除</Button>-->
|
<!-- </div>-->
|
|
<!-- <Button class="delete-btn" type="text" @click="removeTitle(index)" style="color: #ff4d4f;">删除</Button>-->
|
<!-- </card>-->
|
</div>
|
</Col>
|
<Col :span="24">
|
<FormItem label="模板图片" >
|
<div style="display: flex; flex-wrap: wrap;">
|
<vuedraggable :animation="200" :list="showListImages">
|
<div v-for="(item, __index) in showListImages" :key="__index"
|
class="demo-upload-list">
|
<template>
|
<img :src="item"/>
|
<div class="demo-upload-list-cover">
|
<div>
|
<Icon size="30" type="md-search" @click.native="handleViewGoodsPicture(item)"></Icon>
|
<Icon size="30" type="md-trash" @click.native="handleRemoveGoodsPicture(item)"></Icon>
|
</div>
|
</div>
|
</template>
|
</div>
|
</vuedraggable>
|
</div>
|
<div style="width: 100%;display: flex;justify-content: start;margin-top: 10px;">
|
<Button @click="handleCLickImg('goodsGalleryFiles')" type="primary">上传图片</Button>
|
</div>
|
<Modal v-model="goodsPictureVisible" title="View Image">
|
<img v-if="goodsPictureVisible" :src="previewGoodsPicture" style="width: 100%"/>
|
</Modal>
|
</FormItem>
|
</Col>
|
|
</Row>
|
|
|
</Form>
|
<div slot="footer">
|
<Button type="text" @click="showModal = false">关闭</Button>
|
<Button type="primary" :loading="submitLoading" @click="saveOrUpdate">确认</Button>
|
</div>
|
</Modal>
|
|
<Modal v-model="picModelFlag" width="1200px">
|
<div class="demo-upload-list" v-for="(item,index) in showListImages">
|
<template>
|
<img :src="item">
|
<div class="demo-upload-list-cover">
|
<Icon type="ios-eye-outline" @click.native="handleView(item)"></Icon>
|
<Icon type="ios-trash-outline" @click.native="handleRemove(null,index)"></Icon>
|
</div>
|
</template>
|
</div>
|
<div class="demo-upload-list">
|
<Upload
|
:before-upload="upLoadImg"
|
accept="image/*"
|
action="-"
|
type="drag"
|
style=" display: inline-block;width: 58px"
|
>
|
<div style="width: 58px;height:58px;line-height: 58px;">
|
<Icon type="ios-camera" size="20"></Icon>
|
</div>
|
</Upload>
|
</div>
|
</Modal>
|
<Modal v-model="visible" title="预览图片">
|
<img v-if="visible" :src="previewPicture" style="width: 100%">
|
</Modal>
|
|
</div>
|
</template>
|
<script>
|
|
import { getPage,edit,add,del,changeStatus,detail } from "@/api/goods-customeize-template.js"
|
import vuedraggable from "vuedraggable";
|
import COS from "cos-js-sdk-v5";
|
import {getFileKey} from "@/utils/file.js";
|
import {getFilePreview, getSts} from "@/api/file";
|
|
export default {
|
name: 'GoodsCustomizeTemplate',
|
components: {vuedraggable},
|
data() {
|
return {
|
// 预览图片路径
|
previewPicture: "",
|
visible:false,
|
picModelFlag: false, // 图片选择器
|
goodsPictureVisible: false,
|
showListImages: [],
|
listImages: [],
|
previewGoodsPicture: "",
|
selectedFormBtnName: "", // 点击图片绑定form
|
|
//新增
|
modalTitle:"",
|
showModal:false,
|
|
list: [],
|
total: 0,
|
listLoading: false,
|
submitLoading: false,
|
|
listQuery: {
|
pageNumber: 1,
|
pageSize: 10,
|
templateName:"",
|
status:"",
|
},
|
form:{
|
titles: []
|
},
|
// 表单验证规则
|
formRules: {
|
templateName:[
|
{required:true,message:"模板名不能为空",trigger:"blur"}
|
]
|
},
|
|
// 表头配置
|
tableColumns: [
|
{
|
type: 'selection',
|
width: 60,
|
align: 'center'
|
},
|
{
|
title: '模板名称',
|
key: 'templateName',
|
align: 'center',
|
ellipsis: true,
|
tooltip: true
|
},
|
{
|
title: '启用状态',
|
key: 'status',
|
width: 120,
|
align: 'center',
|
render: (h, params) => {
|
const status = params.row.status;
|
const color = status === 'ENABLE' ? 'success' : status === 'DISABLE' ? 'default' : 'warning';
|
const text = status === 'ENABLE' ? '启用' : status === 'DISABLE' ? '未启用' : '未知';
|
|
return h('Tag', {
|
props: {
|
color: color
|
}
|
}, text);
|
}
|
},
|
{
|
title: '操作',
|
slot: 'action',
|
width: 200,
|
align: 'center'
|
},
|
]
|
}
|
},
|
methods: {
|
changeStatus(row){
|
changeStatus(row.id).then(res =>{
|
if (res.code ===200 ){
|
this.$Message.success(res.msg)
|
}
|
this.getList()
|
})
|
|
},
|
async updateTemplate(row){
|
this.showModal = true;
|
this.modalTitle = "修改模板";
|
|
await detail(row.id).then(res =>{
|
if (res.code === 200){
|
this.form = {
|
id: res.data.id,
|
templateName: res.data.templateName || '', // 确保不为undefined
|
titles: res.data.titles || [] // 标题列表
|
};
|
this.form.titles.map((i) =>{
|
i.id = null;
|
return i
|
})
|
this.listImages = res.data.listImages.map((i) => {
|
return i.imgUrl
|
});
|
|
}
|
})
|
const stsInfo = await getSts();
|
const endpoint = stsInfo.data.endpoint;
|
console.log()
|
this.showListImages = this.listImages.map((i) => {
|
if (i!=null&&i.indexOf('http')===-1)
|
return endpoint+'/'+i;
|
else return i;
|
})
|
|
},
|
delTemplate(row){
|
this.$Modal.confirm({
|
title: "操作确认",
|
content: "您确认要删除【 " + row.templateName + "】吗?",
|
onOk: () => {
|
del(row.id).then(res =>{
|
if (res.code === 200){
|
this.$Message.success(res.msg)
|
}
|
this.getList()
|
})
|
}
|
});
|
|
},
|
async upLoadImg(file) {
|
console.log("打印上传1")
|
if (this.listImages.length >= 10) {
|
this.$Message.error("图片上传不能超过10个");
|
return;
|
}
|
console.log("打印上传2")
|
try {
|
// 获取文件上传临时密钥
|
const sts = await getSts();
|
const cos = new COS({
|
getAuthorization: async function (options, callback) {
|
callback({
|
TmpSecretId: sts.data.tmpSecretId,
|
TmpSecretKey: sts.data.tmpSecretKey,
|
SecurityToken: sts.data.sessionToken,
|
// 建议返回服务器时间作为签名的开始时间,避免客户端本地时间偏差过大导致签名错误
|
StartTime: sts.data.stsStartTime, // 时间戳,单位秒,如:1580000000
|
ExpiredTime: sts.data.stsEndTime,// 时间戳,单位秒,如:1580000000
|
ScopeLimit: true, // 细粒度控制权限需要设为 true,会限制密钥只在相同请求时重复使用
|
});
|
}
|
})
|
const fileKey = getFileKey(file.name)
|
const upData = await cos.uploadFile({
|
Bucket: sts.data.bucket,
|
Region: sts.data.region,
|
Key: fileKey,
|
Body: file, // 要上传的文件对象。
|
SliceSize: 1024 * 1024 * 5,
|
onProgress: function (progressData) {
|
console.log('上传进度:', progressData);
|
},
|
});
|
console.log("上传成功", upData)
|
this.$nextTick(() => {
|
this.listImages.push(fileKey);
|
this.showListImages.push(sts.data.endpoint + "/" + fileKey);
|
|
})
|
|
} catch (e) {
|
console.log("上传失败", upData)
|
return false;
|
} finally {
|
|
}
|
return false;
|
},
|
handleView(url) {
|
this.previewPicture = url;
|
this.visible = true;
|
},
|
handleRemove(item, index) {
|
if (!item) {
|
this.listImages.splice(index, 1);
|
this.showListImages.splice(index, 1);
|
|
} else {
|
console.log('移除测试',item, index);
|
item.splice(index, 1)
|
}
|
this.previewPicture = "";
|
},
|
|
handleCLickImg(val, index) {
|
this.picModelFlag = true;
|
this.selectedFormBtnName = val;
|
},
|
handleViewGoodsPicture(url) {
|
this.previewGoodsPicture = url;
|
this.goodsPictureVisible = true;
|
},
|
// 移除商品图片
|
handleRemoveGoodsPicture(file) {
|
// this.baseInfoForm.goodsGalleryFiles =
|
// this.baseInfoForm.goodsGalleryFiles.filter((i) => i !== file);
|
},
|
|
addTitle(type){
|
this.form.titles.push(
|
{
|
contentType: type,
|
templateTitle: '',
|
imgTempUrl:null,//图片临时地址
|
file:null,
|
});
|
},
|
// 移除标题
|
removeTitle(index) {
|
this.form.titles.splice(index, 1);
|
},
|
|
saveOrUpdate() {
|
const submitData = {
|
...this.form,
|
listImages: this.listImages,
|
};
|
|
console.log(submitData)
|
|
const isTitlesValid = (titles) => {
|
console.log("进入判断")
|
// 数组本身为null/undefined或空数组
|
if (!titles || titles.length === 0) return false;
|
// 检查每个元素的有效性
|
return titles.every(title => {
|
// 基础校验:必须是对象且包含contentType
|
if (!title || typeof title !== 'object' || !title.contentType) return false;
|
|
return title.templateTitle !== null
|
&& title.templateTitle !== undefined
|
&& title.templateTitle.trim() !== '';
|
// 未知类型
|
return false;
|
});
|
};
|
|
const isImagesValid = (images) => {
|
if (!images || images.length === 0) return false;
|
return images.some(img => img && (typeof img === 'string' ? img.trim() !== '' : true));
|
};
|
|
this.$refs.form.validate(async (valid) => {
|
if (valid) {
|
try {
|
// 先处理所有图片上传(等待异步完成)
|
this.submitLoading = true;
|
|
// 上传完成后再验证
|
if (!isTitlesValid(submitData.titles)) {
|
this.$Message.error('请确保所有标题都填写完整(文本标题不能为空,图片标题需上传成功)');
|
this.submitLoading = false;
|
return;
|
}
|
|
if (!isImagesValid(submitData.listImages)) {
|
this.$Message.error('请添加有效的图片(至少一张图片)');
|
this.submitLoading = false;
|
return;
|
}
|
|
// 验证通过,提交数据
|
const api = this.form.id ? edit : add;
|
const res = await api(submitData);
|
|
if (res.code === 200) {
|
this.$Message.success(res.msg);
|
this.modelShow = false;
|
this.showModal = false;
|
this.getList();
|
} else {
|
this.$Message.error(res.msg);
|
}
|
} catch (error) {
|
this.$Message.error('提交失败,请重试');
|
console.error(error);
|
} finally {
|
this.submitLoading = false;
|
}
|
}
|
});
|
},
|
addModal(){
|
this.form = {
|
titles: []
|
};
|
this.showModal = true;
|
this.modalTitle = "新增模板";
|
this.showListImages =[];
|
this.listImages =[];
|
},
|
detail(row){
|
console.log(row)
|
},
|
|
// 获取列表数据
|
getList() {
|
this.listLoading = true;
|
// 模拟API调用
|
getPage(this.listQuery).then(res =>{
|
this.listLoading = false;
|
this.list = res.data;
|
this.total = res.total;
|
})
|
},
|
|
// 重置搜索条件
|
handleResetSearch() {
|
this.listQuery = {
|
pageNumber: 1,
|
pageSize: 10,
|
storeName: "",
|
couponName: "",
|
generateStatus: "",
|
status: ""
|
};
|
this.getList();
|
},
|
|
// 分页大小改变
|
handleSizeChange(size) {
|
this.listQuery.pageSize = size;
|
this.listQuery.pageNumber = 1;
|
this.getList();
|
},
|
|
// 当前页码改变
|
handleCurrentChange(pageNumber) {
|
this.listQuery.pageNumber = pageNumber;
|
this.getList();
|
},
|
|
// 提交表单
|
handleSubmit() {
|
this.$refs.dataForm.validate((valid) => {
|
if (valid) {
|
}
|
});
|
}
|
},
|
mounted() {
|
this.getList();
|
}
|
}
|
</script>
|
|
<style scoped lang="less">
|
.preview-image-limit{
|
max-width: 100%;
|
max-height: 200px;
|
object-fit: contain;
|
border-radius: 4px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
display: block;
|
}
|
.delete-btn {
|
position: absolute; /* 绝对定位 */
|
top: 12px; /* 距离顶部的距离 */
|
right: 12px; /* 距离右侧的距离 */
|
padding: 4px 8px; /* 调整按钮大小 */
|
line-height: 1; /* 调整行高 */
|
}
|
.title-item {
|
margin-bottom: 12px;
|
padding-bottom: 12px;
|
border-bottom: 1px dashed #e9e9e9;
|
}
|
|
.title-type-selector {
|
display: flex;
|
justify-content: center;
|
padding: 20px 0;
|
}
|
|
.type-option {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
margin: 0 20px;
|
cursor: pointer;
|
padding: 15px;
|
border-radius: 4px;
|
transition: all 0.3s;
|
|
&:hover {
|
background-color: #f5f7fa;
|
}
|
|
span {
|
margin-top: 10px;
|
}
|
}
|
.coupon-management {
|
padding: 16px;
|
background: #f5f7f9;
|
min-height: 100vh;
|
}
|
|
.filter-container {
|
margin-bottom: 16px;
|
|
.filter-header {
|
display: flex;
|
align-items: center;
|
margin-bottom: 16px;
|
|
.filter-title {
|
margin-left: 8px;
|
font-weight: 600;
|
font-size: 16px;
|
}
|
|
.filter-actions {
|
margin-left: auto;
|
}
|
}
|
|
.filter-content {
|
.search-form {
|
/deep/ .ivu-form-item {
|
margin-bottom: 16px;
|
margin-right: 16px;
|
}
|
}
|
}
|
}
|
|
.operation-container {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 16px;
|
padding: 12px 16px;
|
background: #fff;
|
border-radius: 4px;
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
|
.operation-info {
|
color: #999;
|
font-size: 14px;
|
}
|
}
|
|
.table-container {
|
margin-bottom: 16px;
|
|
.coupon-table {
|
/deep/ .ivu-table-cell {
|
padding: 8px 12px;
|
}
|
|
.action-btns {
|
display: flex;
|
justify-content: center;
|
|
button {
|
margin: 0 2px;
|
}
|
}
|
}
|
}
|
|
.pagination-container {
|
display: flex;
|
justify-content: flex-end;
|
background: #fff;
|
padding: 12px 16px;
|
border-radius: 4px;
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
}
|
|
// 响应式调整
|
@media (max-width: 768px) {
|
.coupon-management {
|
padding: 8px;
|
}
|
|
.filter-content .search-form {
|
/deep/ .ivu-form-item {
|
width: 100%;
|
margin-right: 0;
|
|
.ivu-form-item-content {
|
width: 100%;
|
|
.ivu-input, .ivu-select {
|
width: 100% !important;
|
}
|
}
|
}
|
}
|
|
.operation-container {
|
flex-direction: column;
|
align-items: flex-start;
|
|
.add-btn {
|
margin-bottom: 8px;
|
}
|
}
|
|
.action-btns {
|
flex-direction: column;
|
|
button {
|
margin: 2px 0 !important;
|
width: 100%;
|
}
|
}
|
}
|
.demo-upload-list {
|
display: inline-block;
|
width: 60px;
|
height: 60px;
|
text-align: center;
|
line-height: 60px;
|
border: 1px solid transparent;
|
border-radius: 4px;
|
overflow: hidden;
|
background: #fff;
|
position: relative;
|
box-shadow: 0 1px 1px rgba(0, 0, 0, .2);
|
margin-right: 4px;
|
}
|
|
.demo-upload-list img {
|
width: 100%;
|
height: 100%;
|
}
|
|
.demo-upload-list-cover {
|
display: none;
|
position: absolute;
|
top: 0;
|
bottom: 0;
|
left: 0;
|
right: 0;
|
background: rgba(0, 0, 0, .6);
|
}
|
|
.demo-upload-list:hover .demo-upload-list-cover {
|
display: block;
|
}
|
|
.demo-upload-list-cover i {
|
color: #fff;
|
font-size: 20px;
|
cursor: pointer;
|
margin: 0 2px;
|
}
|
</style>
|