feat: 优化文案模板分组与复制预览

This commit is contained in:
王鹏
2026-09-07 15:40:29 +08:00
parent 074bbda460
commit a466caa793
6 changed files with 474 additions and 15 deletions

View File

@@ -21,6 +21,10 @@ public class TtCopyTemplate extends BaseEntity {
@Excel(name = "按钮名称") @Excel(name = "按钮名称")
private String templateName; private String templateName;
/** 模板分组 */
@Excel(name = "模板分组")
private String templateGroup;
/** 文案模板内容,支持 {变量} 占位符 */ /** 文案模板内容,支持 {变量} 占位符 */
@Excel(name = "模板内容") @Excel(name = "模板内容")
private String templateBody; private String templateBody;
@@ -49,6 +53,14 @@ public class TtCopyTemplate extends BaseEntity {
return templateName; return templateName;
} }
public void setTemplateGroup(String templateGroup) {
this.templateGroup = templateGroup;
}
public String getTemplateGroup() {
return templateGroup;
}
public void setTemplateBody(String templateBody) { public void setTemplateBody(String templateBody) {
this.templateBody = templateBody; this.templateBody = templateBody;
} }
@@ -78,6 +90,7 @@ public class TtCopyTemplate extends BaseEntity {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("templateId", getTemplateId()) .append("templateId", getTemplateId())
.append("templateName", getTemplateName()) .append("templateName", getTemplateName())
.append("templateGroup", getTemplateGroup())
.append("templateBody", getTemplateBody()) .append("templateBody", getTemplateBody())
.append("sortNum", getSortNum()) .append("sortNum", getSortNum())
.append("status", getStatus()) .append("status", getStatus())
@@ -85,4 +98,4 @@ public class TtCopyTemplate extends BaseEntity {
.append("updateTime", getUpdateTime()) .append("updateTime", getUpdateTime())
.toString(); .toString();
} }
} }

View File

