From a466caa79351361ca4576c209f76e6155c0abe1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8E=8B=E9=B9=8F?=
Date: Mon, 7 Sep 2026 15:40:29 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=96=87=E6=A1=88?=
=?UTF-8?q?=E6=A8=A1=E6=9D=BF=E5=88=86=E7=BB=84=E4=B8=8E=E5=A4=8D=E5=88=B6?=
=?UTF-8?q?=E9=A2=84=E8=A7=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ruoyi/office/domain/TtCopyTemplate.java | 15 +-
.../mapper/office/TtCopyTemplateMapper.xml | 9 +-
ruoyi-ui/src/views/office/code/index.vue | 441 +++++++++++++++++-
.../src/views/office/copyTemplate/index.vue | 15 +
sql/copy_template.sql | 1 +
sql/copy_template_group.sql | 8 +
6 files changed, 474 insertions(+), 15 deletions(-)
create mode 100644 sql/copy_template_group.sql
diff --git a/ruoyi-office/src/main/java/com/ruoyi/office/domain/TtCopyTemplate.java b/ruoyi-office/src/main/java/com/ruoyi/office/domain/TtCopyTemplate.java
index b8e9ca2..c316e45 100644
--- a/ruoyi-office/src/main/java/com/ruoyi/office/domain/TtCopyTemplate.java
+++ b/ruoyi-office/src/main/java/com/ruoyi/office/domain/TtCopyTemplate.java
@@ -21,6 +21,10 @@ public class TtCopyTemplate extends BaseEntity {
@Excel(name = "按钮名称")
private String templateName;
+ /** 模板分组 */
+ @Excel(name = "模板分组")
+ private String templateGroup;
+
/** 文案模板内容,支持 {变量} 占位符 */
@Excel(name = "模板内容")
private String templateBody;
@@ -49,6 +53,14 @@ public class TtCopyTemplate extends BaseEntity {
return templateName;
}
+ public void setTemplateGroup(String templateGroup) {
+ this.templateGroup = templateGroup;
+ }
+
+ public String getTemplateGroup() {
+ return templateGroup;
+ }
+
public void setTemplateBody(String templateBody) {
this.templateBody = templateBody;
}
@@ -78,6 +90,7 @@ public class TtCopyTemplate extends BaseEntity {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("templateId", getTemplateId())
.append("templateName", getTemplateName())
+ .append("templateGroup", getTemplateGroup())
.append("templateBody", getTemplateBody())
.append("sortNum", getSortNum())
.append("status", getStatus())
@@ -85,4 +98,4 @@ public class TtCopyTemplate extends BaseEntity {
.append("updateTime", getUpdateTime())
.toString();
}
-}
\ No newline at end of file
+}
diff --git a/ruoyi-office/src/main/resources/mapper/office/TtCopyTemplateMapper.xml b/ruoyi-office/src/main/resources/mapper/office/TtCopyTemplateMapper.xml
index bf933eb..f2cbf1d 100644
--- a/ruoyi-office/src/main/resources/mapper/office/TtCopyTemplateMapper.xml
+++ b/ruoyi-office/src/main/resources/mapper/office/TtCopyTemplateMapper.xml
@@ -7,6 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+
@@ -15,7 +16,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- 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
@@ -23,6 +24,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and template_name like concat('%', #{templateName}, '%')
+ and template_group like concat('%', #{templateGroup}, '%')
and status = #{status}
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
template_name,
+ template_group,
template_body,
sort_num,
status,
@@ -51,6 +54,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{templateName},
+ #{templateGroup},
#{templateBody},
#{sortNum},
#{status},
@@ -63,6 +67,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
update tt_copy_template
template_name = #{templateName},
+ template_group = #{templateGroup},
template_body = #{templateBody},
sort_num = #{sortNum},
status = #{status},
@@ -82,4 +87,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-
\ No newline at end of file
+
diff --git a/ruoyi-ui/src/views/office/code/index.vue b/ruoyi-ui/src/views/office/code/index.vue
index ddb40ca..c695dfd 100644
--- a/ruoyi-ui/src/views/office/code/index.vue
+++ b/ruoyi-ui/src/views/office/code/index.vue
@@ -154,6 +154,14 @@
v-hasPermi="['office:code:query']"
>查看
+ 文案
+
+
+
+
+
+ 文案:
+ {{ copywritingProject.codeName || '' }}
+
+
+
+
+
+
+ {{ group.name }}
+ {{ group.templates.length }}
+
+
+
+
+
+
模板列表
+
+
+
+
暂无启用的文案模板
+
+
+
+
文案预览
+
{{ copywritingContent }}
+
+
+
+
+ 暂无启用的文案模板
+
+
+
+
@@ -340,6 +432,14 @@
// 是否显示弹出层
open: false,
openDetail: false,
+ // 文案预览弹窗
+ copywritingDialogVisible: false,
+ copywritingLoading: false,
+ copywritingProject: {},
+ copywritingTemplateId: null,
+ copywritingGroupName: '',
+ copywritingCopied: false,
+ copywritingCopyTimer: null,
// 查询参数
queryParams: {
pageNum: 1,
@@ -383,11 +483,36 @@
this.getList();
this.loadCopyTemplates();
},
+ beforeDestroy() {
+ if (this.copywritingCopyTimer) {
+ clearTimeout(this.copywritingCopyTimer);
+ }
+ },
computed: {
previewList() {
return this.form.fileList.map(i => {
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: {
@@ -426,9 +551,10 @@
};
this.resetForm("form");
},
- copyByTemplate(tpl) {
+ renderCopyTemplate(tpl, source) {
// 从数据库模板中提取并替换占位符
- let codeName = this.form.codeName || '';
+ const code = source || {};
+ let codeName = code.codeName || '';
let projectCode = '';
let projectName = codeName;
@@ -448,16 +574,16 @@
// 项目描述转纯文本
let plainDesc = '';
- if (this.form.codeDesc) {
- let codeDesc = this.form.codeDesc.replace(/<\/p>/g, '
\n');
+ if (code.codeDesc) {
+ let codeDesc = code.codeDesc.replace(/<\/p>/g, '\n');
plainDesc = html2Text(codeDesc.trimEnd());
}
// 生成截图列表
let screenshots = '';
let index = 0;
- if (this.form.fileList && this.form.fileList.length > 0) {
- for (let item of this.form.fileList) {
+ if (code.fileList && code.fileList.length > 0) {
+ for (let item of code.fileList) {
screenshots = screenshots + ++index + '.' + item.fileName + '\n\n\n';
}
} else {
@@ -470,13 +596,17 @@
content = content.replace(/{projectCode}/g, projectCode);
content = content.replace(/{projectName}/g, projectName);
content = content.replace(/{codeDesc}/g, plainDesc);
- content = content.replace(/{codeEnvironment}/g, this.form.codeEnvironment || '');
- content = content.replace(/{frontendTechnology}/g, this.form.frontendTechnology || '');
- content = content.replace(/{backendTechnology}/g, this.form.backendTechnology || '');
- content = content.replace(/{databaseTechnology}/g, this.form.databaseTechnology || '');
- content = content.replace(/{codeTechnology}/g, this.form.codeTechnology || '');
- content = content.replace(/{diskLink}/g, this.form.diskLink || '');
+ content = content.replace(/{codeEnvironment}/g, code.codeEnvironment || '');
+ content = content.replace(/{frontendTechnology}/g, code.frontendTechnology || '');
+ content = content.replace(/{backendTechnology}/g, code.backendTechnology || '');
+ content = content.replace(/{databaseTechnology}/g, code.databaseTechnology || '');
+ content = content.replace(/{codeTechnology}/g, code.codeTechnology || '');
+ content = content.replace(/{diskLink}/g, code.diskLink || '');
content = content.replace(/{screenshots}/g, screenshots);
+ return content;
+ },
+ copyByTemplate(tpl) {
+ const content = this.renderCopyTemplate(tpl, this.form);
navigator.clipboard.writeText(content).then(() => {
this.$message({ message: '复制成功', type: 'success' });
@@ -488,8 +618,49 @@
listEnabledTemplates().then(response => {
this.copyTemplates = response.data || [];
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() {
if (!this.copyTemplates.length) {
this.articleTemplateId = null;
@@ -584,6 +755,57 @@
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) {
this.currentRow = row
this.selectDefaultArticleTemplate()
@@ -702,3 +924,198 @@
}
};
+
+
diff --git a/ruoyi-ui/src/views/office/copyTemplate/index.vue b/ruoyi-ui/src/views/office/copyTemplate/index.vue
index 4a51b7a..e8153c9 100644
--- a/ruoyi-ui/src/views/office/copyTemplate/index.vue
+++ b/ruoyi-ui/src/views/office/copyTemplate/index.vue
@@ -9,6 +9,14 @@
@keyup.enter.native="handleQuery"
/>
+
+
+
@@ -37,6 +45,7 @@
+
@@ -63,6 +72,9 @@
+
+
+
@@ -142,11 +154,13 @@ export default {
pageNum: 1,
pageSize: 10,
templateName: null,
+ templateGroup: null,
status: null
},
form: {},
rules: {
templateName: [{ required: true, message: "按钮名称不能为空", trigger: "blur" }],
+ templateGroup: [{ required: true, message: "模板分组不能为空", trigger: "blur" }],
templateBody: [{ required: true, message: "模板内容不能为空", trigger: "blur" }],
sortNum: [{ required: true, message: "排序不能为空", trigger: "blur" }]
}
@@ -172,6 +186,7 @@ export default {
this.form = {
templateId: null,
templateName: null,
+ templateGroup: "默认分组",
templateBody: null,
sortNum: 0,
status: "0"
diff --git a/sql/copy_template.sql b/sql/copy_template.sql
index b3dd783..d07ddc8 100644
--- a/sql/copy_template.sql
+++ b/sql/copy_template.sql
@@ -5,6 +5,7 @@ DROP TABLE IF EXISTS tt_copy_template;
CREATE TABLE tt_copy_template (
template_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '模板ID',
template_name VARCHAR(100) NOT NULL COMMENT '按钮显示名称',
+ template_group VARCHAR(100) NOT NULL DEFAULT '默认分组' COMMENT '模板分组',
template_body LONGTEXT NOT NULL COMMENT '文案模板内容,支持 {变量} 占位符',
sort_num INT DEFAULT 0 COMMENT '排序号',
status CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
diff --git a/sql/copy_template_group.sql b/sql/copy_template_group.sql
new file mode 100644
index 0000000..41dc66e
--- /dev/null
+++ b/sql/copy_template_group.sql
@@ -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`) = '';