Add multi-platform publishing workflow
This commit is contained in:
@@ -9,11 +9,12 @@ export function listCopyTemplate(query) {
|
||||
})
|
||||
}
|
||||
|
||||
// 查询所有启用的文案模板(源码明细页按钮用)
|
||||
export function listEnabledTemplates() {
|
||||
// 按场景查询所有启用的文案模板
|
||||
export function listEnabledTemplates(templateScene) {
|
||||
return request({
|
||||
url: '/office/copyTemplate/enabled',
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
params: { templateScene }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -49,4 +50,4 @@ export function delCopyTemplate(templateId) {
|
||||
url: '/office/copyTemplate/' + templateId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
33
ruoyi-ui/src/api/office/publish.js
Normal file
33
ruoyi-ui/src/api/office/publish.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listPublishProjects(query) {
|
||||
return request({
|
||||
url: '/office/publish/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getPublishMaterial(codeId, platform) {
|
||||
return request({
|
||||
url: '/office/publish/material/' + codeId,
|
||||
method: 'get',
|
||||
params: { platform }
|
||||
})
|
||||
}
|
||||
|
||||
export function createPublishTask(data) {
|
||||
return request({
|
||||
url: '/office/publish/task',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function createPublishTasks(data) {
|
||||
return request({
|
||||
url: '/office/publish/tasks',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
<script>
|
||||
import { getToken } from "@/utils/auth";
|
||||
import Sortable from "sortablejs";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -67,6 +68,11 @@ export default {
|
||||
isShowTip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否允许拖动图片调整顺序
|
||||
enableSort: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -81,9 +87,16 @@ export default {
|
||||
headers: {
|
||||
Authorization: "Bearer " + getToken(),
|
||||
},
|
||||
fileList: []
|
||||
fileList: [],
|
||||
imageSortable: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(this.initImageSort);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.imageSortable) this.imageSortable.destroy();
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(val) {
|
||||
@@ -93,11 +106,7 @@ export default {
|
||||
// 然后将数组转为对象数组
|
||||
this.fileList = list.map(item => {
|
||||
if (typeof item === "string") {
|
||||
if (item.indexOf(this.baseUrl) === -1) {
|
||||
item = { name: this.baseUrl + item, url: item };
|
||||
} else {
|
||||
item = { name: item, url: item };
|
||||
}
|
||||
item = { name: item, url: this.getPreviewUrl(item) };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
@@ -105,6 +114,7 @@ export default {
|
||||
this.fileList = [];
|
||||
return [];
|
||||
}
|
||||
this.$nextTick(this.initImageSort);
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
@@ -117,6 +127,21 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
initImageSort() {
|
||||
if (!this.enableSort || this.imageSortable || !this.$refs.imageUpload) return;
|
||||
const list = this.$refs.imageUpload.$el.querySelector('.el-upload-list');
|
||||
if (!list) return;
|
||||
this.imageSortable = Sortable.create(list, {
|
||||
animation: 150,
|
||||
draggable: '.el-upload-list__item',
|
||||
onEnd: ({ oldIndex, newIndex }) => {
|
||||
if (oldIndex === newIndex || oldIndex == null || newIndex == null) return;
|
||||
const moved = this.fileList.splice(oldIndex, 1)[0];
|
||||
this.fileList.splice(newIndex, 0, moved);
|
||||
this.$emit("input", this.listToString(this.fileList));
|
||||
}
|
||||
});
|
||||
},
|
||||
// 上传前loading加载
|
||||
handleBeforeUpload(file) {
|
||||
let isImg = false;
|
||||
@@ -155,12 +180,12 @@ export default {
|
||||
// 上传成功回调
|
||||
handleUploadSuccess(res, file) {
|
||||
if (res.code === 200) {
|
||||
this.uploadList.push({ name: res.fileName, url: res.fileName });
|
||||
this.uploadList.push({ name: res.fileName, url: this.getPreviewUrl(res.fileName) });
|
||||
this.uploadedSuccessfully();
|
||||
} else {
|
||||
this.number--;
|
||||
this.$modal.closeLoading();
|
||||
this.$modal.msgError(res.msg);
|
||||
this.$modal.msgError((res && res.msg) || "上传图片失败,请检查服务器上传目录配置");
|
||||
this.$refs.imageUpload.handleRemove(file);
|
||||
this.uploadedSuccessfully();
|
||||
}
|
||||
@@ -175,6 +200,7 @@ export default {
|
||||
},
|
||||
// 上传失败
|
||||
handleUploadError() {
|
||||
if (this.number > 0) this.number--;
|
||||
this.$modal.msgError("上传图片失败,请重试");
|
||||
this.$modal.closeLoading();
|
||||
},
|
||||
@@ -193,13 +219,30 @@ export default {
|
||||
this.dialogImageUrl = file.url;
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
// 将后端返回的 /profile/... 相对路径转换为可访问的预览地址。
|
||||
// 完整网络地址和 data/blob 本地预览地址保持不变。
|
||||
getPreviewUrl(url) {
|
||||
if (!url || typeof url !== "string") return "";
|
||||
if (/^(https?:)?\/\//i.test(url) || /^(data|blob):/i.test(url)) return url;
|
||||
if (url.indexOf(this.baseUrl + "/") === 0 || url === this.baseUrl) return url;
|
||||
const path = url.indexOf("/") === 0 ? url : "/" + url;
|
||||
return this.baseUrl + path;
|
||||
},
|
||||
// 提交表单时仍保存相对路径,避免把环境前缀或域名写入数据库。
|
||||
getStorageUrl(url) {
|
||||
if (!url || typeof url !== "string") return "";
|
||||
if (url.indexOf(this.baseUrl + "/") === 0) {
|
||||
return url.substring(this.baseUrl.length);
|
||||
}
|
||||
return url;
|
||||
},
|
||||
// 对象转成指定字符串分隔
|
||||
listToString(list, separator) {
|
||||
let strs = "";
|
||||
separator = separator || ",";
|
||||
for (let i in list) {
|
||||
if (list[i].url) {
|
||||
strs += list[i].url.replace(this.baseUrl, "") + separator;
|
||||
strs += this.getStorageUrl(list[i].url) + separator;
|
||||
}
|
||||
}
|
||||
return strs != '' ? strs.substr(0, strs.length - 1) : '';
|
||||
@@ -223,4 +266,3 @@ export default {
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -615,7 +615,7 @@
|
||||
});
|
||||
},
|
||||
loadCopyTemplates() {
|
||||
listEnabledTemplates().then(response => {
|
||||
listEnabledTemplates('CODE').then(response => {
|
||||
this.copyTemplates = response.data || [];
|
||||
this.selectDefaultArticleTemplate();
|
||||
this.selectDefaultCopywritingTemplate();
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="模板场景" prop="templateScene">
|
||||
<el-select v-model="queryParams.templateScene" placeholder="请选择模板场景" clearable>
|
||||
<el-option label="源码文案" value="CODE" />
|
||||
<el-option label="发货文案" value="DELIVERY" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option label="正常" value="0" />
|
||||
@@ -46,6 +52,13 @@
|
||||
<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="templateGroup" width="140" />
|
||||
<el-table-column label="模板场景" align="center" prop="templateScene" width="110">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="mini" :type="scope.row.templateScene === 'DELIVERY' ? 'warning' : ''">
|
||||
{{ scope.row.templateScene === 'DELIVERY' ? '发货文案' : '源码文案' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" align="center" prop="sortNum" width="80" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template slot-scope="scope">
|
||||
@@ -75,6 +88,12 @@
|
||||
<el-form-item label="模板分组" prop="templateGroup">
|
||||
<el-input v-model="form.templateGroup" placeholder="请输入模板分组,例如:闲鱼、公众号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="模板场景" prop="templateScene">
|
||||
<el-radio-group v-model="form.templateScene">
|
||||
<el-radio label="CODE">源码文案</el-radio>
|
||||
<el-radio label="DELIVERY">发货文案</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序" prop="sortNum">
|
||||
@@ -95,34 +114,37 @@
|
||||
v-model="form.templateBody"
|
||||
type="textarea"
|
||||
:rows="20"
|
||||
placeholder="请输入文案模板内容,支持以下占位符:
|
||||
{codeName} 项目名称(完整)
|
||||
{projectCode} 项目编号(如 A075)
|
||||
{projectName} 纯项目名(去掉编号和基于...实现的前缀)
|
||||
{codeDesc} 项目描述(纯文本)
|
||||
{codeEnvironment} 运行环境
|
||||
{frontendTechnology} 前端
|
||||
{backendTechnology} 后端
|
||||
{databaseTechnology} 数据库
|
||||
{codeTechnology} 其他技术
|
||||
{diskLink} 网盘地址
|
||||
{screenshots} 截图列表(自动生成 markdown 格式)"
|
||||
:placeholder="templateBodyPlaceholder"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:0">
|
||||
<template slot="title">
|
||||
<strong>支持的占位符:</strong>
|
||||
<code>{codeName}</code> 项目名称
|
||||
<code>{projectCode}</code> 项目编号
|
||||
<code>{projectName}</code> 纯项目名
|
||||
<code>{codeDesc}</code> 描述(纯文本)
|
||||
<code>{codeEnvironment}</code> 运行环境
|
||||
<code>{frontendTechnology}</code> 前端
|
||||
<code>{backendTechnology}</code> 后端
|
||||
<code>{databaseTechnology}</code> 数据库
|
||||
<code>{codeTechnology}</code> 其他技术
|
||||
<code>{diskLink}</code> 网盘地址
|
||||
<code>{screenshots}</code> 截图列表
|
||||
<template v-if="form.templateScene === 'DELIVERY'">
|
||||
<strong>发货文案占位符:</strong>
|
||||
<code>{deliveryLinks}</code> 完整网盘文案
|
||||
<code>{quarkLink}</code> 夸克链接
|
||||
<code>{baiduLink}</code> 百度链接
|
||||
<code>{projectNum}</code> 项目编号
|
||||
<code>{projectName}</code> 项目名称
|
||||
<code>{sourceName}</code> 源码名称
|
||||
<code>{projectTechnology}</code> 项目技术
|
||||
<code>{projectDesc}</code> 项目描述
|
||||
<code>{videoLink}</code> 演示视频
|
||||
</template>
|
||||
<template v-else>
|
||||
<strong>源码文案占位符:</strong>
|
||||
<code>{codeName}</code> 项目名称
|
||||
<code>{projectCode}</code> 项目编号
|
||||
<code>{projectName}</code> 纯项目名
|
||||
<code>{codeDesc}</code> 描述(纯文本)
|
||||
<code>{codeEnvironment}</code> 运行环境
|
||||
<code>{frontendTechnology}</code> 前端
|
||||
<code>{backendTechnology}</code> 后端
|
||||
<code>{databaseTechnology}</code> 数据库
|
||||
<code>{codeTechnology}</code> 其他技术
|
||||
<code>{diskLink}</code> 网盘地址
|
||||
<code>{screenshots}</code> 截图列表
|
||||
</template>
|
||||
</template>
|
||||
</el-alert>
|
||||
</el-form>
|
||||
@@ -155,12 +177,14 @@ export default {
|
||||
pageSize: 10,
|
||||
templateName: null,
|
||||
templateGroup: null,
|
||||
templateScene: null,
|
||||
status: null
|
||||
},
|
||||
form: {},
|
||||
rules: {
|
||||
templateName: [{ required: true, message: "按钮名称不能为空", trigger: "blur" }],
|
||||
templateGroup: [{ required: true, message: "模板分组不能为空", trigger: "blur" }],
|
||||
templateScene: [{ required: true, message: "模板场景不能为空", trigger: "change" }],
|
||||
templateBody: [{ required: true, message: "模板内容不能为空", trigger: "blur" }],
|
||||
sortNum: [{ required: true, message: "排序不能为空", trigger: "blur" }]
|
||||
}
|
||||
@@ -169,6 +193,14 @@ export default {
|
||||
created() {
|
||||
this.getList();
|
||||
},
|
||||
computed: {
|
||||
templateBodyPlaceholder() {
|
||||
if (this.form.templateScene === 'DELIVERY') {
|
||||
return '请输入发货文案模板内容,例如:\n{deliveryLinks}\n\n项目部署文档\n文档地址和密码'
|
||||
}
|
||||
return '请输入源码文案模板内容,支持 {codeName}、{projectCode}、{projectName}、{codeDesc}、{diskLink}、{screenshots} 等占位符'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true;
|
||||
@@ -187,6 +219,7 @@ export default {
|
||||
templateId: null,
|
||||
templateName: null,
|
||||
templateGroup: "默认分组",
|
||||
templateScene: "CODE",
|
||||
templateBody: null,
|
||||
sortNum: 0,
|
||||
status: "0"
|
||||
|
||||
@@ -380,6 +380,7 @@ import {
|
||||
checkProjectLinks,
|
||||
checkSelectedProjectLinks
|
||||
} from '@/api/office/project'
|
||||
import { listEnabledTemplates } from '@/api/office/copyTemplate'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
@@ -404,6 +405,8 @@ export default {
|
||||
total: 0,
|
||||
// 项目清单表格数据
|
||||
projectList: [],
|
||||
// 启用的发货文案模板
|
||||
deliveryTemplates: [],
|
||||
// 弹出层标题
|
||||
title: '',
|
||||
// 是否显示弹出层
|
||||
@@ -450,6 +453,7 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.loadDeliveryTemplates()
|
||||
},
|
||||
methods: {
|
||||
/** 查询项目清单列表 */
|
||||
@@ -461,6 +465,12 @@ export default {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 查询启用的发货文案模板 */
|
||||
loadDeliveryTemplates() {
|
||||
listEnabledTemplates('DELIVERY').then(response => {
|
||||
this.deliveryTemplates = response.data || []
|
||||
})
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false
|
||||
@@ -761,20 +771,42 @@ export default {
|
||||
})
|
||||
})
|
||||
},
|
||||
copyUrlToClipboard1(row) {
|
||||
let content = ''
|
||||
renderDeliveryTemplate(template, row) {
|
||||
const linkSections = []
|
||||
if (row.projectUrl) {
|
||||
content += '夸克网盘\n'
|
||||
content += '链接:' + row.projectUrl
|
||||
}
|
||||
if (content.length > 0) {
|
||||
content += '\n\n'
|
||||
linkSections.push('夸克网盘\n链接:' + row.projectUrl)
|
||||
}
|
||||
if (row.projectBaiduUrl) {
|
||||
content += '百度网盘\n'
|
||||
content += '链接:' + row.projectBaiduUrl
|
||||
linkSections.push('百度网盘\n链接:' + row.projectBaiduUrl)
|
||||
}
|
||||
content += '\n\n项目部署文档\nhttps://www.yuque.com/feastcoding/cg5w9h/rbvp8w80h8tultgy 密码:el4q'
|
||||
const variables = {
|
||||
deliveryLinks: linkSections.join('\n\n'),
|
||||
projectNum: row.projectNum || '',
|
||||
projectName: row.projectName || '',
|
||||
sourceName: row.projectName1 || '',
|
||||
projectName1: row.projectName1 || '',
|
||||
projectTechnology: row.projectNum1 || '',
|
||||
projectDesc: row.projectDesc || '',
|
||||
quarkLink: row.projectUrl || '',
|
||||
projectUrl: row.projectUrl || '',
|
||||
baiduLink: row.projectBaiduUrl || '',
|
||||
projectBaiduUrl: row.projectBaiduUrl || '',
|
||||
videoLink: row.projectVurl || '',
|
||||
projectVurl: row.projectVurl || ''
|
||||
}
|
||||
let content = template.templateBody || ''
|
||||
Object.keys(variables).forEach(key => {
|
||||
content = content.split('{' + key + '}').join(variables[key])
|
||||
})
|
||||
return content
|
||||
},
|
||||
copyUrlToClipboard1(row, template) {
|
||||
const selectedTemplate = template || this.deliveryTemplates[0]
|
||||
if (!selectedTemplate) {
|
||||
this.$message({ message: '请先在文案模板管理中配置并启用发货文案模板', type: 'warning' })
|
||||
return
|
||||
}
|
||||
const content = this.renderDeliveryTemplate(selectedTemplate, row)
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
this.$message({
|
||||
message: '复制成功',
|
||||
|
||||
1130
ruoyi-ui/src/views/office/publish/index.vue
Normal file
1130
ruoyi-ui/src/views/office/publish/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user