From d765628bb366dd24cff14462a3669b4d577ff47e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=8B=E4=BA=8E=E5=88=9D=E8=A7=81?= <752204717@qq.com> Date: Fri, 15 May 2026 10:54:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=B9=E9=87=8F=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/langs/en.js | 68 +- src/components/common/langs/zh.js | 66 ++ src/components/page/autoPromotion.vue | 1476 +++++++++++++++++++++---- src/components/page/mailboxMould.vue | 220 +++- 4 files changed, 1590 insertions(+), 240 deletions(-) diff --git a/src/components/common/langs/en.js b/src/components/common/langs/en.js index 7687a77..94acad6 100644 --- a/src/components/common/langs/en.js +++ b/src/components/common/langs/en.js @@ -510,7 +510,7 @@ const en = { languagePlaceholder: 'Language', searchBtn: 'Search', createTemplate: 'Create Template', -colTitle: 'Template title', + colTitle: 'Template title', colSubject: 'Email subject', colScene: 'Scene', colLanguage: 'Language', @@ -526,6 +526,23 @@ colTitle: 'Template title', deleteFail: 'Delete failed', previewTitle: 'Template preview', previewClose: 'Close', + batchImportBtn: 'Batch import', + batchImportTitle: 'Batch import templates (JSON)', + batchImportHint: + 'Paste a JSON array. Each item is saved via the same API as the editor (omit template_id to create; include template_id to update). Fields: title, subject, scene, language (or lang), version, body_html (or body), variables_json (or variables), is_active.', + batchImportCommonTip: 'Journal ID is set in the field below; when non-empty it overrides journal_id / journalId on every row.', + batchImportJournalId: 'Journal ID', + batchImportJournalPlaceholder: 'Match list filter or type manually', + batchImportRun: 'Run import', + batchImportBadJson: 'Invalid JSON', + batchImportEmpty: 'Array must contain at least one object', + batchImportMissingJournal: 'Row {index}: missing journal_id (use the input above or put journal_id in JSON)', + batchImportMissingField: 'Row {index}: missing {field}', + batchImportRowFail: 'Row {index} failed: {msg}', + batchImportRowNetwork: 'Row {index}: request error', + batchImportDone: 'Done: {ok} succeeded, {fail} failed', + batchImportErrorsTitle: 'Errors (first 8)', + batchImportSaveFail: 'Save failed', }, mailboxStyle: { title: 'Email Styles', @@ -1244,6 +1261,55 @@ colTitle: 'Template title', onlySaveConfig: 'Save configuration only', enableNowNextDay: 'Enable auto promotion now (starts next day)', factoryCreateBtn: 'Create automated promotion task', + factoryBatchImportBtn: 'Batch import (JSON)', + factoryBatchImportTitle: 'Batch create tasks (JSON)', + factoryBatchImportHintShort: 'Submit a JSON array; non-empty fields merge into each row. You can still edit JSON.', + factoryBatchImportHint: + 'Paste a JSON array… Pick journal (getAllJournal) or type ID; template/style/fetch_ids below override JSON when non-empty. Load promotion fields to tick IDs or type comma-separated fetch_ids; load accounts for j_email_id. expert_type "5" needs partitions/countries. Shorthand: zones, countries, email_id_list.', + factoryBatchImportCommonTip: 'Journal from cover or ID field; non-empty template ID, style ID, or fetch_ids override JSON on every row.', + factoryBatchImportJournalId: 'Journal ID', + factoryBatchImportJournalPick: 'Journal', + factoryBatchImportJournalEmpty: 'No journals returned. Check api/Journal/getAllJournal.', + factoryBatchImportJournalManualPlaceholder: 'Filled when you pick a cover, or type manually', + factoryBatchImportTemplateId: 'Template ID', + factoryBatchImportStyleId: 'Style ID', + factoryBatchImportJournalPlaceholder: 'Merged into payload', + factoryBatchImportTemplatePlaceholder: 'Merged into payload', + factoryBatchImportStylePlaceholder: 'Merged into payload', + factoryBatchImportFetchIdsLabel: 'Promotion fields (fetch_ids)', + factoryBatchImportLoadFields: 'Load available fields', + factoryBatchImportFetchTip: 'Uses current journal: pick journal, then load. Search by name or ID (multiple tokens: space or comma). “Select all” selects the filtered list when search is set, otherwise all fields. Checkboxes sync with the comma text; when non-empty, overrides fetch_ids on every row.', + factoryBatchImportFetchIdsManual: 'Merged IDs (comma-separated, editable)', + factoryBatchImportFetchIdsPlaceholder: 'e.g. 1,2,3 or use checkboxes', + factoryBatchImportNeedJournalForFields: 'Select or enter journal ID first', + factoryBatchImportLoadAccounts: 'Load accounts for journal', + factoryBatchImportAccountsApiTip: 'POST api/email_client/getAccounts with journal_id', + factoryBatchImportColEmailId: 'j_email_id', + factoryBatchImportColAddress: 'Sender address', + factoryBatchImportColQuota: 'Remaining / daily limit', + factoryBatchImportCopyEmailIds: 'Copy email_ids (comma)', + factoryBatchImportNeedJournalForAccounts: 'Enter journal ID first', + factoryBatchImportNoAccounts: 'No mailbox accounts for this journal', + factoryBatchImportAccountsFail: 'Failed to load accounts', + factoryBatchImportCopyEmailIdsEmpty: 'Load the account list first', + factoryBatchImportIdsCopied: 'Copied j_email_id list to clipboard', + factoryBatchImportCopyFail: 'Copy failed; select and copy manually', + factoryBatchImportSyncToJson: 'Apply top form to JSON', + factoryBatchImportSyncFromJson: 'Load first row into form', + factoryBatchImportSyncIncludeEmails: 'When applying, set each row email_ids from loaded accounts', + factoryBatchImportSyncTip: 'You can still edit JSON manually; non-empty top fields are merged again on submit.', + factoryBatchImportJsonFromUiOk: 'JSON updated from the form', + factoryBatchImportUiFromJsonOk: 'Form updated from the first JSON row', + factoryBatchImportRun: 'Run batch create', + factoryBatchImportBadJson: 'Invalid JSON; check brackets and quotes', + factoryBatchImportEmpty: 'Array must contain at least one object', + factoryBatchImportMissing: 'Row {index}: missing field {field}', + factoryBatchImportNeedFetch: 'Row {index}: expert database requires fetch_ids', + factoryBatchImportNeedZone: 'Row {index}: expert database requires target_partitions or target_country_ids', + factoryBatchImportRowFail: 'Row {index} failed: {msg}', + factoryBatchImportRowNetwork: 'Row {index}: request error', + factoryBatchImportDone: 'Done: {ok} succeeded, {fail} failed', + factoryBatchImportErrorsTitle: 'Errors (first 8)', factoryDialogTitle: 'Create task', factoryJournal: 'Journal', factoryJournalPlaceholder: 'Select a journal', diff --git a/src/components/common/langs/zh.js b/src/components/common/langs/zh.js index e8b5e56..5c98152 100644 --- a/src/components/common/langs/zh.js +++ b/src/components/common/langs/zh.js @@ -515,6 +515,23 @@ const zh = { deleteFail: '删除失败', previewTitle: '模板预览', previewClose: '关闭', + batchImportBtn: '批量导入', + batchImportTitle: '批量导入邮件模板(JSON)', + batchImportHint: + '粘贴 JSON 数组,每条对应一次保存接口(新建不传 template_id;更新可带 template_id)。字段与编辑页一致:title、subject、scene、language(可用 lang)、version、body_html(可用 body)、variables_json(可用 variables)、is_active。', + batchImportCommonTip: '期刊 ID 在下方单独填写;若填写非空,会覆盖每条 JSON 中的 journal_id / journalId。', + batchImportJournalId: '期刊 ID', + batchImportJournalPlaceholder: '可与列表筛选一致,或手填', + batchImportRun: '开始导入', + batchImportBadJson: 'JSON 解析失败', + batchImportEmpty: '请至少包含一条对象', + batchImportMissingJournal: '第 {index} 条:缺少期刊 ID(请填写上方输入框或在 JSON 中提供 journal_id)', + batchImportMissingField: '第 {index} 条:缺少字段 {field}', + batchImportRowFail: '第 {index} 条保存失败:{msg}', + batchImportRowNetwork: '第 {index} 条请求异常', + batchImportDone: '完成:成功 {ok},失败 {fail}', + batchImportErrorsTitle: '失败明细(最多 8 条)', + batchImportSaveFail: '保存失败', }, mailboxStyle: { title: '邮件风格', @@ -1225,6 +1242,55 @@ const zh = { onlySaveConfig: '仅保存配置', enableNowNextDay: '立即激活自动推广(次日开始自动推广)', factoryCreateBtn: '创建自动化推广任务', + factoryBatchImportBtn: '临时批量导入', + factoryBatchImportTitle: '批量创建推广任务(JSON)', + factoryBatchImportHintShort: '数组提交;上方非空项会合并进每条请求,仍可直接改 JSON。', + factoryBatchImportHint: + '粘贴 JSON 数组…期刊用封面(getAllJournal)或手填 ID;模板/样式/推广领域 fetch_ids 在下方非空则覆盖 JSON。推广领域可「加载可选领域」勾选或手改逗号 ID;填期刊后可查邮箱 j_email_id(getAccounts)。expert_type 为 5 时须分区或国家。简写:zones、countries、email_id_list。', + factoryBatchImportCommonTip: '期刊以封面或下方 ID 为准;模板 ID、样式 ID、推广领域 fetch_ids 任一非空则覆盖每条 JSON 中对应字段后再请求接口。', + factoryBatchImportJournalId: '期刊 ID', + factoryBatchImportJournalPick: '选择期刊', + factoryBatchImportJournalEmpty: '未获取到期刊列表,请检查接口或稍后重试', + factoryBatchImportJournalManualPlaceholder: '点击上方封面自动填入,也可手改', + factoryBatchImportTemplateId: '模板 ID', + factoryBatchImportStyleId: '样式 ID', + factoryBatchImportJournalPlaceholder: '与 JSON 合并', + factoryBatchImportTemplatePlaceholder: '与 JSON 合并', + factoryBatchImportStylePlaceholder: '与 JSON 合并', + factoryBatchImportFetchIdsLabel: '推广领域 fetch_ids', + factoryBatchImportLoadFields: '加载可选领域', + factoryBatchImportFetchTip: '依赖当前期刊:先选期刊再加载。上方可搜索名称或 ID(多关键词用空格或逗号);有搜索时「全选」勾选当前筛选结果,无搜索时「全选」为全部。勾选与下方逗号文本同步,非空则覆盖每条 JSON 的 fetch_ids。', + factoryBatchImportFetchIdsManual: '合并用 ID(逗号分隔,可手改)', + factoryBatchImportFetchIdsPlaceholder: '例:1,2,3;或与勾选联动', + factoryBatchImportNeedJournalForFields: '请先选择或填写期刊 ID', + factoryBatchImportLoadAccounts: '查询该期刊邮箱', + factoryBatchImportAccountsApiTip: 'POST api/email_client/getAccounts,参数 journal_id', + factoryBatchImportColEmailId: 'j_email_id', + factoryBatchImportColAddress: '发件地址', + factoryBatchImportColQuota: '今日剩余 / 日上限', + factoryBatchImportCopyEmailIds: '复制 email_ids(逗号)', + factoryBatchImportNeedJournalForAccounts: '请先填写期刊 ID', + factoryBatchImportNoAccounts: '该期刊下暂无邮箱账号', + factoryBatchImportAccountsFail: '拉取邮箱列表失败', + factoryBatchImportCopyEmailIdsEmpty: '请先查询出账号列表', + factoryBatchImportIdsCopied: '已复制 j_email_id 列表到剪贴板', + factoryBatchImportCopyFail: '复制失败,请手动选中复制', + factoryBatchImportSyncToJson: '上方选项写入 JSON', + factoryBatchImportSyncFromJson: '首条 JSON 回显到上方', + factoryBatchImportSyncIncludeEmails: '写入时用当前邮箱列表覆盖每条 email_ids', + factoryBatchImportSyncTip: '写入后仍可单独改 JSON;提交时若上方输入框非空仍会再合并覆盖。', + factoryBatchImportJsonFromUiOk: '已根据上方选项更新 JSON', + factoryBatchImportUiFromJsonOk: '已用首条 JSON 更新上方表单', + factoryBatchImportRun: '开始批量创建', + factoryBatchImportBadJson: 'JSON 解析失败,请检查括号与引号', + factoryBatchImportEmpty: '请至少包含一条对象', + factoryBatchImportMissing: '第 {index} 条缺少字段:{field}', + factoryBatchImportNeedFetch: '第 {index} 条:专家库需填写 fetch_ids(推广领域)', + factoryBatchImportNeedZone: '第 {index} 条:专家库需至少填写分区或国家(target_partitions / target_country_ids)', + factoryBatchImportRowFail: '第 {index} 条创建失败:{msg}', + factoryBatchImportRowNetwork: '第 {index} 条请求异常', + factoryBatchImportDone: '完成:成功 {ok},失败 {fail}', + factoryBatchImportErrorsTitle: '失败明细(最多显示 8 条)', factoryDialogTitle: '创建任务', factoryJournal: '期刊', factoryJournalPlaceholder: '请选择期刊', diff --git a/src/components/page/autoPromotion.vue b/src/components/page/autoPromotion.vue index aebbe41..4ba7c57 100644 --- a/src/components/page/autoPromotion.vue +++ b/src/components/page/autoPromotion.vue @@ -1,20 +1,23 @@ @@ -1158,6 +1798,368 @@ export default { border-color: #409eff; } +/* 临时批量导入 JSON 对话框 */ +.factory-batch-import-dialog .el-dialog__body { + padding-top: 12px; +} + +.batch-import-layout { + display: flex; + align-items: flex-start; + gap: 16px; + min-height: 0; +} + +.batch-import-journal-rail { + flex: 0 0 74px; + width: 74px; + padding: 8px 8px 8px 0; + border-right: 1px solid #ebeef5; + align-self: stretch; + max-height: 72vh; + overflow: hidden; + display: flex; + flex-direction: column; + min-height: 0; +} + +.batch-import-journal-scroll { + flex: 1; + min-height: 0; + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +.batch-import-rail-title { + font-size: 11px; + font-weight: 600; + color: #606266; + margin-bottom: 6px; + text-align: center; + line-height: 1.25; + flex-shrink: 0; +} + +.batch-import-journal-rail .batch-import-journal-empty { + text-align: center; + font-size: 10px; + color: #909399; + padding: 4px 0 8px; + line-height: 1.3; +} + +.batch-import-journal-rail .batch-import-journal-grid--rail { + flex-direction: column; + flex-wrap: nowrap; + align-items: center; + gap: 8px; + margin-bottom: 0; + flex: none; + min-height: 0; + overflow: visible; + padding-right: 0; +} + +.batch-import-journal-rail .batch-import-journal-card { + width: 35px; +} + +.batch-import-journal-rail .batch-import-mini-cover-wrapper { + width: 35px; + height: 45px; +} + +.batch-import-journal-rail .batch-import-mini-badge { + width: 14px; + height: 14px; + font-size: 8px; + top: -3px; + right: -3px; +} + +.batch-import-journal-rail .batch-import-mini-abbr { + font-size: 9px; + margin-top: 4px; + max-width: 35px; +} + +.batch-import-journal-rail .batch-import-journal-card.is-active .batch-import-mini-cover { + transform: translateY(-1px); +} + +.batch-import-journal-id-row--rail { + flex-direction: column; + align-items: stretch; + gap: 6px; + padding-top: 4px; + border-top: 1px solid #ebeef5; +} + +.batch-import-journal-id-row--rail .batch-import-id-label { + font-size: 11px; +} + +.batch-import-journal-id-row--rail .batch-import-journal-id-input { + max-width: none; + width: 100%; + min-width: 0; +} + +.batch-import-main { + flex: 1; + min-width: 0; +} + +.batch-import-hint-compact { + font-size: 12px; + line-height: 1.45; + color: #909399; + margin: 0 0 12px; +} + +.batch-import-hint { + font-size: 12px; + line-height: 1.55; + color: #606266; + margin: 0 0 10px; + white-space: pre-wrap; +} + +.batch-import-common-tip { + font-size: 12px; + line-height: 1.5; + color: #909399; + margin: 0 0 10px; +} + +.batch-import-common-fields { + margin-bottom: 12px; +} + +.batch-import-journal-block { + margin-bottom: 14px; +} + +.batch-import-journal-panel { + background: #f8fafc; + border: 1px solid #ebf0f5; + border-radius: 8px; + padding: 12px; +} + +.batch-import-journal-empty { + font-size: 12px; + color: #909399; + padding: 8px 0; +} + +.batch-import-journal-grid { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-bottom: 12px; +} + +.batch-import-journal-card { + width: 70px; + text-align: center; + cursor: pointer; +} + +.batch-import-mini-cover-wrapper { + position: relative; + width: 70px; + height: 90px; +} + +.batch-import-mini-cover { + width: 100%; + height: 100%; + border-radius: 4px; + border: 2px solid transparent; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); + transition: 0.25s; +} + +.batch-import-journal-card.is-active .batch-import-mini-cover { + border-color: #409eff; + transform: translateY(-2px); +} + +.batch-import-mini-badge { + position: absolute; + top: -6px; + right: -6px; + background: #67c23a; + color: #fff; + width: 18px; + height: 18px; + border-radius: 50%; + font-size: 10px; + display: flex; + align-items: center; + justify-content: center; + z-index: 2; +} + +.batch-import-image-slot { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + background: #f5f7fa; + color: #c0c4cc; +} + +.batch-import-mini-abbr { + font-size: 11px; + margin-top: 6px; + color: #606266; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.batch-import-mini-abbr.is-active { + color: #409eff; + font-weight: 600; +} + +.batch-import-journal-id-row { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.batch-import-id-label { + font-size: 12px; + color: #606266; + font-weight: 600; + flex-shrink: 0; +} + +.batch-import-journal-id-input { + flex: 1; + min-width: 160px; + max-width: 360px; +} + +.batch-import-fetch-block { + margin-bottom: 12px; + padding: 10px 12px; + background: #fafafa; + border: 1px solid #eef0f3; + border-radius: 6px; +} + +.batch-import-field-row-head { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 6px; +} + +.batch-import-fetch-count { + font-size: 12px; + color: #909399; + flex: 1; + min-width: 80px; +} + +.batch-import-fetch-tip { + font-size: 12px; + color: #c27a00; + margin: 0 0 8px; + line-height: 1.45; +} + +.batch-import-field-toolbar { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 8px; +} + +.batch-import-field-search { + flex: 1; + min-width: 180px; + max-width: 100%; +} + +.batch-import-field-empty { + font-size: 12px; + color: #909399; + padding: 8px 4px; +} + +.batch-import-field-checkbox-wrap { + max-height: 160px; + overflow: auto; + margin-bottom: 8px; + padding: 8px; + background: #fff; + border: 1px solid #ebeef5; + border-radius: 4px; +} + +.batch-import-field-checkbox-wrap .el-checkbox { + display: block; + margin-right: 0; + margin-bottom: 6px; +} + +.batch-import-fetch-text-label { + margin-top: 4px; + margin-bottom: 4px; +} + +.factory-batch-import-dialog .batch-import-fetch-textarea >>> textarea { + font-family: Consolas, 'Courier New', monospace; + font-size: 12px; + line-height: 1.45; +} + +.batch-import-field-label { + font-size: 12px; + color: #606266; + margin-bottom: 4px; + font-weight: 600; +} + +.batch-import-accounts-table { + margin-bottom: 12px; +} + +.batch-import-json-toolbar { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 10px; +} + +.batch-import-sync-email-check { + margin-right: 0; +} + +.batch-import-json-toolbar-tip { + font-size: 12px; + color: #909399; + flex: 1; + min-width: 200px; + line-height: 1.45; +} + +.factory-batch-import-dialog .batch-import-textarea >>> textarea { + font-family: Consolas, 'Courier New', monospace; + font-size: 12px; + line-height: 1.45; +} + /* 响应式 */ @media (max-width: 1200px) { .cards-grid { diff --git a/src/components/page/mailboxMould.vue b/src/components/page/mailboxMould.vue index 6fa837f..51e68cf 100644 --- a/src/components/page/mailboxMould.vue +++ b/src/components/page/mailboxMould.vue @@ -50,6 +50,9 @@
+ {{ + $t('mailboxMould.batchImportBtn') + }} {{ $t('mailboxMould.createTemplate') }}
@@ -136,6 +139,36 @@ {{ $t('mailboxMould.previewClose') }} + + +

{{ $t('mailboxMould.batchImportHint') }}

+

{{ $t('mailboxMould.batchImportCommonTip') }}

+
+ {{ $t('mailboxMould.batchImportJournalId') }} + +
+ + + {{ $t('mailboxMould.cancel') }} + {{ + $t('mailboxMould.batchImportRun') + }} + +
@@ -145,7 +178,8 @@ const API = { listStyles: 'api/mail_template/listStyles', getAllJournal: 'api/Article/getJournal', deleteTemplate: 'api/mail_template/deleteTemplate', - deleteStyle: 'api/mail_template/deleteStyle' + deleteStyle: 'api/mail_template/deleteStyle', + saveTemplate: 'api/mail_template/saveTemplate' }; // 仅在当前 SPA 会话内记忆筛选(刷新页面即重置) const mailboxMouldSessionMemory = { @@ -177,7 +211,13 @@ export default { // --- 共用预览 --- previewVisible: false, - previewContent: '' + previewContent: '', + + /** 邮件模板 JSON 批量导入(期刊 ID 单独输入,与每条合并后调 saveTemplate) */ + batchTplImportVisible: false, + batchTplImportText: '', + batchTplImportJournalId: '', + batchTplImporting: false }; }, created() { @@ -281,6 +321,147 @@ export default { const journalId = this.tplFilters && this.tplFilters.journalId ? String(this.tplFilters.journalId) : ''; this.$router.push({ path: '/mailboxMouldDetail', query: journalId ? { journal_id: journalId } : {} }); }, + openTemplateBatchImportDialog() { + const cur = String((this.tplFilters && this.tplFilters.journalId) || '').trim(); + if (cur) this.batchTplImportJournalId = cur; + if (!this.batchTplImportText || !String(this.batchTplImportText).trim()) { + this.batchTplImportText = this.defaultTemplateBatchImportSample(); + } + this.batchTplImportVisible = true; + }, + defaultTemplateBatchImportSample() { + return ( + '[\n' + + ' {\n' + + ' "title": "示例标题",\n' + + ' "subject": "示例主题",\n' + + ' "scene": "invite_submission",\n' + + ' "language": "en",\n' + + ' "version": "1.0.0",\n' + + ' "body_html": "

正文 HTML

",\n' + + ' "variables_json": "",\n' + + ' "is_active": 1\n' + + ' }\n' + + ']\n' + ); + }, + /** 与 mailboxMouldDetail._doSave 提交 saveTemplate 字段对齐;支持 body、lang、variables 别名 */ + normalizeMailTemplateSavePayload(row) { + if (!row || typeof row !== 'object') return {}; + const bodyHtml = + row.body_html != null + ? String(row.body_html) + : row.body != null + ? String(row.body) + : ''; + const bodyTextRaw = row.body_text != null ? String(row.body_text) : ''; + const bodyText = + bodyTextRaw.trim() || + bodyHtml + .replace(/<[^>]+>/g, ' ') + .replace(/\s+/g, ' ') + .trim(); + const lang = String(row.language != null ? row.language : row.lang != null ? row.lang : 'en').toLowerCase(); + let isActive = '1'; + if (row.is_active === 0 || row.is_active === '0' || row.is_active === false) isActive = '0'; + const out = { + journal_id: String(row.journal_id != null ? row.journal_id : row.journalId != null ? row.journalId : '').trim(), + scene: String(row.scene || 'invite_submission'), + language: lang, + title: String(row.title || ''), + subject: String(row.subject || ''), + body_html: bodyHtml, + body_text: bodyText, + variables_json: String( + row.variables_json != null ? row.variables_json : row.variables != null ? row.variables : '' + ), + version: String(row.version != null ? row.version : '1.0.0'), + is_active: isActive + }; + const tid = row.template_id != null ? row.template_id : row.id; + if (tid != null && String(tid).trim() !== '') out.template_id = String(tid).trim(); + return out; + }, + applyTemplateBatchImportJournal(payload) { + const jid = String(this.batchTplImportJournalId || '').trim(); + if (jid) payload.journal_id = jid; + }, + validateMailTemplateSavePayload(p, index) { + if (!p.journal_id || !String(p.journal_id).trim()) { + return this.$t('mailboxMould.batchImportMissingJournal', { index: index + 1 }); + } + if (!p.title || !String(p.title).trim()) { + return this.$t('mailboxMould.batchImportMissingField', { index: index + 1, field: 'title' }); + } + if (!p.subject || !String(p.subject).trim()) { + return this.$t('mailboxMould.batchImportMissingField', { index: index + 1, field: 'subject' }); + } + if (!p.body_html || !String(p.body_html).trim()) { + return this.$t('mailboxMould.batchImportMissingField', { index: index + 1, field: 'body_html' }); + } + return ''; + }, + async runTemplateBatchImport() { + let rows; + try { + rows = JSON.parse(this.batchTplImportText || '[]'); + } catch (e) { + this.$message.error(this.$t('mailboxMould.batchImportBadJson')); + return; + } + if (!Array.isArray(rows) || !rows.length) { + this.$message.warning(this.$t('mailboxMould.batchImportEmpty')); + return; + } + this.batchTplImporting = true; + let ok = 0; + let fail = 0; + const errLines = []; + try { + for (let i = 0; i < rows.length; i++) { + const payload = this.normalizeMailTemplateSavePayload(rows[i]); + this.applyTemplateBatchImportJournal(payload); + const ve = this.validateMailTemplateSavePayload(payload, i); + if (ve) { + fail++; + errLines.push(ve); + continue; + } + try { + const res = await this.$api.post(API.saveTemplate, payload); + if (res && res.code === 0) { + ok++; + } else { + fail++; + errLines.push( + this.$t('mailboxMould.batchImportRowFail', { + index: i + 1, + msg: (res && res.msg) || this.$t('mailboxMould.batchImportSaveFail') + }) + ); + } + } catch (e) { + console.error(e); + fail++; + errLines.push(this.$t('mailboxMould.batchImportRowNetwork', { index: i + 1 })); + } + } + this.$message.success(this.$t('mailboxMould.batchImportDone', { ok, fail })); + if (errLines.length) { + this.$notify({ + title: this.$t('mailboxMould.batchImportErrorsTitle'), + message: errLines.slice(0, 8).join('\n'), + type: fail && !ok ? 'error' : 'warning', + duration: 12000 + }); + } + this.batchTplImportVisible = false; + this.syncTplFilterMemory(); + this.fetchTemplates(); + } finally { + this.batchTplImporting = false; + } + }, handleEditTemplate(row) { this.syncTplFilterMemory(); const templateId = row && (row.template_id || row.id); @@ -419,4 +600,39 @@ export default { background: #fff; box-sizing: border-box; } + +.batch-tpl-hint { + font-size: 12px; + line-height: 1.55; + color: #606266; + margin: 0 0 8px; +} +.batch-tpl-tip { + font-size: 12px; + line-height: 1.5; + color: #909399; + margin: 0 0 12px; +} +.batch-tpl-journal-row { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px; +} +.batch-tpl-label { + flex-shrink: 0; + font-size: 12px; + font-weight: 600; + color: #606266; + min-width: 72px; +} +.batch-tpl-journal-input { + flex: 1; + max-width: 360px; +} +.mailbox-mould-batch-import-dialog .batch-tpl-textarea >>> textarea { + font-family: Consolas, 'Courier New', monospace; + font-size: 12px; + line-height: 1.45; +}