@@ -7,6 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="TtCopyTemplate" id="TtCopyTemplateResult"> <resultMap type="TtCopyTemplate" id="TtCopyTemplateResult">
<result property="templateId" column="template_id" /> <result property="templateId" column="template_id" />
<result property="templateName" column="template_name" /> <result property="templateName" column="template_name" />
<result property="templateGroup" column="template_group" />
<result property="templateBody" column="template_body" /> <result property="templateBody" column="template_body" />
<result property="sortNum" column="sort_num" /> <result property="sortNum" column="sort_num" />
<result property="status" column="status" /> <result property="status" column="status" />
@@ -15,7 +16,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectTtCopyTemplateVo"> <sql id="selectTtCopyTemplateVo">
select template_id, template_name, template_body, sort_num, status, create_time, update_time select template_id, template_name, template_group, template_body, sort_num, status, create_time, update_time
from tt_copy_template from tt_copy_template
</sql> </sql>
@@ -23,6 +24,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include refid="selectTtCopyTemplateVo"/> <include refid="selectTtCopyTemplateVo"/>
<where> <where>
<if test="templateName != null and templateName != ''"> and template_name like concat('%', #{templateName}, '%')</if> <if test="templateName != null and templateName != ''"> and template_name like concat('%', #{templateName}, '%')</if>
<if test="templateGroup != null and templateGroup != ''"> and template_group like concat('%', #{templateGroup}, '%')</if>
<if test="status != null and status != ''"> and status = #{status}</if> <if test="status != null and status != ''"> and status = #{status}</if>
</where> </where>
order by sort_num asc, create_time asc order by sort_num asc, create_time asc
@@ -43,6 +45,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
insert into tt_copy_template insert into tt_copy_template
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
<if test="templateName != null and templateName != ''">template_name,</if> <if test="templateName != null and templateName != ''">template_name,</if>
<if test="templateGroup != null and templateGroup != ''">template_group,</if>
<if test="templateBody != null">template_body,</if> <if test="templateBody != null">template_body,</if>
<if test="sortNum != null">sort_num,</if> <if test="sortNum != null">sort_num,</if>
<if test="status != null and status != ''">status,</if> <if test="status != null and status != ''">status,</if>
@@ -51,6 +54,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="templateName != null and templateName != ''">#{templateName},</if> <if test="templateName != null and templateName != ''">#{templateName},</if>
<if test="templateGroup != null and templateGroup != ''">#{templateGroup},</if>
<if test="templateBody != null">#{templateBody},</if> <if test="templateBody != null">#{templateBody},</if>
<if test="sortNum != null">#{sortNum},</if> <if test="sortNum != null">#{sortNum},</if>
<if test="status != null and status != ''">#{status},</if> <if test="status != null and status != ''">#{status},</if>
@@ -63,6 +67,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
update tt_copy_template update tt_copy_template
<trim prefix="SET" suffixOverrides=","> <trim prefix="SET" suffixOverrides=",">
<if test="templateName != null and templateName != ''">template_name = #{templateName},</if> <if test="templateName != null and templateName != ''">template_name = #{templateName},</if>
<if test="templateGroup != null and templateGroup != ''">template_group = #{templateGroup},</if>
<if test="templateBody != null">template_body = #{templateBody},</if> <if test="templateBody != null">template_body = #{templateBody},</if>
<if test="sortNum != null">sort_num = #{sortNum},</if> <if test="sortNum != null">sort_num = #{sortNum},</if>
<if test="status != null and status != ''">status = #{status},</if> <if test="status != null and status != ''">status = #{status},</if>
@@ -82,4 +87,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach> </foreach>
</delete> </delete>
</mapper> </mapper>

View File

@@ -154,6 +154,14 @@
v-hasPermi="['office:code:query']" v-hasPermi="['office:code:query']"
>查看 >查看
</el-button> </el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document"
@click="handleCopywriting(scope.row)"
v-hasPermi="['office:code:query']"
>文案
</el-button>
<el-button <el-button
size="mini" size="mini"
type="text" type="text"
@@ -275,6 +283,90 @@
</div> </div>
</el-dialog> </el-dialog>
<!-- 文案预览对话框 -->
<el-dialog
:visible.sync="copywritingDialogVisible"
width="92%"
custom-class="copywriting-dialog-window"
append-to-body
@closed="resetCopywritingDialog"
>
<template slot="title">
<div class="copywriting-dialog-title">
<span>文案:</span>
<span
class="copywriting-project-name"
:title="copywritingProject.codeName || ''"
>{{ copywritingProject.codeName || '' }}</span>
</div>
</template>
<div v-loading="copywritingLoading" class="copywriting-shell">
<el-radio-group
v-if="groupedCopyTemplates.length"
v-model="copywritingGroupName"
size="small"
class="copywriting-group-tabs"
@change="handleCopywritingGroupChange"
>
<el-radio-button
v-for="group in groupedCopyTemplates"
:key="group.name"
:label="group.name"
>
{{ group.name }}
<span class="copywriting-group-count">{{ group.templates.length }}</span>
</el-radio-button>
</el-radio-group>
<div v-if="groupedCopyTemplates.length" class="copywriting-layout">
<div class="copywriting-template-panel">
<div class="copywriting-panel-title">模板列表</div>
<div v-if="activeCopywritingTemplates.length" class="copywriting-template-list">
<button
v-for="tpl in activeCopywritingTemplates"
:key="tpl.templateId"
type="button"
class="copywriting-template-item"
:class="{ 'is-active': tpl.templateId === copywritingTemplateId }"
@click="selectCopywritingTemplate(tpl)"
>
<span>{{ tpl.templateName }}</span>
<i
v-if="tpl.templateId === copywritingTemplateId"
class="el-icon-check"
/>
</button>
</div>
<div v-else class="copywriting-empty">暂无启用的文案模板</div>
</div>
<div class="copywriting-preview-panel">
<div class="copywriting-panel-title">文案预览</div>
<pre class="copywriting-preview">{{ copywritingContent }}</pre>
<div class="copywriting-preview-footer">
<span>字数:{{ copywritingContent.length }}</span>
<div>
<el-button
:disabled="!copywritingTemplateId"
@click="copyCurrentWriting(true)"
>复制并关闭</el-button>
<el-button
:type="copywritingCopied ? 'success' : 'primary'"
:icon="copywritingCopied ? 'el-icon-check' : 'el-icon-document-copy'"
:disabled="!copywritingTemplateId"
@click="copyCurrentWriting(false)"
>{{ copywritingCopied ? '已复制' : '复制文案' }}</el-button>
</div>
</div>
</div>
</div>
<div v-else class="copywriting-empty-state">
暂无启用的文案模板
</div>
</div>
</el-dialog>
<!-- 封面预览对话框 --> <!-- 封面预览对话框 -->
<el-dialog :title="'封面预览'" :visible.sync="coverDialogVisible" width="500px" append-to-body> <el-dialog :title="'封面预览'" :visible.sync="coverDialogVisible" width="500px" append-to-body>
<el-form label-width="80px"> <el-form label-width="80px">
@@ -340,6 +432,14 @@
// 是否显示弹出层 // 是否显示弹出层
open: false, open: false,
openDetail: false, openDetail: false,
// 文案预览弹窗
copywritingDialogVisible: false,
copywritingLoading: false,
copywritingProject: {},
copywritingTemplateId: null,
copywritingGroupName: '',
copywritingCopied: false,
copywritingCopyTimer: null,
// 查询参数 // 查询参数
queryParams: { queryParams: {
pageNum: 1, pageNum: 1,
@@ -383,11 +483,36 @@
this.getList(); this.getList();
this.loadCopyTemplates(); this.loadCopyTemplates();
}, },
beforeDestroy() {
if (this.copywritingCopyTimer) {
clearTimeout(this.copywritingCopyTimer);
}
},
computed: { computed: {
previewList() { previewList() {
return this.form.fileList.map(i => { return this.form.fileList.map(i => {
return i.fileUrl return i.fileUrl
}) })
},
copywritingContent() {
const template = this.copyTemplates.find(item => item.templateId === this.copywritingTemplateId);
return template ? this.renderCopyTemplate(template, this.copywritingProject) : '';
},
groupedCopyTemplates() {
return this.copyTemplates.reduce((groups, template) => {
const groupName = (template.templateGroup || '').trim() || '默认分组';
let group = groups.find(item => item.name === groupName);
if (!group) {
group = { name: groupName, templates: [] };
groups.push(group);
}
group.templates.push(template);
return groups;
}, []);
},
activeCopywritingTemplates() {
const group = this.groupedCopyTemplates.find(item => item.name === this.copywritingGroupName);
return group ? group.templates : [];
} }
}, },
methods: { methods: {
@@ -426,9 +551,10 @@
}; };
this.resetForm("form"); this.resetForm("form");
}, },
copyByTemplate(tpl) { renderCopyTemplate(tpl, source) {
// 从数据库模板中提取并替换占位符 // 从数据库模板中提取并替换占位符
let codeName = this.form.codeName || ''; const code = source || {};
let codeName = code.codeName || '';
let projectCode = ''; let projectCode = '';
let projectName = codeName; let projectName = codeName;
@@ -448,16 +574,16 @@
// 项目描述转纯文本 // 项目描述转纯文本
let plainDesc = ''; let plainDesc = '';
if (this.form.codeDesc) { if (code.codeDesc) {
let codeDesc = this.form.codeDesc.replace(/<\/p>/g, '</p>\n'); let codeDesc = code.codeDesc.replace(/<\/p>/g, '</p>\n');
plainDesc = html2Text(codeDesc.trimEnd()); plainDesc = html2Text(codeDesc.trimEnd());
} }
// 生成截图列表 // 生成截图列表
let screenshots = ''; let screenshots = '';
let index = 0; let index = 0;
if (this.form.fileList && this.form.fileList.length > 0) { if (code.fileList && code.fileList.length > 0) {
for (let item of this.form.fileList) { for (let item of code.fileList) {
screenshots = screenshots + ++index + '.' + item.fileName + '\n![](' + item.fileUrl + ')\n\n'; screenshots = screenshots + ++index + '.' + item.fileName + '\n![](' + item.fileUrl + ')\n\n';
} }
} else { } else {
@@ -470,13 +596,17 @@
content = content.replace(/{projectCode}/g, projectCode); content = content.replace(/{projectCode}/g, projectCode);
content = content.replace(/{projectName}/g, projectName); content = content.replace(/{projectName}/g, projectName);
content = content.replace(/{codeDesc}/g, plainDesc); content = content.replace(/{codeDesc}/g, plainDesc);
content = content.replace(/{codeEnvironment}/g, this.form.codeEnvironment || ''); content = content.replace(/{codeEnvironment}/g, code.codeEnvironment || '');
content = content.replace(/{frontendTechnology}/g, this.form.frontendTechnology || ''); content = content.replace(/{frontendTechnology}/g, code.frontendTechnology || '');
content = content.replace(/{backendTechnology}/g, this.form.backendTechnology || ''); content = content.replace(/{backendTechnology}/g, code.backendTechnology || '');
content = content.replace(/{databaseTechnology}/g, this.form.databaseTechnology || ''); content = content.replace(/{databaseTechnology}/g, code.databaseTechnology || '');
content = content.replace(/{codeTechnology}/g, this.form.codeTechnology || ''); content = content.replace(/{codeTechnology}/g, code.codeTechnology || '');
content = content.replace(/{diskLink}/g, this.form.diskLink || ''); content = content.replace(/{diskLink}/g, code.diskLink || '');
content = content.replace(/{screenshots}/g, screenshots); content = content.replace(/{screenshots}/g, screenshots);
return content;
},
copyByTemplate(tpl) {
const content = this.renderCopyTemplate(tpl, this.form);
navigator.clipboard.writeText(content).then(() => { navigator.clipboard.writeText(content).then(() => {
this.$message({ message: '复制成功', type: 'success' }); this.$message({ message: '复制成功', type: 'success' });
@@ -488,8 +618,49 @@
listEnabledTemplates().then(response => { listEnabledTemplates().then(response => {
this.copyTemplates = response.data || []; this.copyTemplates = response.data || [];
this.selectDefaultArticleTemplate(); this.selectDefaultArticleTemplate();
this.selectDefaultCopywritingTemplate();
}); });
}, },
selectDefaultCopywritingTemplate() {
if (!this.copyTemplates.length) {
this.copywritingTemplateId = null;
this.copywritingGroupName = '';
return;
}
let selectedTemplate = this.copyTemplates.find(tpl => tpl.templateId === this.copywritingTemplateId);
if (!selectedTemplate) {
let lastTemplateId = '';
try {
lastTemplateId = window.localStorage.getItem('officeCopywritingTemplateId') || '';
} catch (error) {
lastTemplateId = '';
}
selectedTemplate = this.copyTemplates.find(tpl => String(tpl.templateId) === lastTemplateId)
|| this.copyTemplates[0];
}
this.selectCopywritingTemplate(selectedTemplate);
},
getCopywritingGroupName(template) {
return ((template && template.templateGroup) || '').trim() || '默认分组';
},
selectCopywritingTemplate(template) {
if (!template) {
return;
}
this.copywritingTemplateId = template.templateId;
this.copywritingGroupName = this.getCopywritingGroupName(template);
this.copywritingCopied = false;
try {
window.localStorage.setItem('officeCopywritingTemplateId', String(template.templateId));
} catch (error) {
}
},
handleCopywritingGroupChange(groupName) {
const group = this.groupedCopyTemplates.find(item => item.name === groupName);
if (group && group.templates.length) {
this.selectCopywritingTemplate(group.templates[0]);
}
},
selectDefaultArticleTemplate() { selectDefaultArticleTemplate() {
if (!this.copyTemplates.length) { if (!this.copyTemplates.length) {
this.articleTemplateId = null; this.articleTemplateId = null;
@@ -584,6 +755,57 @@
this.title = "查看源码信息"; this.title = "查看源码信息";
}); });
}, },
/** 文案预览按钮操作 */
handleCopywriting(row) {
this.copywritingLoading = true;
this.copywritingProject = {};
this.copywritingTemplateId = null;
this.copywritingGroupName = '';
this.copywritingCopied = false;
this.copywritingDialogVisible = true;
this.selectDefaultCopywritingTemplate();
getCode(row.codeId).then(response => {
this.copywritingProject = response.data || {};
}).catch(() => {
this.copywritingDialogVisible = false;
}).finally(() => {
this.copywritingLoading = false;
});
},
resetCopywritingDialog() {
if (this.copywritingCopyTimer) {
clearTimeout(this.copywritingCopyTimer);
this.copywritingCopyTimer = null;
}
this.copywritingProject = {};
this.copywritingTemplateId = null;
this.copywritingGroupName = '';
this.copywritingCopied = false;
this.copywritingLoading = false;
},
copyCurrentWriting(closeAfterCopy) {
if (!this.copywritingTemplateId) {
this.$modal.msgError('请选择文案模板');
return;
}
navigator.clipboard.writeText(this.copywritingContent).then(() => {
if (closeAfterCopy) {
this.copywritingDialogVisible = false;
this.$modal.msgSuccess('文案复制成功');
return;
}
this.copywritingCopied = true;
if (this.copywritingCopyTimer) {
clearTimeout(this.copywritingCopyTimer);
}
this.copywritingCopyTimer = setTimeout(() => {
this.copywritingCopied = false;
this.copywritingCopyTimer = null;
}, 1800);
}).catch(error => {
this.$modal.msgError('复制失败:' + error);
});
},
showCoverDialog(row) { showCoverDialog(row) {
this.currentRow = row this.currentRow = row
this.selectDefaultArticleTemplate() this.selectDefaultArticleTemplate()
@@ -702,3 +924,198 @@
} }
}; };
</script> </script>
<style scoped>
.copywriting-dialog-window {
max-width: 1180px;
}
.copywriting-dialog-title {
display: flex;
min-width: 0;
padding-right: 40px;
color: #303133;
font-size: 20px;
line-height: 24px;
}
.copywriting-project-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.copywriting-shell {
min-height: 420px;
}
.copywriting-group-tabs {
display: flex;
max-width: 100%;
margin-bottom: 16px;
padding-bottom: 4px;
overflow-x: auto;
white-space: nowrap;
}
.copywriting-group-count {
display: inline-block;
min-width: 18px;
margin-left: 4px;
padding: 0 5px;
border-radius: 9px;
background: rgba(144, 147, 153, 0.16);
font-size: 12px;
line-height: 18px;
}
.copywriting-layout {
display: flex;
height: 62vh;
min-height: 420px;
max-height: 620px;
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
}
.copywriting-template-panel {
flex: 0 0 240px;
padding: 18px 14px;
border-right: 1px solid #dcdfe6;
background: #fafafa;
overflow-y: auto;
}
.copywriting-preview-panel {
display: flex;
flex: 1;
min-width: 0;
padding: 20px;
flex-direction: column;
}
.copywriting-panel-title {
margin-bottom: 14px;
color: #303133;
font-size: 16px;
font-weight: 600;
}
.copywriting-template-list {
display: flex;
width: 100%;
flex-direction: column;
}
.copywriting-template-item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 8px;
padding: 11px 12px;
border: 1px solid transparent;
border-radius: 4px;
outline: none;
background: transparent;
color: #606266;
font-size: 14px;
line-height: 20px;
text-align: left;
cursor: pointer;
transition: all 0.2s;
}
.copywriting-template-item:last-child {
margin-bottom: 0;
}
.copywriting-template-item:hover {
background: #ecf5ff;
color: #409eff;
}
.copywriting-template-item.is-active {
border-color: #b3d8ff;
background: #ecf5ff;
color: #409eff;
font-weight: 600;
}
.copywriting-template-item span {
min-width: 0;
overflow-wrap: anywhere;
}
.copywriting-template-item i {
flex: none;
margin-left: 8px;
font-weight: 700;
}
.copywriting-empty {
color: #909399;
font-size: 14px;
}
.copywriting-empty-state {
display: flex;
align-items: center;
justify-content: center;
min-height: 420px;
border: 1px solid #ebeef5;
color: #909399;
}
.copywriting-preview {
flex: 1;
min-height: 0;
margin: 0;
padding: 16px;
overflow: auto;
border: 1px solid #ebeef5;
border-radius: 4px;
background: #fafafa;
color: #303133;
font-family: Consolas, "Courier New", monospace;
font-size: 14px;
line-height: 1.7;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.copywriting-preview-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 16px;
color: #606266;
}
@media (max-width: 768px) {
.copywriting-layout {
height: 68vh;
min-height: 0;
flex-direction: column;
}
.copywriting-template-panel {
flex: 0 0 auto;
max-height: 180px;
border-right: 0;
border-bottom: 1px solid #dcdfe6;
}
.copywriting-preview-panel {
padding: 14px;
}
.copywriting-preview-footer {
align-items: flex-start;
gap: 12px;
flex-direction: column;
}
}
</style>

View File

@@ -9,6 +9,14 @@
@keyup.enter.native="handleQuery" @keyup.enter.native="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="模板分组" prop="templateGroup">
<el-input
v-model="queryParams.templateGroup"
placeholder="请输入模板分组"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable> <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option label="正常" value="0" /> <el-option label="正常" value="0" />
@@ -37,6 +45,7 @@
<el-table v-loading="loading" :data="templateList" @selection-change="handleSelectionChange"> <el-table v-loading="loading" :data="templateList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="按钮名称" align="center" prop="templateName" width="160" /> <el-table-column label="按钮名称" align="center" prop="templateName" width="160" />
<el-table-column label="模板分组" align="center" prop="templateGroup" width="140" />
<el-table-column label="排序" align="center" prop="sortNum" width="80" /> <el-table-column label="排序" align="center" prop="sortNum" width="80" />
<el-table-column label="状态" align="center" prop="status" width="100"> <el-table-column label="状态" align="center" prop="status" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
@@ -63,6 +72,9 @@
<el-form-item label="按钮名称" prop="templateName"> <el-form-item label="按钮名称" prop="templateName">
<el-input v-model="form.templateName" placeholder="请输入按钮显示名称,例如:复制网站文案" /> <el-input v-model="form.templateName" placeholder="请输入按钮显示名称,例如:复制网站文案" />
</el-form-item> </el-form-item>
<el-form-item label="模板分组" prop="templateGroup">
<el-input v-model="form.templateGroup" placeholder="请输入模板分组,例如:闲鱼、公众号" />
</el-form-item>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="排序" prop="sortNum"> <el-form-item label="排序" prop="sortNum">
@@ -142,11 +154,13 @@ export default {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
templateName: null, templateName: null,
templateGroup: null,
status: null status: null
}, },
form: {}, form: {},
rules: { rules: {
templateName: [{ required: true, message: "按钮名称不能为空", trigger: "blur" }], templateName: [{ required: true, message: "按钮名称不能为空", trigger: "blur" }],
templateGroup: [{ required: true, message: "模板分组不能为空", trigger: "blur" }],
templateBody: [{ required: true, message: "模板内容不能为空", trigger: "blur" }], templateBody: [{ required: true, message: "模板内容不能为空", trigger: "blur" }],
sortNum: [{ required: true, message: "排序不能为空", trigger: "blur" }] sortNum: [{ required: true, message: "排序不能为空", trigger: "blur" }]
} }
@@ -172,6 +186,7 @@ export default {
this.form = { this.form = {
templateId: null, templateId: null,
templateName: null, templateName: null,
templateGroup: "默认分组",
templateBody: null, templateBody: null,
sortNum: 0, sortNum: 0,
status: "0" status: "0"

View File

@@ -5,6 +5,7 @@ DROP TABLE IF EXISTS tt_copy_template;
CREATE TABLE tt_copy_template ( CREATE TABLE tt_copy_template (
template_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '模板ID', template_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '模板ID',
template_name VARCHAR(100) NOT NULL COMMENT '按钮显示名称', template_name VARCHAR(100) NOT NULL COMMENT '按钮显示名称',
template_group VARCHAR(100) NOT NULL DEFAULT '默认分组' COMMENT '模板分组',
template_body LONGTEXT NOT NULL COMMENT '文案模板内容,支持 {变量} 占位符', template_body LONGTEXT NOT NULL COMMENT '文案模板内容,支持 {变量} 占位符',
sort_num INT DEFAULT 0 COMMENT '排序号', sort_num INT DEFAULT 0 COMMENT '排序号',
status CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', status CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',

View File

@@ -0,0 +1,8 @@
-- 文案模板增加分组字段(已有数据库升级脚本)
ALTER TABLE `tt_copy_template`
ADD COLUMN `template_group` VARCHAR(100) NOT NULL DEFAULT '默认分组' COMMENT '模板分组' AFTER `template_name`;
-- 兼容历史空数据
UPDATE `tt_copy_template`
SET `template_group` = '默认分组'
WHERE `template_group` IS NULL OR TRIM(`template_group`) = '';