From 4e33ab19b645fdb83dd2c1a220d4f1617f4cc1a2 Mon Sep 17 00:00:00 2001 From: "pikachu1995@126.com" Date: Mon, 2 Nov 2026 15:49:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(manager):=20=E7=9F=AD=E4=BF=A1=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E9=85=8D=E7=BD=AE=E5=BC=B9=E7=AA=97=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E5=91=BD=E5=90=8D=E7=BA=A6=E6=9D=9F=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/sys/message/messageTemplate.vue | 926 ++++++++++++------ 1 file changed, 623 insertions(+), 303 deletions(-) diff --git a/manager/src/views/sys/message/messageTemplate.vue b/manager/src/views/sys/message/messageTemplate.vue index 0ba020517..092ab08ed 100644 --- a/manager/src/views/sys/message/messageTemplate.vue +++ b/manager/src/views/sys/message/messageTemplate.vue @@ -2,217 +2,276 @@ @@ -222,40 +281,105 @@ import { onMounted, ref } from "vue"; import { ElMessage } from "element-plus"; import * as API_Setting from "@/api/setting.js"; +import { getSetting, setSetting } from "@/api/index"; defineOptions({ name: "messageTemplate" }); const loading = ref(false); -const activeTab = ref("member"); +const saving = ref(false); +const activeTab = ref("MEMBER"); const syncOaLoading = ref(false); const syncMpLoading = ref(false); const total = ref(0); + const searchForm = ref({ pageNumber: 1, pageSize: 20, }); -const memberRows = ref([]); -const currentRow = ref({}); -const noticeDetailText = ref(""); + +const tableRows = ref([]); + +// 弹窗状态与表单对象 const noticeModalVisible = ref(false); +const formNotice = ref({ id: "", sceneName: "", title: "", content: "", status: "CLOSE" }); + +const smsModalVisible = ref(false); +const formSms = ref({ id: "", sceneName: "", smsTemplateCode: "", content: "", status: "CLOSE" }); + const oaModalVisible = ref(false); +const formOa = ref({ id: "", sceneName: "", code: "", content: "", enable: false }); + const mpModalVisible = ref(false); -const emailModalVisible = ref(false); -const emailDetailText = ref(""); +const formMp = ref({ id: "", sceneName: "", code: "", content: "", enable: false }); + +// 公众号通知设置(SettingEnum.WECHAT_OFFICIAL_ACCOUNT_SETTING) +const OA_SETTING_KEY = "WECHAT_OFFICIAL_ACCOUNT_SETTING"; +const oaSettingLoading = ref(false); +const oaSettingSaving = ref(false); +const emptyOaSetting = () => ({ + enabled: false, + accountName: "", + articleUrl: "", + serverToken: "", + encodingAesKey: "", + subscribeReply: "", +}); +const oaSettingForm = ref(emptyOaSetting()); + +async function loadOaSetting() { + oaSettingLoading.value = true; + try { + const res = await getSetting(OA_SETTING_KEY); + const data = (res && res.success && res.result) || {}; + oaSettingForm.value = Object.assign(emptyOaSetting(), data, { enabled: data.enabled === true }); + } finally { + oaSettingLoading.value = false; + } +} + +function previewOaArticle() { + if (oaSettingForm.value.articleUrl) window.open(oaSettingForm.value.articleUrl, "_blank"); +} + +async function saveOaSetting() { + const form = oaSettingForm.value; + const url = (form.articleUrl || "").trim(); + if (form.enabled && !url) { + ElMessage.warning("开启公众号通知时必须配置引导关注文章链接"); + return; + } + if (url && !url.startsWith("https://mp.weixin.qq.com/")) { + ElMessage.warning("文章链接必须是 https://mp.weixin.qq.com/ 开头的公众号文章地址"); + return; + } + const aesKey = (form.encodingAesKey || "").trim(); + if (aesKey && aesKey.length !== 43) { + ElMessage.warning("EncodingAESKey 长度必须为 43 位"); + return; + } + oaSettingSaving.value = true; + try { + const res = await setSetting(OA_SETTING_KEY, { ...form, articleUrl: url, encodingAesKey: aesKey }); + if (res && res.success) { + ElMessage.success("保存成功"); + } + } finally { + oaSettingSaving.value = false; + } +} function isOpen(status) { return String(status || "").toUpperCase() === "OPEN"; } + function wechatEnableToOpenClose(enable) { - if (enable === true || enable === "true" || enable === 1) return "OPEN"; - return "CLOSE"; -} -function toOpenClose(flag) { - return flag ? "OPEN" : "CLOSE"; + return enable === true || enable === "true" || enable === 1 ? "OPEN" : "CLOSE"; } + function safeText(v) { return v === undefined || v === null ? "" : String(v); } + function pickByKeys(obj, keys) { for (let i = 0; i < keys.length; i++) { const val = obj ? obj[keys[i]] : ""; @@ -263,6 +387,7 @@ function pickByKeys(obj, keys) { } return ""; } + function formatOaContent(item) { if (!item) return ""; const first = pickByKeys(item, ["first", "title", "name"]); @@ -272,8 +397,56 @@ function formatOaContent(item) { : safeText(item.keywordsText || ""); return [first, keywords, remark].filter(Boolean).join("\n"); } + +const MP_KEYWORD_LABELS = { + GOODS_INFO: "物品名称", + ORDER_SN: "订单编号", + AFTER_SALE_SN: "售后单号", + AUDIT_RESULT: "审核结果", + CHANGE_POINT: "变动数量", + CURRENT_POINT: "当前积分", + POINT_TIME: "积分变更时间", + CHANGE_AMOUNT: "变动金额", + WALLET_BALANCE: "账户余额", + WALLET_TIME: "时间", + CHANGE_REASON: "变动原因", + WITHDRAW_AMOUNT: "提现金额", + WITHDRAW_RESULT: "提现结果", + APPLY_TIME: "申请时间", + WARM_REMIND: "温馨提醒", + LOGISTICS_TIME: "发货时间", + COMPLETE_TIME: "完成时间", + PINTUAN_TIME: "成团时间", + REMARK: "温馨提示", + LOGISTICS_NAME: "快递公司", + LOGISTICS_NO: "快递单号", + SHOP_NAME: "商家名称", + MEMBER_NAME: "买家昵称", + PRICE: "订单金额", + PAYMENT_TIME: "支付时间", +}; + function formatMpContent(item) { if (!item) return ""; + try { + const keys = typeof item.keywords === "string" ? JSON.parse(item.keywords) : item.keywords; + const texts = typeof item.keywordsText === "string" ? JSON.parse(item.keywordsText) : item.keywordsText; + if (Array.isArray(keys) && Array.isArray(texts) && keys.length === texts.length && keys.length > 0) { + const isGoodsNameLabel = item.sceneCode !== "ORDER_DELIVER"; + return keys + .map((k, idx) => { + let label = MP_KEYWORD_LABELS[k] || k; + if (isGoodsNameLabel && k === "GOODS_INFO") label = "商品名称"; + if (isGoodsNameLabel && k === "ORDER_SN") label = "订单号"; + const field = texts[idx] || ""; + const placeholder = field.includes(".DATA") ? `{{${field}}}` : `{{${field}.DATA}}`; + return `${label}:${placeholder}`; + }) + .join("\n"); + } + } catch (e) { + // fallback below + } const keywordsText = safeText(item.keywordsText); if (keywordsText) return keywordsText; if (Array.isArray(item.keywords)) { @@ -281,55 +454,234 @@ function formatMpContent(item) { } return safeText(item.content); } -async function loadMemberPage() { + +// 加载分页 +async function loadPage() { loading.value = true; try { - const res = await API_Setting.getMessageTemplateAggregatePage(searchForm.value); + const params = { + ...searchForm.value, + type: activeTab.value, + }; + const res = await API_Setting.getMessageTemplateAggregatePage(params); if (!(res && res.success && res.result)) { - memberRows.value = []; + tableRows.value = []; total.value = 0; ElMessage.error((res && res.message) || "加载消息模板失败"); return; } const records = res.result.records || []; total.value = res.result.total || 0; - memberRows.value = records.map((agg, idx) => { + tableRows.value = 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 || "", + smsStatus: item.smsStatus || "CLOSE", + smsTemplateCode: item.smsTemplateCode || "", + smsContent: item.smsContent || "", index: (searchForm.value.pageNumber - 1) * searchForm.value.pageSize + idx + 1, oaId: oa ? oa.id : "", oaName: oa ? pickByKeys(oa, ["name", "templateName", "title"]) : "", oaCode: oa ? pickByKeys(oa, ["code", "templateId"]) : "", - oaStatus: oa ? wechatEnableToOpenClose(oa.enable) : "", + oaStatus: oa ? wechatEnableToOpenClose(oa.enable) : "CLOSE", oaRaw: oa || null, oaContent: formatOaContent(oa), mpId: mp ? mp.id : "", mpName: mp ? pickByKeys(mp, ["name", "templateName", "title"]) : "", - mpTemplateId: mp ? pickByKeys(mp, ["templateId"]) : "", - mpCode: mp ? pickByKeys(mp, ["code"]) : "", - mpStatus: mp ? wechatEnableToOpenClose(mp.enable) : "", + mpCode: mp ? pickByKeys(mp, ["code", "templateId"]) : "", + mpStatus: mp ? wechatEnableToOpenClose(mp.enable) : "CLOSE", mpRaw: mp || null, mpContent: formatMpContent(mp), + _switchLoading: "", }; }); } finally { loading.value = false; } } -function reloadCurrent() { - if (activeTab.value === "member") loadMemberPage(); + +function handleTabChange() { + searchForm.value.pageNumber = 1; + if (activeTab.value === "SETTING") { + loadOaSetting(); + return; + } + loadPage(); } + +function changePageSize() { + searchForm.value.pageNumber = 1; + loadPage(); +} + +// 行内 Switch 快速切换 +async function handleSwitchChange(row, channel, value) { + const targetStatus = value ? "OPEN" : "CLOSE"; + row._switchLoading = channel; + try { + const res = await API_Setting.updateMessageChannelSwitch({ + id: row.id, + channel, + status: targetStatus, + }); + if (res && res.success) { + if (channel === "NOTICE") row.noticeStatus = targetStatus; + else if (channel === "SMS") row.smsStatus = targetStatus; + else if (channel === "WECHAT_OA") row.oaStatus = targetStatus; + else if (channel === "WECHAT_MP") row.mpStatus = targetStatus; + ElMessage.success("开关状态已更新"); + } else { + ElMessage.error((res && res.message) || "更新开关失败"); + } + } catch (e) { + ElMessage.error("网络异常,更新开关失败"); + } finally { + row._switchLoading = ""; + } +} + +// 1. 打开站内信弹窗 +function openNoticeModal(row) { + formNotice.value = { + id: row.id, + sceneName: row.noticeNode, + title: row.noticeTitle || "", + content: row.noticeContent || "", + status: isOpen(row.noticeStatus) ? "OPEN" : "CLOSE", + }; + noticeModalVisible.value = true; +} + +async function saveNoticeModal() { + if (!formNotice.value.content) { + ElMessage.warning("请输入模板内容"); + return; + } + saving.value = true; + try { + const res = await API_Setting.updateNoticeMessageTemplate({ + id: formNotice.value.id, + title: formNotice.value.title, + content: formNotice.value.content, + status: formNotice.value.status, + }); + if (res && res.success) { + ElMessage.success("站内信配置已保存"); + noticeModalVisible.value = false; + loadPage(); + } else { + ElMessage.error((res && res.message) || "保存失败"); + } + } finally { + saving.value = false; + } +} + +// 2. 打开短信弹窗 +function openSmsModal(row) { + formSms.value = { + id: row.id, + sceneName: row.noticeNode, + smsTemplateCode: row.smsTemplateCode || "", + content: row.smsContent || "", + status: isOpen(row.smsStatus) ? "OPEN" : "CLOSE", + }; + smsModalVisible.value = true; +} + +async function saveSmsModal() { + saving.value = true; + try { + const res = await API_Setting.updateSmsMessageTemplateConfig({ + id: formSms.value.id, + smsTemplateCode: formSms.value.smsTemplateCode, + status: formSms.value.status, + }); + if (res && res.success) { + ElMessage.success("短信模板配置已保存"); + smsModalVisible.value = false; + loadPage(); + } else { + ElMessage.error((res && res.message) || "保存失败"); + } + } finally { + saving.value = false; + } +} + +// 3. 打开微信公众号弹窗 +function openOaModal(row) { + formOa.value = { + id: row.id, + sceneName: row.noticeNode, + code: row.oaCode || "", + content: row.oaContent || "", + enable: isOpen(row.oaStatus), + }; + oaModalVisible.value = true; +} + +async function saveOaModal() { + saving.value = true; + try { + const res = await API_Setting.updateWechatOaMessageTemplate({ + id: formOa.value.id, + code: formOa.value.code, + enable: formOa.value.enable, + }); + if (res && res.success) { + ElMessage.success("微信公众号模板配置已保存"); + oaModalVisible.value = false; + loadPage(); + } else { + ElMessage.error((res && res.message) || "保存失败"); + } + } finally { + saving.value = false; + } +} + +// 4. 打开微信小程序弹窗 +function openMpModal(row) { + formMp.value = { + id: row.id, + sceneName: row.noticeNode, + code: row.mpCode || "", + content: row.mpContent || "", + enable: isOpen(row.mpStatus), + }; + mpModalVisible.value = true; +} + +async function saveMpModal() { + saving.value = true; + try { + const res = await API_Setting.updateWechatMpMessageTemplate({ + id: formMp.value.id, + code: formMp.value.code, + enable: formMp.value.enable, + }); + if (res && res.success) { + ElMessage.success("微信小程序订阅消息配置已保存"); + mpModalVisible.value = false; + loadPage(); + } else { + ElMessage.error((res && res.message) || "保存失败"); + } + } finally { + saving.value = false; + } +} + +// 同步微信服务号模板 async function syncOaTemplates() { syncOaLoading.value = true; try { const res = await API_Setting.wechatMessageSync(); if (res && res.success) { - await loadMemberPage(); + await loadPage(); ElMessage.success("服务号模板同步成功"); } else { ElMessage.error((res && res.message) || "服务号模板同步失败"); @@ -338,12 +690,14 @@ async function syncOaTemplates() { syncOaLoading.value = false; } } + +// 同步微信小程序模板 async function syncMpTemplates() { syncMpLoading.value = true; try { const res = await API_Setting.wechatMPMessageSync(); if (res && res.success) { - await loadMemberPage(); + await loadPage(); ElMessage.success("小程序模板同步成功"); } else { ElMessage.error((res && res.message) || "小程序模板同步失败"); @@ -352,130 +706,96 @@ async function syncMpTemplates() { syncMpLoading.value = false; } } -function changePageSize() { - searchForm.value.pageNumber = 1; - loadMemberPage(); -} -async function openNoticeModal(row) { - currentRow.value = { ...row }; - noticeDetailText.value = row.noticeContent || ""; - noticeModalVisible.value = true; - const res = await API_Setting.getNoticeMessageDetail(row.id); - if (res && res.success && res.result) { - const d = res.result; - noticeDetailText.value = [d.noticeTitle, d.noticeContent].filter(Boolean).join("\n"); - } -} -function openOaModal(row) { - currentRow.value = { ...row }; - oaModalVisible.value = true; -} -function openMpModal(row) { - currentRow.value = { ...row }; - mpModalVisible.value = true; -} -async function openEmailModal(row) { - currentRow.value = { ...row }; - const fallback = row.emailContent || row.noticeContent || ""; - emailDetailText.value = row.noticeTitle ? [row.noticeTitle, fallback].filter(Boolean).join("\n") : fallback; - emailModalVisible.value = true; - const res = await API_Setting.getNoticeMessageDetail(row.id); - if (res && res.success && res.result) { - const d = res.result; - if (d.emailContent) { - emailDetailText.value = d.emailContent; - } else { - emailDetailText.value = [d.noticeTitle, d.noticeContent].filter(Boolean).join("\n"); - } - } -} -function updateRowStatus(row, key, val) { - const idx = memberRows.value.findIndex((x) => x.id === row.id); - if (idx >= 0) memberRows.value[idx][key] = val; - if (currentRow.value && currentRow.value.id === row.id) currentRow.value[key] = val; -} -async function toggleNoticeChannel(v, row) { - const old = row.noticeStatus; - updateRowStatus(row, "noticeStatus", toOpenClose(v)); - const res = await API_Setting.updateMessageStatus(row.id, toOpenClose(v)); - if (!(res && res.success)) { - updateRowStatus(row, "noticeStatus", old); - ElMessage.error((res && res.message) || "站内信开关更新失败"); - } else { - ElMessage.success("站内信开关更新成功"); - } -} -async function toggleOaChannel(v, row) { - if (!row.oaId) return; - const old = row.oaStatus; - updateRowStatus(row, "oaStatus", toOpenClose(v)); - const params = { id: row.oaId, enable: v }; - try { - const res = await API_Setting.editWechatMessageTemplate(row.oaId, params); - if (!(res && res.success)) { - updateRowStatus(row, "oaStatus", old); - ElMessage.error((res && res.message) || "服务号开关更新失败"); - } else { - ElMessage.success("服务号开关更新成功"); - } - } catch (e) { - updateRowStatus(row, "oaStatus", old); - ElMessage.error((e && e.message) || "服务号开关更新失败"); - } -} -async function toggleMpChannel(v, row) { - if (!row.mpId) return; - const old = row.mpStatus; - updateRowStatus(row, "mpStatus", toOpenClose(v)); - const params = { id: row.mpId, enable: v }; - const res = await API_Setting.editWechatMPMessageTemplate(row.mpId, params); - if (!(res && res.success)) { - updateRowStatus(row, "mpStatus", old); - ElMessage.error((res && res.message) || "小程序开关更新失败"); - } else { - ElMessage.success("小程序开关更新成功"); - } -} -async function toggleEmailChannel(v, row) { - const old = row.emailStatus || "CLOSE"; - updateRowStatus(row, "emailStatus", toOpenClose(v)); - const res = await API_Setting.updateNoticeMessageEmailStatus(row.id, toOpenClose(v)); - if (!(res && res.success)) { - updateRowStatus(row, "emailStatus", old); - ElMessage.error((res && res.message) || "邮箱开关更新失败"); - } else { - ElMessage.success("邮箱开关更新成功"); - } -} onMounted(() => { - loadMemberPage(); + loadPage(); }); -