Files
tougao_web/src/components/page/mailboxMould.vue
2026-04-28 13:27:09 +08:00

423 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="admin-container">
<el-tabs v-model="activeTab" @tab-click="handleTabChange">
<!-- ========== Tab 1: Templates ========== -->
<el-tab-pane :label="$t('mailboxMould.title') || 'Templates'" name="templates">
<div class="toolbar">
<el-select
v-model="tplFilters.journalId"
:placeholder="$t('mailboxMould.journalPlaceholder')"
style="width: 300px; margin-right: 10px;"
@change="fetchTemplates"
>
<el-option
v-for="j in journalList"
:key="j.journal_id"
:label="j.title"
:value="String(j.journal_id)"
></el-option>
</el-select>
<el-select
v-model="tplFilters.scene"
:placeholder="$t('mailboxMould.scenePlaceholder')"
clearable
style="width: 200px; margin-right: 10px;"
@change="fetchTemplates"
@clear="fetchTemplates"
>
<el-option :label="$t('mailboxMould.inviteSubmission')" value="invite_submission"></el-option>
<el-option :label="$t('mailboxMould.promoteCitation')" value="promote_citation"></el-option>
<el-option :label="$t('mailboxMould.generalThanks')" value="general_thanks"></el-option>
</el-select>
<el-select
v-model="tplFilters.language"
:placeholder="$t('mailboxMould.languagePlaceholder')"
clearable
style="width: 120px; margin-right: 10px;"
@change="fetchTemplates"
@clear="fetchTemplates"
>
<el-option label="EN" value="en"></el-option>
<el-option label="ZH" value="zh"></el-option>
</el-select>
<el-button type="primary" icon="el-icon-search" @click="fetchTemplates" style="margin-right: 10px;">
{{ $t('mailboxMould.searchBtn') }}
</el-button>
<div class="right-actions">
<el-button type="primary" plain icon="el-icon-plus" @click="handleCreateTemplate">{{ $t('mailboxMould.createTemplate') }}</el-button>
</div>
</div>
<el-table :data="tplTableData" border style="width: 100%; margin-top: 20px;" v-loading="tplLoading">
<el-table-column type="index" :label="$t('mailboxMould.no')" width="70" align="center"></el-table-column>
<el-table-column prop="title" :label="$t('mailboxMould.colTitle')" min-width="220">
<template slot-scope="scope">
<div class="title-cell"><strong>{{ scope.row.title }}</strong></div>
</template>
</el-table-column>
<el-table-column prop="subject" :label="$t('mailboxMould.colSubject')" min-width="220">
<template slot-scope="scope">
<span>{{ scope.row.subject || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="scene" :label="$t('mailboxMould.colScene')" width="160">
<template slot-scope="scope">
<el-tag size="small" type="info">{{ scope.row.scene }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="language" :label="$t('mailboxMould.colLanguage')" width="100">
<template slot-scope="scope">
{{ String(scope.row.language || '').toUpperCase() }}
</template>
</el-table-column>
<el-table-column prop="version" :label="$t('mailboxMould.colVersion')" width="100"></el-table-column>
<el-table-column :label="$t('mailboxMould.colStatus')" width="120">
<template slot-scope="scope">
<span :class="['status-dot', scope.row.is_active==1?'active':'inactive']"></span>
{{ scope.row.is_active == 1 ? $t('mailboxMould.active') : $t('mailboxMould.inactive') }}
</template>
</el-table-column>
<el-table-column width="160">
<template slot-scope="scope">
<el-button type="text" icon="el-icon-view" @click="handlePreviewTemplate(scope.row)"></el-button>
<el-button type="text" icon="el-icon-edit" @click="handleEditTemplate(scope.row)"></el-button>
<el-button type="text" icon="el-icon-delete" class="delete-btn" @click="handleDeleteTemplate(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- ========== Tab 2: Styles ========== -->
<el-tab-pane :label="$t('mailboxStyle.title') || 'Styles'" name="styles">
<div class="toolbar">
<div class="right-actions">
<el-button type="primary" icon="el-icon-refresh" @click="fetchStyles">{{ $t('mailboxStyle.searchBtn') }}</el-button>
<el-button type="primary" plain icon="el-icon-plus" @click="handleCreateStyle">{{ $t('mailboxStyle.createStyle') }}</el-button>
</div>
</div>
<el-table :data="styleTableData" border style="width: 100%; margin-top: 20px;" v-loading="styleLoading">
<el-table-column type="index" :label="$t('mailboxStyle.no')" width="70" align="center"></el-table-column>
<el-table-column prop="name" :label="$t('mailboxStyle.colName')" min-width="220">
<template slot-scope="scope">
<div class="title-cell"><strong>{{ scope.row.title }}</strong></div>
</template>
</el-table-column>
<el-table-column prop="description" :label="$t('mailboxStyle.colDescription')" min-width="220">
<template slot-scope="scope">
<span>{{ scope.row.description || '-' }}</span>
</template>
</el-table-column>
<el-table-column width="160">
<template slot-scope="scope">
<el-button type="text" icon="el-icon-view" @click="handlePreviewStyle(scope.row)"></el-button>
<el-button type="text" icon="el-icon-edit" @click="handleEditStyle(scope.row)"></el-button>
<el-button type="text" icon="el-icon-delete" class="delete-btn" @click="handleDeleteStyle(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<!-- 预览弹窗两个 Tab 共用 -->
<el-dialog
:title="$t('mailboxMould.previewTitle')"
:visible.sync="previewVisible"
width="80%"
>
<div class="preview-body" v-html="previewContent"></div>
<span slot="footer" class="dialog-footer">
<el-button @click="previewVisible = false">{{ $t('mailboxMould.previewClose') }}</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
const API = {
listTemplates: 'api/mail_template/listTemplates',
listStyles: 'api/mail_template/listStyles',
getAllJournal: 'api/Article/getJournal',
deleteTemplate: 'api/mail_template/deleteTemplate',
deleteStyle: 'api/mail_template/deleteStyle'
};
// 仅在当前 SPA 会话内记忆筛选(刷新页面即重置)
const mailboxMouldSessionMemory = {
journalId: '',
scene: '',
language: ''
};
export default {
data() {
return {
activeTab: 'templates',
journalList: [],
// 用于避免 ElementUI 初始化时触发的 tab 事件把 localStorage 写回 templates
tabChangeLocked: true,
// --- Templates ---
tplLoading: false,
tplFilters: {
journalId: '',
scene: '',
language: ''
},
tplTableData: [],
// --- Styles ---
styleLoading: false,
styleTableData: [],
// --- 共用预览 ---
previewVisible: false,
previewContent: ''
};
},
created() {
const q = (this.$route && this.$route.query) || {};
const routeTab = q && (q.activeTab || q.tab || q.mouldTab) ? String(q.activeTab || q.tab || q.mouldTab) : '';
let savedTab = localStorage.getItem('mailboxMouldActiveTab') || '';
savedTab = String(savedTab).trim();
const normalized = (routeTab && (routeTab === 'styles' || routeTab === 'templates')) ? routeTab : savedTab;
if (normalized === 'styles' || normalized === 'templates') this.activeTab = normalized;
// 初始化期间不要响应 tab-click避免覆盖 localStorage
this.tabChangeLocked = true;
this.loadJournals();
this.$nextTick(() => {
this.tabChangeLocked = false;
});
},
methods: {
// ========== 公共 ==========
loadJournals() {
this.$api
.post(API.getAllJournal, {username: localStorage.getItem('U_name')})
.then(res => {
const list = res || [];
const mapped = (Array.isArray(list) ? list : []).map(j => ({
journal_id: j.journal_id || j.id,
title: j.title || j.name || ''
}));
this.journalList = mapped;
if (mapped.length > 0) {
const rememberedJournalId = String(mailboxMouldSessionMemory.journalId || '').trim();
const hasRemembered = rememberedJournalId && mapped.some(j => String(j.journal_id) === rememberedJournalId);
this.tplFilters.journalId = hasRemembered
? rememberedJournalId
: String(mapped[0].journal_id);
}
this.tplFilters.scene = String(mailboxMouldSessionMemory.scene || '');
this.tplFilters.language = String(mailboxMouldSessionMemory.language || '');
this.syncTplFilterMemory();
if (this.activeTab === 'styles') {
this.fetchStyles();
} else {
this.fetchTemplates();
}
})
.catch(() => {
this.journalList = [];
});
},
handleTabChange(tab) {
if (this.tabChangeLocked) return;
localStorage.setItem('mailboxMouldActiveTab', tab.name);
if (tab.name === 'templates' && this.tplTableData.length === 0) {
this.fetchTemplates();
} else if (tab.name === 'styles' && this.styleTableData.length === 0) {
this.fetchStyles();
}
},
// ========== Templates ==========
syncTplFilterMemory() {
mailboxMouldSessionMemory.journalId = String((this.tplFilters && this.tplFilters.journalId) || '');
mailboxMouldSessionMemory.scene = String((this.tplFilters && this.tplFilters.scene) || '');
mailboxMouldSessionMemory.language = String((this.tplFilters && this.tplFilters.language) || '');
},
fetchTemplates() {
this.tplLoading = true;
const params = {
journal_id: this.tplFilters.journalId || '',
scene: this.tplFilters.scene || '',
language: this.tplFilters.language || ''
};
this.syncTplFilterMemory();
this.$api
.post(API.listTemplates, params)
.then(res => {
this.tplLoading = false;
const list = (res && res.data && res.data.list) || [];
this.tplTableData = (Array.isArray(list) ? list : []).map(item => ({
id: item.template_id || item.id,
template_id: item.template_id || item.id,
title: item.title,
subject: item.subject,
body_html: item.body_html,
description: item.description || '',
scene: item.scene,
language: item.language,
version: item.version,
is_active: item.is_active || 0
}));
})
.catch(() => {
this.tplLoading = false;
this.tplTableData = [];
});
},
handleCreateTemplate() {
this.syncTplFilterMemory();
// 传入当前模板列表选中的期刊,详情页用于默认回填
const journalId = this.tplFilters && this.tplFilters.journalId ? String(this.tplFilters.journalId) : '';
this.$router.push({ path: '/mailboxMouldDetail', query: journalId ? { journal_id: journalId } : {} });
},
handleEditTemplate(row) {
this.syncTplFilterMemory();
const templateId = row && (row.template_id || row.id);
const journalId = this.tplFilters && this.tplFilters.journalId ? String(this.tplFilters.journalId) : '';
const query = templateId ? { template_id: String(templateId) } : {};
// 编辑时一般会由模板详情回填期刊,但带上也能避免个别场景先展示错误默认值
if (journalId && !query.journal_id) query.journal_id = journalId;
this.$router.push({ path: '/mailboxMouldDetail', query });
},
handlePreviewTemplate(row) {
this.previewContent = row && row.body_html ? row.body_html : '';
this.previewVisible = true;
},
handleDeleteTemplate(row) {
this.syncTplFilterMemory();
const templateId = row && (row.template_id || row.id);
if (!templateId) return;
this.$confirm(this.$t('mailboxMould.deleteConfirm'), this.$t('mailboxMould.colActions'), {
confirmButtonText: this.$t('mailboxMould.confirm'),
cancelButtonText: this.$t('mailboxMould.cancel'),
type: 'warning'
}).then(() => {
this.$api.post(API.deleteTemplate, { template_id: String(templateId) }).then(res => {
if (res && res.code === 0) {
this.$message.success(this.$t('mailboxMould.deleteSuccess'));
this.fetchTemplates();
} else {
this.$message.error((res && res.msg) || this.$t('mailboxMould.deleteFail'));
}
}).catch(() => {
this.$message.error(this.$t('mailboxMould.deleteFail'));
});
}).catch(() => {});
},
// ========== Styles ==========
fetchStyles() {
this.styleLoading = true;
this.$api
.post(API.listStyles, {})
.then(res => {
this.styleLoading = false;
const list = (res && res.data && res.data.list) || [];
this.styleTableData = (Array.isArray(list) ? list : []).map(item => ({
id: item.style_id || item.id,
style_id: item.style_id || item.id,
title: item.name,
header_html: item.header_html,
footer_html: item.footer_html,
description: item.description || ''
}));
})
.catch(() => {
this.styleLoading = false;
this.styleTableData = [];
});
},
handleCreateStyle() {
this.$router.push({ path: '/mailboxStyleDetail' });
},
handleEditStyle(row) {
const styleId = row && (row.style_id || row.id);
this.$router.push({ path: '/mailboxStyleDetail', query: styleId ? { style_id: String(styleId) } : {} });
},
handlePreviewStyle(row) {
const header = (row && row.header_html) || '';
const footer = (row && row.footer_html) || '';
this.previewContent = `${header}${footer}`;
this.previewVisible = true;
},
handleDeleteStyle(row) {
const styleId = row && (row.style_id || row.id);
if (!styleId) return;
this.$confirm(this.$t('mailboxMould.deleteConfirm'), this.$t('mailboxMould.colActions'), {
confirmButtonText: this.$t('mailboxMould.confirm'),
cancelButtonText: this.$t('mailboxMould.cancel'),
type: 'warning'
}).then(() => {
this.$api.post(API.deleteStyle, { style_id: String(styleId) }).then(res => {
if (res && res.code === 0) {
this.$message.success(this.$t('mailboxMould.deleteSuccess'));
this.fetchStyles();
} else {
this.$message.error((res && res.msg) || this.$t('mailboxMould.deleteFail'));
}
}).catch(() => {
this.$message.error(this.$t('mailboxMould.deleteFail'));
});
}).catch(() => {});
}
}
};
</script>
<style scoped>
.admin-container {
padding: 20px;
background: #fff;
}
.subtitle {
color: #666;
font-size: 14px;
margin-bottom: 20px;
}
.toolbar {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.right-actions {
margin-left: auto;
display: flex;
gap: 8px;
}
.status-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 5px;
}
.status-dot.active {
background-color: #52c41a;
}
.status-dot.inactive {
background-color: #f5222d;
}
.delete-btn {
color: #f5222d !important;
}
.preview-body {
max-height: 70vh;
overflow: auto;
padding: 10px;
border: 1px solid #eee;
background: #fff;
box-sizing: border-box;
}
</style>