423 lines
16 KiB
Vue
423 lines
16 KiB
Vue
<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>
|