mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-05-07 16:14:42 +08:00
暂提 消息模板功能
This commit is contained in:
480
manager/src/views/sys/message/messageTemplate.vue
Normal file
480
manager/src/views/sys/message/messageTemplate.vue
Normal file
@@ -0,0 +1,480 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<Card>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||||
<div style="font-weight:600;">消息模板管理</div>
|
||||
<div>
|
||||
<Button size="small" style="margin-right:8px;" @click="reloadCurrent">刷新</Button>
|
||||
<Button size="small" style="margin-right:8px;" :loading="syncOaLoading" @click="syncOaTemplates">同步服务号模板</Button>
|
||||
<Button size="small" :loading="syncMpLoading" @click="syncMpTemplates">同步小程序模板</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs v-model="activeTab">
|
||||
<TabPane label="会员消息模板" name="member">
|
||||
<Alert type="info" show-icon style="margin-bottom:10px;">
|
||||
支持维护站内信、微信服务号消息、微信小程序订阅消息开关。未匹配模板的渠道会显示禁用。
|
||||
</Alert>
|
||||
<Table :loading="loading" border :columns="memberColumns" :data="memberRows"></Table>
|
||||
<Row type="flex" justify="end" class="mt_10">
|
||||
<Page
|
||||
:current="searchForm.pageNumber"
|
||||
:total="total"
|
||||
:page-size="searchForm.pageSize"
|
||||
@on-change="changePage"
|
||||
@on-page-size-change="changePageSize"
|
||||
:page-size-opts="[20, 50, 100]"
|
||||
size="small"
|
||||
show-total
|
||||
show-elevator
|
||||
show-sizer
|
||||
></Page>
|
||||
</Row>
|
||||
</TabPane>
|
||||
<TabPane label="商户消息模板" name="store">
|
||||
<Alert>商户消息模板待扩展,后续可复用会员模板能力。</Alert>
|
||||
</TabPane>
|
||||
<TabPane label="平台消息模板" name="platform">
|
||||
<Alert>平台消息模板待扩展,后续可复用会员模板能力。</Alert>
|
||||
</TabPane>
|
||||
<TabPane label="邮箱消息模板" name="email">
|
||||
<Alert>邮箱消息模板待扩展,后续可按场景映射邮箱模板。</Alert>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
|
||||
<Modal v-model="noticeModalVisible" title="站内信模板" width="700">
|
||||
<Form :label-width="120">
|
||||
<FormItem label="模板应用场景">
|
||||
<Input :value="currentRow.noticeNode || '-'" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="启用">
|
||||
<i-switch
|
||||
:value="isOpen(currentRow.noticeStatus)"
|
||||
@on-change="(v) => toggleNoticeChannel(v, currentRow)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="模板内容">
|
||||
<Input :value="noticeDetailText" type="textarea" :autosize="{ minRows: 4, maxRows: 8 }" readonly />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div slot="footer">
|
||||
<Button @click="noticeModalVisible = false">关闭</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal v-model="oaModalVisible" title="微信服务号消息模板" width="700">
|
||||
<Form :label-width="140">
|
||||
<FormItem label="模板应用场景">
|
||||
<Input :value="currentRow.noticeNode || '-'" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="启用">
|
||||
<i-switch
|
||||
:value="isOpen(currentRow.oaStatus)"
|
||||
:disabled="!currentRow.oaId"
|
||||
@on-change="(v) => toggleOaChannel(v, currentRow)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="模板名称">
|
||||
<Input :value="currentRow.oaName || '-'" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="微信模板ID">
|
||||
<Input :value="currentRow.oaCode || '-'" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="模板内容">
|
||||
<Input :value="currentRow.oaContent || '-'" type="textarea" :autosize="{ minRows: 4, maxRows: 8 }" readonly />
|
||||
</FormItem>
|
||||
<Alert v-if="!currentRow.oaId" type="warning">当前场景未匹配到微信服务号模板。</Alert>
|
||||
</Form>
|
||||
<div slot="footer">
|
||||
<Button @click="oaModalVisible = false">关闭</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal v-model="emailModalVisible" title="邮箱消息模板" width="700">
|
||||
<Form :label-width="120">
|
||||
<FormItem label="模板应用场景">
|
||||
<Input :value="currentRow.noticeNode || '-'" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="启用">
|
||||
<i-switch
|
||||
:value="isOpen(currentRow.emailStatus)"
|
||||
@on-change="(v) => toggleEmailChannel(v, currentRow)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="模板内容">
|
||||
<Input :value="emailDetailText" type="textarea" :autosize="{ minRows: 4, maxRows: 8 }" readonly />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div slot="footer">
|
||||
<Button @click="emailModalVisible = false">关闭</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal v-model="mpModalVisible" title="微信小程序订阅消息模板" width="700">
|
||||
<Form :label-width="150">
|
||||
<FormItem label="模板应用场景">
|
||||
<Input :value="currentRow.noticeNode || '-'" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="启用">
|
||||
<i-switch
|
||||
:value="isOpen(currentRow.mpStatus)"
|
||||
:disabled="!currentRow.mpId"
|
||||
@on-change="(v) => toggleMpChannel(v, currentRow)"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="模板名称">
|
||||
<Input :value="currentRow.mpName || '-'" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="公共模板库ID">
|
||||
<Input :value="currentRow.mpTemplateId || '-'" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="订阅消息模板ID">
|
||||
<Input :value="currentRow.mpCode || '-'" readonly />
|
||||
</FormItem>
|
||||
<FormItem label="模板内容">
|
||||
<Input :value="currentRow.mpContent || '-'" type="textarea" :autosize="{ minRows: 4, maxRows: 8 }" readonly />
|
||||
</FormItem>
|
||||
<Alert v-if="!currentRow.mpId" type="warning">当前场景未匹配到微信小程序模板。</Alert>
|
||||
</Form>
|
||||
<div slot="footer">
|
||||
<Button @click="mpModalVisible = false">关闭</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as API_Setting from "@/api/setting.js";
|
||||
|
||||
export default {
|
||||
name: "messageTemplate",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
activeTab: "member",
|
||||
syncOaLoading: false,
|
||||
syncMpLoading: false,
|
||||
total: 0,
|
||||
searchForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
},
|
||||
memberRows: [],
|
||||
currentRow: {},
|
||||
noticeDetailText: "",
|
||||
noticeModalVisible: false,
|
||||
oaModalVisible: false,
|
||||
mpModalVisible: false,
|
||||
emailModalVisible: false,
|
||||
emailDetailText: "",
|
||||
memberColumns: [
|
||||
{ title: "序号", key: "index", width: 80, align: "center" },
|
||||
{ title: "模板应用场景", key: "noticeNode", minWidth: 180, tooltip: true },
|
||||
{
|
||||
title: "站内信",
|
||||
key: "noticeStatus",
|
||||
width: 110,
|
||||
align: "center",
|
||||
render: (h, params) =>
|
||||
h("i-switch", {
|
||||
props: { value: this.isOpen(params.row.noticeStatus) },
|
||||
on: { "on-change": (v) => this.toggleNoticeChannel(v, params.row) },
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "微信服务号消息",
|
||||
key: "oaStatus",
|
||||
width: 140,
|
||||
align: "center",
|
||||
render: (h, params) =>
|
||||
h("i-switch", {
|
||||
props: { value: this.isOpen(params.row.oaStatus), disabled: !params.row.oaId },
|
||||
on: { "on-change": (v) => this.toggleOaChannel(v, params.row) },
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "微信小程序订阅消息",
|
||||
key: "mpStatus",
|
||||
width: 160,
|
||||
align: "center",
|
||||
render: (h, params) =>
|
||||
h("i-switch", {
|
||||
props: { value: this.isOpen(params.row.mpStatus), disabled: !params.row.mpId },
|
||||
on: { "on-change": (v) => this.toggleMpChannel(v, params.row) },
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "邮箱",
|
||||
key: "emailStatus",
|
||||
width: 120,
|
||||
align: "center",
|
||||
render: (h, params) =>
|
||||
h("i-switch", {
|
||||
props: {
|
||||
value: this.isOpen(params.row.emailStatus),
|
||||
// email_content 为空则禁用(避免切到无正文模板)
|
||||
disabled: !params.row.emailContent,
|
||||
},
|
||||
on: { "on-change": (v) => this.toggleEmailChannel(v, params.row) },
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: 400,
|
||||
align: "center",
|
||||
render: (h, params) => {
|
||||
const linkStyle = { color: "#2d8cf0", cursor: "pointer", margin: "0 8px" };
|
||||
const disabledStyle = { color: "#c5c8ce", cursor: "not-allowed", margin: "0 8px" };
|
||||
return h("div", [
|
||||
h("a", { style: linkStyle, on: { click: () => this.openNoticeModal(params.row) } }, "站内信"),
|
||||
h(
|
||||
"a",
|
||||
{
|
||||
style: params.row.oaId ? linkStyle : disabledStyle,
|
||||
on: params.row.oaId ? { click: () => this.openOaModal(params.row) } : {},
|
||||
},
|
||||
"微信服务号消息"
|
||||
),
|
||||
h(
|
||||
"a",
|
||||
{
|
||||
style: params.row.mpId ? linkStyle : disabledStyle,
|
||||
on: params.row.mpId ? { click: () => this.openMpModal(params.row) } : {},
|
||||
},
|
||||
"微信小程序订阅消息"
|
||||
),
|
||||
h("a", { style: linkStyle, on: { click: () => this.openEmailModal(params.row) } }, "邮箱"),
|
||||
]);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
isOpen(status) {
|
||||
return String(status || "").toUpperCase() === "OPEN";
|
||||
},
|
||||
/** 微信实体 Boolean enable 与表格 OPEN/CLOSE 统一 */
|
||||
wechatEnableToOpenClose(enable) {
|
||||
if (enable === true || enable === "true" || enable === 1) return "OPEN";
|
||||
return "CLOSE";
|
||||
},
|
||||
toOpenClose(flag) {
|
||||
return flag ? "OPEN" : "CLOSE";
|
||||
},
|
||||
safeText(v) {
|
||||
return v === undefined || v === null ? "" : String(v);
|
||||
},
|
||||
pickByKeys(obj, keys) {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const val = obj ? obj[keys[i]] : "";
|
||||
if (val !== undefined && val !== null && val !== "") return val;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
formatOaContent(item) {
|
||||
if (!item) return "";
|
||||
const first = this.pickByKeys(item, ["first", "title", "name"]);
|
||||
const remark = this.pickByKeys(item, ["remark", "content"]);
|
||||
const keywords = Array.isArray(item.keywords)
|
||||
? item.keywords.map((k) => this.pickByKeys(k, ["name", "value", "label"])).filter(Boolean).join(" / ")
|
||||
: this.safeText(item.keywordsText || "");
|
||||
return [first, keywords, remark].filter(Boolean).join("\n");
|
||||
},
|
||||
formatMpContent(item) {
|
||||
if (!item) return "";
|
||||
const keywordsText = this.safeText(item.keywordsText);
|
||||
if (keywordsText) return keywordsText;
|
||||
if (Array.isArray(item.keywords)) {
|
||||
return item.keywords.map((k) => this.pickByKeys(k, ["name", "value", "label"])).filter(Boolean).join(" / ");
|
||||
}
|
||||
return this.safeText(item.content);
|
||||
},
|
||||
async loadMemberPage() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const res = await API_Setting.getMessageTemplateAggregatePage(this.searchForm);
|
||||
if (!(res && res.success && res.result)) {
|
||||
this.memberRows = [];
|
||||
this.total = 0;
|
||||
this.$Message.error((res && res.message) || "加载消息模板失败");
|
||||
return;
|
||||
}
|
||||
const records = res.result.records || [];
|
||||
this.total = res.result.total || 0;
|
||||
this.memberRows = records.map((agg, idx) => {
|
||||
const item = { ...(agg.notice || {}) };
|
||||
const oa = agg.wechatOa;
|
||||
const mp = agg.wechatMp;
|
||||
return {
|
||||
...item,
|
||||
emailStatus: item.emailStatus || "CLOSE",
|
||||
emailContent: item.emailContent || "",
|
||||
index: (this.searchForm.pageNumber - 1) * this.searchForm.pageSize + idx + 1,
|
||||
oaId: oa ? oa.id : "",
|
||||
oaName: oa ? this.pickByKeys(oa, ["name", "templateName", "title"]) : "",
|
||||
oaCode: oa ? this.pickByKeys(oa, ["code", "templateId"]) : "",
|
||||
oaStatus: oa ? this.wechatEnableToOpenClose(oa.enable) : "",
|
||||
oaRaw: oa || null,
|
||||
oaContent: this.formatOaContent(oa),
|
||||
mpId: mp ? mp.id : "",
|
||||
mpName: mp ? this.pickByKeys(mp, ["name", "templateName", "title"]) : "",
|
||||
mpTemplateId: mp ? this.pickByKeys(mp, ["templateId"]) : "",
|
||||
mpCode: mp ? this.pickByKeys(mp, ["code"]) : "",
|
||||
mpStatus: mp ? this.wechatEnableToOpenClose(mp.enable) : "",
|
||||
mpRaw: mp || null,
|
||||
mpContent: this.formatMpContent(mp),
|
||||
};
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
reloadCurrent() {
|
||||
if (this.activeTab === "member") this.loadMemberPage();
|
||||
},
|
||||
async syncOaTemplates() {
|
||||
this.syncOaLoading = true;
|
||||
try {
|
||||
const res = await API_Setting.wechatMessageSync();
|
||||
if (res && res.success) {
|
||||
await this.loadMemberPage();
|
||||
this.$Message.success("服务号模板同步成功");
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || "服务号模板同步失败");
|
||||
}
|
||||
} finally {
|
||||
this.syncOaLoading = false;
|
||||
}
|
||||
},
|
||||
async syncMpTemplates() {
|
||||
this.syncMpLoading = true;
|
||||
try {
|
||||
const res = await API_Setting.wechatMPMessageSync();
|
||||
if (res && res.success) {
|
||||
await this.loadMemberPage();
|
||||
this.$Message.success("小程序模板同步成功");
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || "小程序模板同步失败");
|
||||
}
|
||||
} finally {
|
||||
this.syncMpLoading = false;
|
||||
}
|
||||
},
|
||||
changePage(v) {
|
||||
this.searchForm.pageNumber = v;
|
||||
this.loadMemberPage();
|
||||
},
|
||||
changePageSize(v) {
|
||||
this.searchForm.pageNumber = 1;
|
||||
this.searchForm.pageSize = v;
|
||||
this.loadMemberPage();
|
||||
},
|
||||
async openNoticeModal(row) {
|
||||
this.currentRow = { ...row };
|
||||
this.noticeDetailText = row.noticeContent || "";
|
||||
this.noticeModalVisible = true;
|
||||
const res = await API_Setting.getNoticeMessageDetail(row.id);
|
||||
if (res && res.success && res.result) {
|
||||
const d = res.result;
|
||||
this.noticeDetailText = [d.noticeTitle, d.noticeContent].filter(Boolean).join("\n");
|
||||
}
|
||||
},
|
||||
openOaModal(row) {
|
||||
this.currentRow = { ...row };
|
||||
this.oaModalVisible = true;
|
||||
},
|
||||
openMpModal(row) {
|
||||
this.currentRow = { ...row };
|
||||
this.mpModalVisible = true;
|
||||
},
|
||||
async openEmailModal(row) {
|
||||
this.currentRow = { ...row };
|
||||
const fallback = row.emailContent || row.noticeContent || "";
|
||||
this.emailDetailText = row.noticeTitle ? [row.noticeTitle, fallback].filter(Boolean).join("\n") : fallback;
|
||||
this.emailModalVisible = true;
|
||||
const res = await API_Setting.getNoticeMessageDetail(row.id);
|
||||
if (res && res.success && res.result) {
|
||||
const d = res.result;
|
||||
if (d.emailContent) {
|
||||
this.emailDetailText = d.emailContent;
|
||||
} else {
|
||||
this.emailDetailText = [d.noticeTitle, d.noticeContent].filter(Boolean).join("\n");
|
||||
}
|
||||
}
|
||||
},
|
||||
updateRowStatus(row, key, val) {
|
||||
const idx = this.memberRows.findIndex((x) => x.id === row.id);
|
||||
if (idx >= 0) this.$set(this.memberRows[idx], key, val);
|
||||
if (this.currentRow && this.currentRow.id === row.id) this.$set(this.currentRow, key, val);
|
||||
},
|
||||
async toggleNoticeChannel(v, row) {
|
||||
const old = row.noticeStatus;
|
||||
this.updateRowStatus(row, "noticeStatus", this.toOpenClose(v));
|
||||
const res = await API_Setting.updateMessageStatus(row.id, this.toOpenClose(v));
|
||||
if (!(res && res.success)) {
|
||||
this.updateRowStatus(row, "noticeStatus", old);
|
||||
this.$Message.error((res && res.message) || "站内信开关更新失败");
|
||||
} else {
|
||||
this.$Message.success("站内信开关更新成功");
|
||||
}
|
||||
},
|
||||
async toggleOaChannel(v, row) {
|
||||
if (!row.oaId) return;
|
||||
const old = row.oaStatus;
|
||||
this.updateRowStatus(row, "oaStatus", this.toOpenClose(v));
|
||||
// #region agent log: avoid sending full entity (it may include updateTime=null and cause backend bind errors)
|
||||
const params = { id: row.oaId, enable: v };
|
||||
// #region agent log: toggleOaChannel request
|
||||
fetch('http://127.0.0.1:7540/ingest/70859f24-04fe-4b64-8e32-c394f0219fba', {method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'ade6ce'},body:JSON.stringify({sessionId:'ade6ce',location:'messageTemplate.vue:toggleOaChannel:beforeRequest',message:'OA toggle request payload',hypothesisId:'A',data:{oaId:row.oaId,oaRawId:row.oaRaw&&row.oaRaw.id,oaRawType:typeof (row.oaRaw&&row.oaRaw.id),paramsId:params&¶ms.id,paramsIdType:typeof (params&¶ms.id),paramsKeys:Object.keys(params||{}),enable:v},timestamp:Date.now()})}).catch(()=>{});
|
||||
// #endregion
|
||||
try {
|
||||
const res = await API_Setting.editWechatMessageTemplate(row.oaId, params);
|
||||
// #region agent log: toggleOaChannel response
|
||||
fetch('http://127.0.0.1:7540/ingest/70859f24-04fe-4b64-8e32-c394f0219fba', {method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'ade6ce'},body:JSON.stringify({sessionId:'ade6ce',location:'messageTemplate.vue:toggleOaChannel:afterRequest',message:'OA toggle response',hypothesisId:'C',data:{success:res&&res.success,resMessage:res&&res.message,resCode:res&&res.code},timestamp:Date.now()})}).catch(()=>{});
|
||||
// #endregion
|
||||
if (!(res && res.success)) {
|
||||
this.updateRowStatus(row, "oaStatus", old);
|
||||
this.$Message.error((res && res.message) || "服务号开关更新失败");
|
||||
} else {
|
||||
this.$Message.success("服务号开关更新成功");
|
||||
}
|
||||
} catch (e) {
|
||||
// #region agent log: toggleOaChannel thrown error
|
||||
fetch('http://127.0.0.1:7540/ingest/70859f24-04fe-4b64-8e32-c394f0219fba', {method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'ade6ce'},body:JSON.stringify({sessionId:'ade6ce',location:'messageTemplate.vue:toggleOaChannel:catch',message:'OA toggle thrown exception',hypothesisId:'B',data:{errorMessage:(e&&e.message)||'',error:(e&&String(e))||''},timestamp:Date.now()})}).catch(()=>{});
|
||||
// #endregion
|
||||
this.updateRowStatus(row, "oaStatus", old);
|
||||
this.$Message.error((e && e.message) || "服务号开关更新失败");
|
||||
}
|
||||
},
|
||||
async toggleMpChannel(v, row) {
|
||||
if (!row.mpId) return;
|
||||
const old = row.mpStatus;
|
||||
this.updateRowStatus(row, "mpStatus", this.toOpenClose(v));
|
||||
const params = { id: row.mpId, enable: v };
|
||||
const res = await API_Setting.editWechatMPMessageTemplate(row.mpId, params);
|
||||
if (!(res && res.success)) {
|
||||
this.updateRowStatus(row, "mpStatus", old);
|
||||
this.$Message.error((res && res.message) || "小程序开关更新失败");
|
||||
} else {
|
||||
this.$Message.success("小程序开关更新成功");
|
||||
}
|
||||
},
|
||||
async toggleEmailChannel(v, row) {
|
||||
const old = row.emailStatus || "CLOSE";
|
||||
this.updateRowStatus(row, "emailStatus", this.toOpenClose(v));
|
||||
const res = await API_Setting.updateNoticeMessageEmailStatus(row.id, this.toOpenClose(v));
|
||||
if (!(res && res.success)) {
|
||||
this.updateRowStatus(row, "emailStatus", old);
|
||||
this.$Message.error((res && res.message) || "邮箱开关更新失败");
|
||||
} else {
|
||||
this.$Message.success("邮箱开关更新成功");
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadMemberPage();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user