feat: 优化文案模板分组与复制预览
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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\n\n';
|
screenshots = screenshots + ++index + '.' + item.fileName + '\n\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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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停用)',
|
||||||
|
|||||||
8
sql/copy_template_group.sql
Normal file
8
sql/copy_template_group.sql
Normal 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`) = '';
|
||||||
Reference in New Issue
Block a user