自动化推广【约稿】

This commit is contained in:
2026-03-23 09:28:56 +08:00
parent f44b3910a4
commit 12760aaf44
21 changed files with 3482 additions and 559 deletions

View File

@@ -0,0 +1,892 @@
<template>
<div class="auto-promo-container">
<div class="crumbs">
<div class="page-header">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-s-promotion"></i> {{ $t('autoPromotion.title') }}</el-breadcrumb-item>
<el-breadcrumb-item>
{{ $t('autoPromotionLogs.detail') }}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</div>
<el-card shadow="never" class="journal-header-card">
<div class="config-bar">
<div class="left">
<span class="label">{{ $t('autoPromotion.journal') }} : </span>
{{currentJournalName}}
<template v-if="config.initialized">
<el-tag type="success" size="small" effect="plain" style="margin-left: 10px">
<i class="el-icon-circle-check"></i> {{ $t('autoPromotionLogs.configured') }}
</el-tag>
<el-button
type="text"
size="small"
style="margin-left: 10px"
@click="openWizardDialog"
>
<i class="el-icon-edit"></i>
{{
config.initialized
? $t('autoPromotionLogs.editConfig')
: $t('autoPromotionLogs.startConfig')
}}
</el-button>
</template>
<el-tag v-else type="info" size="small" effect="plain" style="margin-left: 10px">
<i class="el-icon-info"></i> {{ $t('autoPromotionLogs.notConfigured') }}
</el-tag>
</div>
<div class="right" v-if="config.initialized">
<el-button size="small" icon="el-icon-document" @click="openLogs">
{{ $t('autoPromotion.logs') }}
</el-button>
</div>
</div>
</el-card>
<div v-loading="loading" class="main-body">
<el-card v-if="!config.initialized" shadow="never" class="wizard-card">
<auto-promotion-wizard
mode="inline"
:config="config"
:wizardStartDate.sync="wizardStartDate"
:currentJournalName="currentJournalName"
:selectedTemplateThumbHtml="selectedTemplateThumbHtml"
:selectedTemplateName="selectedTemplateName"
:selectedStyleName="selectedStyleName"
:saving="saving"
:title="$t('autoPromotion.title')"
@open-template-selector="showTemplateDialog = true"
@confirm="completeInitialization"
/>
</el-card>
<div v-else class="manage-mode" style="display: none;">
<el-card shadow="never" class="list-card">
<div class="list-header">
<el-input
v-model="query.keyword"
size="small"
clearable
style="width: 300px"
:placeholder="$t('autoPromotionLogs.searchPlaceholder')"
@keyup.enter.native="fetchList"
>
</el-input>
<el-button type="primary" size="small" icon="el-icon-search" style="margin-left: 20px;" @click="fetchList">{{ $t('autoPromotionLogs.searchBtn') }}</el-button>
</div>
<el-table :data="list" border stripe size="small" class="custom-table">
<el-table-column type="index" :label="$t('autoPromotionLogs.index')" width="70" align="center"></el-table-column>
<el-table-column :label="$t('autoPromotionLogs.expertInfo')" min-width="180">
<template slot-scope="scope">
<div class="user-info">
<div class="name">{{ scope.row.name }}</div>
<div class="email">{{ scope.row.email }}</div>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('autoPromotionLogs.templateStyle')" min-width="200">
<template slot-scope="scope">
<div class="template-info-cell">
<div class="text-content">
<div class="tpl-main">{{ getTemplateName(scope.row.template_id) }}</div>
<div class="tpl-sub">{{ $t('autoPromotionLogs.stylePrefix') }}: {{ getStyleDisplay(scope.row) }}</div>
</div>
<el-button
type="text"
icon="el-icon-edit-outline"
class="edit-icon-btn"
@click="handleEditRowTemplate(scope.row)"
></el-button>
</div>
</template>
</el-table-column>
<el-table-column prop="run_at" :label="$t('autoPromotionLogs.runAt')" width="180">
<template slot-scope="scope">
<span class="time-cell">
<i class="el-icon-time"></i> {{ scope.row.run_at }}
</span>
</template>
</el-table-column>
<el-table-column :label="$t('autoPromotionLogs.status')" width="120">
<template slot-scope="scope">
<span :class="['status-badge', scope.row.paused ? 'is-paused' : 'is-waiting']">
{{ scope.row.paused ? $t('autoPromotionLogs.paused') : $t('autoPromotionLogs.toRun') }}
</span>
</template>
</el-table-column>
<el-table-column :label="$t('autoPromotionLogs.operation')" width="150" align="center">
<template slot-scope="scope">
<div class="action-btns">
<el-tooltip :content="$t('autoPromotionLogs.preview')" placement="top">
<el-button type="text" icon="el-icon-view" @click="previewRow(scope.row)"></el-button>
</el-tooltip>
<el-tooltip :content="scope.row.paused ? $t('autoPromotionLogs.enable') : $t('autoPromotionLogs.pause')" placement="top">
<el-button
type="text"
:icon="scope.row.paused ? 'el-icon-video-play' : 'el-icon-video-pause'"
@click="togglePause(scope.row)"
></el-button>
</el-tooltip>
<el-button
type="text"
icon="el-icon-delete"
class="delete-btn"
@click="deleteRow(scope.row)"
></el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
background
layout="total, prev, pager, next"
:current-page="query.pageIndex"
:page-size="query.pageSize"
:total="total"
@current-change="handlePageChange"
/>
</div>
</el-card>
</div>
</div>
<auto-promotion-wizard
mode="dialog"
:visible.sync="showWizardDialog"
:config="config"
:wizardStartDate.sync="wizardStartDate"
:currentJournalName="currentJournalName"
:selectedTemplateThumbHtml="selectedTemplateThumbHtml"
:selectedTemplateName="selectedTemplateName"
:selectedStyleName="selectedStyleName"
:saving="saving"
:title="$t('autoPromotion.title')"
@open-template-selector="showTemplateDialog = true"
@cancel="showWizardDialog = false"
@confirm="completeInitialization"
/>
<template-selector-dialog
v-if="showTemplateDialog"
:visible.sync="showTemplateDialog"
:journalId="selectedJournalId"
:journalLabel="currentJournalName"
:initial-style-id="templateDialogInitialStyleId"
:initial-template-id="templateDialogInitialTemplateId"
:return-source="'autoPromotion'"
@confirm="handleTemplateApply"
@close-all-dialogs="closeAllDialogs"
/>
<el-dialog :title="$t('autoPromotionLogs.previewEditTitle')" :visible.sync="showPreviewDialog" width="1200px" top="5vh">
<div class="mail-edit-wrapper" v-if="previewForm">
<el-form label-width="80px" size="small">
<el-form-item :label="$t('autoPromotionLogs.receiver')">
<el-input v-model="previewForm.email" disabled :placeholder="$t('autoPromotionLogs.receiverImmutablePlaceholder')" />
</el-form-item>
<el-form-item :label="$t('autoPromotionLogs.subject')">
<el-input v-model="previewForm.subject" :placeholder="$t('autoPromotionLogs.subjectPlaceholder')" />
</el-form-item>
<el-form-item :label="$t('autoPromotionLogs.runAt')">
<el-date-picker
v-model="previewForm.run_at"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
:placeholder="$t('autoPromotionLogs.runAtPlaceholder')"
style="width: 100%;"
/>
</el-form-item>
<CkeditorMail v-model="previewForm.content" />
</el-form>
</div>
<div slot="footer">
<el-button size="small" @click="showPreviewDialog = false">{{ $t('autoPromotionLogs.cancel') }}</el-button>
<el-button size="small" type="primary" @click="saveMailContent" :loading="saving">{{ $t('autoPromotionLogs.confirmEdit') }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import CkeditorMail from '@/components/page/components/email/CkeditorMail.vue';
import TemplateSelectorDialog from '@/components/page/components/email/TemplateSelectorDialog.vue'
import AutoPromotionWizard from '@/components/page/components/autoPromotion/AutoPromotionWizard.vue'
// 这里假设你已经定义了 API 地址
const API = {
getAllJournal: 'api/email_client/getPromotionJournalList',
listTemplates: 'api/mail_template/listTemplates',
getConfig: 'api/auto_promotion/getConfig',
saveConfig: 'api/email_client/setDefaultPromotion',
list: 'api/auto_promotion/getList',
};
export default {
name: 'autoPromotion',
components: { TemplateSelectorDialog, AutoPromotionWizard, CkeditorMail },
data() {
return {
// 基础数据
journalOptions: [],
selectedJournalId: '',
templateOptions: [],
// 配置对象
config: {
initialized: false, // 核心:控制向导与列表的显示
enabled: false,
defaultTemplateId: '',
defaultTime: '09:00:00'
},
// 向导控制
wizardStep: 0,
wizardStartDate: '',
showTemplateDialog: false,
selectedTemplateThumbHtml: '',
selectedTemplateName: '',
selectedStyleName: '',
// 列表数据
query: { keyword: '', pageIndex: 1, pageSize: 15 },
list: [],
total: 0,
// 状态控制
loading: false,
saving: false,
currentJournalName: '',
// 向导弹窗(用于编辑已初始化配置)
showWizardDialog: false,
showPreviewDialog: false,
currentRow: null,
templateDialogInitialStyleId: '',
templateDialogInitialTemplateId: '',
previewForm: {
id: '',
email: '',
run_at: '',
subject: '',
content: ''
},
};
},
computed: {
},
created() {
this.initPage();
},
methods: {
closeAllDialogs() {
// 点击“去新增模板”后:关闭当前页面所有可能的弹窗
this.showWizardDialog = false;
this.showTemplateDialog = false;
this.showPreviewDialog = false;
this.currentRow = null;
},
openLogs() {
this.$router.push({
path: '/autoPromotionLogs',
query: { journal_id: this.selectedJournalId || '' }
});
},
// 点击列表“查看”图标
previewRow(row) {
this.previewForm = {
id: row.id,
email: row.email,
run_at: row.run_at || '',
// 假设后端或mock数据里有这些字段没有则赋初始值
subject:
row.subject ||
this.$t('autoPromotionLogs.mockPromotionSubject', { journal: this.currentJournalName }),
content:
row.mail_content ||
this.$t('autoPromotionLogs.mockPromotionContent', { name: row.name || '' })
};
this.showPreviewDialog = true;
},
// 保存修改后的邮件内容
async saveMailContent() {
this.saving = true;
try {
// 模拟调用 API
// await this.$api.post(API.updateMailDetail, this.previewForm);
// 同步到本地 list 中Mock 效果)
const index = this.list.findIndex(item => item.id === this.previewForm.id);
if (index !== -1) {
this.$set(this.list[index], 'run_at', this.previewForm.run_at);
this.$set(this.list[index], 'subject', this.previewForm.subject);
this.$set(this.list[index], 'mail_content', this.previewForm.content);
}
this.$message.success(this.$t('autoPromotionLogs.mailContentSaved'));
this.showPreviewDialog = false;
} finally {
this.saving = false;
}
},
// 修改 handleEditRowTemplate 使其调用你已有的选择器
handleEditRowTemplate(row) {
this.currentRow = row;
this.showTemplateDialog = true;
},
// 处理表格内点击编辑图标
handleEditRowTemplate(row) {
this.currentRow = row; // 记录当前正在操作哪一行
this.templateDialogInitialStyleId = row && row.style_id ? String(row.style_id) : '';
this.templateDialogInitialTemplateId = row && row.template_id ? String(row.template_id) : '';
this.showTemplateDialog = true;
},
// 修改原有的 handleTemplateApply 方法,使其兼容向导和表格修改
handleTemplateApply(payload) {
const templateObj = payload && payload.template ? payload.template : {};
const styleObj = payload && payload.style ? payload.style : {};
const templateId = templateObj.id || (payload && payload.template_id) || '';
const templateName = templateObj.name || '';
const styleName = styleObj.name || '';
if (this.currentRow) {
// 说明是修改表格里的某一行
this.currentRow.template_id = String(templateId);
this.currentRow.template_name = templateName;
this.currentRow.style_name = styleName;
this.updateRow(this.currentRow); // 提交保存
this.currentRow = null; // 清空引用
} else {
// 说明是向导模式在选择
this.config.defaultTemplateId = String(templateId);
this.selectedTemplateName = templateName;
this.selectedStyleName = styleName;
this.selectedTemplateThumbHtml = `<div style="zoom:0.18; pointer-events:none;">${payload.html}</div>`;
}
this.showTemplateDialog = false;
},
getTemplateName(id) {
const found = this.templateOptions.find(t => String(t.template_id) === String(id));
return found ? found.title : id || this.$t('autoPromotionLogs.templateNotSelected');
},
getStyleDisplay(row) {
if (!row) return '-';
if (row.style_name) return row.style_name;
if (row.style_id) return String(row.style_id);
return '-';
},
async initPage() {
var journal_id = (this.$route.query && this.$route.query.journal_id) || '';
this.selectedJournalId = String(journal_id);
this.loading = true;
try {
await this.fetchJournalDetail();
if (this.config.initialized) {
await this.fetchTemplates();
await this.fetchList();
}
} finally {
this.loading = false;
}
},
_parseJournalDetail(data) {
const journalInfo = data.journal || data || {};
const tpl = journalInfo.template || data.template || {};
const style = journalInfo.style || data.style || {};
const tplId = String(journalInfo.default_template_id || '0');
const styleId = String(journalInfo.default_style_id || '0');
return {
title: journalInfo.title || journalInfo.journal_title || '',
templateId: tplId,
styleId: styleId,
templateName: tpl.name || tpl.title || journalInfo.default_template_name || '',
styleName: style.name || style.title || journalInfo.default_style_name || '',
html: `${style.header_html || ''}${tpl.body_html || ''}${style.footer_html || ''}`,
enabled: String(journalInfo.start_promotion || '0') === '1',
initialized: tplId !== '0' && styleId !== '0'
};
},
async fetchJournalDetail() {
if (!this.selectedJournalId) {
this.config.initialized = false;
return;
}
try {
var res = await this.$api.post('api/email_client/getPromotionJournalDetail', {
journal_id: this.selectedJournalId
});
var data = (res && res.data) || {};
var detail = this._parseJournalDetail(data);
var journalInfo = data.journal || data || {};
this.currentJournalName = detail.title || this.currentJournalName || '';
this.config = {
initialized: detail.initialized,
enabled: detail.enabled, // 回显“是否立即推广”
defaultTemplateId: detail.initialized ? detail.templateId : '',
defaultStyleId: detail.initialized ? detail.styleId : '',
defaultTime: journalInfo.default_time || this.config.defaultTime || ''
};
// 回显默认模板/风格(用于“修改默认配置”)
this.selectedTemplateName = detail.initialized ? (detail.templateName || '') : '';
this.selectedStyleName = detail.initialized ? (detail.styleName || '') : '';
this.selectedTemplateThumbHtml = detail.initialized && detail.html
? `<div style="zoom:0.18; pointer-events:none; user-select:none;">${detail.html}</div>`
: '';
// 打开模板选择弹窗时,默认选中当前配置
this.templateDialogInitialTemplateId = detail.initialized ? String(detail.templateId || '') : '';
this.templateDialogInitialStyleId = detail.initialized ? String(detail.styleId || '') : '';
} catch (e) {
this.config.initialized = false;
}
},
// 打开向导弹窗:用于“修改期刊自动推广配置”
openWizardDialog() {
this.showWizardDialog = true;
this.wizardStep = 0;
// 尽量从已加载的 config 里取开始日期(没有就保持空)
if (this.config && this.config.start_date) {
this.wizardStartDate = this.config.start_date;
}
},
// 切换期刊逻辑
async handleJournalChange() {
this.loading = true;
// 切换期刊时:先清空“已选模板/缩略图/风格名”,再判断该期刊是否初始化
this.showTemplateDialog = false;
this.selectedTemplateThumbHtml = '';
this.selectedTemplateName = '';
this.selectedStyleName = '';
this.config.defaultTemplateId = '';
this.wizardStartDate = '';
this.wizardStep = 0;
try {
await this.fetchTemplates(); // 获取对应期刊的模版
await this.loadConfig(); // 获取该期刊的初始化状态
if (!this.config.initialized) {
// 未初始化:确保默认模板为空(保持向导第一步)
this.config.defaultTemplateId = '';
} else if (this.config.initialized) {
await this.fetchList(); // 已初始化的直接加载列表
}
} finally {
this.loading = false;
}
},
async fetchJournals() {
const res = await this.$api.post(API.getAllJournal, {user_id: localStorage.getItem('U_id')});
this.journalOptions = res.data.journals || [];
},
async fetchTemplates() {
const res = await this.$api.post(API.listTemplates, { journal_id: this.selectedJournalId });
this.templateOptions = (res.data.list || []).map(t => ({
template_id: t.template_id || t.id,
title: t.title || t.name
}));
},
async loadConfig() {
await this.fetchJournalDetail();
},
// 接收模版选择:兼容向导与列表行编辑
handleTemplateApply(payload) {
// payload 兼容:可能是 string(html) 或对象
const html = payload && typeof payload === 'object' ? payload.html : String(payload || '');
const templateId =
payload && typeof payload === 'object'
? (payload.template_id || payload.id || (payload.template && (payload.template.id || payload.template.template_id)) || '')
: '';
const styleId =
payload && typeof payload === 'object'
? (payload.style_id || (payload.style && payload.style.id) || '')
: '';
const templateName = payload && typeof payload === 'object' && payload.template ? payload.template.name : '';
const styleName = payload && typeof payload === 'object' && payload.style ? payload.style.name : '';
// 必须选到模板template_id才允许“下一步”解除置灰
if (!templateId) {
this.$message.warning(this.$t('autoPromotionLogs.selectTemplateWarning'));
return;
}
if (this.currentRow) {
this.currentRow.template_id = String(templateId);
this.currentRow.template_name = templateName || this.getTemplateName(templateId);
this.currentRow.style_id = styleId ? String(styleId) : this.currentRow.style_id;
this.currentRow.style_name = styleName || this.currentRow.style_name || '';
this.updateRow(this.currentRow);
this.currentRow = null;
} else {
this.config.defaultTemplateId = String(templateId);
this.selectedTemplateName = templateName || '';
this.selectedStyleName = styleName || '';
// 生成缩略图 HTML禁用交互避免点击跳转
this.selectedTemplateThumbHtml = `<div style="zoom:0.18; pointer-events:none; user-select:none;">${html}</div>`;
}
this.templateDialogInitialStyleId = '';
this.templateDialogInitialTemplateId = '';
this.showTemplateDialog = false;
// this.$message.success(this.$t('autoPromotion.saved') || 'Saved');
},
// 最终提交初始化
async completeInitialization() {
this.saving = true;
try {
const payload = {
journal_id: String(this.selectedJournalId || ''),
default_template_id: String(this.config.defaultTemplateId || ''),
default_style_id: String(this.config.defaultStyleId || ''),
start_promotion: this.config.enabled ? '1' : '0'
};
this.config.initialized = true; // 切换到管理模式
this.showWizardDialog = false;
this.fetchList();
await this.$api.post(API.saveConfig, payload);
this.$message.success(this.$t('autoPromotionLogs.configUpdated'));
} finally {
this.saving = false;
}
},
async saveConfig() {
// 用于管理模式下的快捷修改保存
await this.$api.post(API.saveConfig, {
journal_id: String(this.selectedJournalId || ''),
default_template_id: String(this.config.defaultTemplateId || ''),
default_style_id: String(this.config.defaultStyleId || ''),
start_promotion: this.config.enabled ? '1' : '0'
});
this.$message.success(this.$t('autoPromotionLogs.configUpdated'));
},
async fetchList() {
this.loading = true;
try {
// 当前接口未联通:直接使用本地假数据
const mock = this.generateMockList();
this.list = mock.list;
this.total = mock.total;
} finally {
this.loading = false;
}
},
// 生成用于页面展示的假数据mock
generateMockList() {
const tpl1 = this.templateOptions && this.templateOptions.length ? String(this.templateOptions[0].template_id) : '1';
const tpl2 = this.templateOptions && this.templateOptions.length > 1 ? String(this.templateOptions[1].template_id) : tpl1;
const baseDate = new Date();
// 使用固定的偏移,保证每次打开都有“未来”的时间
const d1 = new Date(baseDate.getTime() + 86400000 * 2);
const d2 = new Date(baseDate.getTime() + 86400000 * 3);
const d3 = new Date(baseDate.getTime() + 86400000 * 4);
const d4 = new Date(baseDate.getTime() + 86400000 * 5);
const d5 = new Date(baseDate.getTime() + 86400000 * 6);
const fmt = (d) => {
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return `${yyyy}-${mm}-${dd} 09:00:00`;
};
const mk = (idx, name, email, template_id, style_id, style_name, paused, run_at) => ({
id: `mock_${idx}`,
name,
email,
template_id: String(template_id),
style_id: String(style_id),
style_name: style_name || '',
paused: Boolean(paused),
run_at
});
const list = [
mk(1, '张三', 'zhangsan@example.com', tpl1, 1, '简约风格', false, fmt(d1)),
mk(2, '李四', 'lisi@example.com', tpl2, 2, '商务风格', true, fmt(d2)),
mk(3, '王五', 'wangwu@example.com', tpl1, 1, '简约风格', false, fmt(d3)),
mk(4, '赵六', 'zhaoliu@example.com', tpl2, 3, '学术风格', false, fmt(d4)),
mk(5, '钱七', 'qianqi@example.com', tpl1, 2, '商务风格', true, fmt(d5)),
];
return { list, total: list.length };
},
// 模板切换mock仅更新本行数据不做后端请求
updateRow(row) {
if (!row) return;
row.template_id = row.template_id == null ? '' : String(row.template_id);
this.$message.success(this.$t('autoPromotion.saved') || 'Saved');
},
// 暂停/开启mock切换 paused
togglePause(row) {
if (!row) return;
row.paused = !row.paused;
if (row.paused) {
this.$message({
type: 'info',
message: this.$t('autoPromotionLogs.pauseSuccess')
});
} else {
this.$message.success(this.$t('autoPromotionLogs.enableSuccess'));
}
},
// 删除mock从列表移除
deleteRow(row) {
if (!row) return;
this.list = (this.list || []).filter(item => item !== row);
this.total = this.list.length;
this.$message.success(this.$t('autoPromotionLogs.deletedSuccess'));
},
handlePageChange(p) {
this.query.pageIndex = p;
this.fetchList();
}
}
};
</script>
<style scoped>
.auto-promo-container { background: #f9fafc; min-height: 90%; }
.journal-header-card { margin-bottom: 15px; }
.config-bar { display: flex; justify-content: space-between; align-items: center; }
/* 向导样式 */
.wizard-card { margin-top: 10px; min-height: 500px; }
.wizard-header { padding: 20px 0 40px; border-bottom: 1px solid #f0f2f5; }
.wizard-body { padding: 40px 0; }
.step-content { text-align: center; }
.step-tips { margin-bottom: 30px; }
.step-tips i { font-size: 40px; color: #409EFF; margin-bottom: 15px; }
.template-placeholder {
width: 520px;
max-width: 100%;
border: 2px dashed #dcdfe6;
margin: 0 auto;
border-radius: 8px;
padding: 14px;
cursor: pointer;
transition: 0.3s;
}
.template-placeholder:hover { border-color: #409EFF; color: #409EFF; background: #f5f7fa; }
.template-placeholder .inner { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 150px; }
.template-placeholder .inner.selected { display: block; min-height: unset; color: #333; }
.template-placeholder .inner i { font-size: 30px; }
.thumb-box {
border: 1px solid #ebeef5;
border-radius: 6px;
overflow: hidden;
background: #fff;
height: 120px;
margin-bottom: 10px;
}
.thumb-content { transform-origin: top left; }
.selected-meta { display: flex; align-items: center; gap: 10px; }
.selected-text { flex: 1; min-width: 0; }
.tpl-line { color: #606266; line-height: 20px; }
.style-line { color: #909399; font-size: 12px; line-height: 18px; margin-top: 2px; }
.wizard-footer { text-align: center; padding-top: 30px; border-top: 1px solid #f0f2f5; }
/* 管理模式样式 */
.config-card { margin-bottom: 15px; background: #fff; }
.list-card { background: #fff; }
.list-header { margin-bottom: 15px; }
.pagination { margin-top: 20px; text-align: right; }
.final-check { margin-top: 15px; line-height: 2; }
/* 弹窗容器布局 */
.vertical-wizard-container {
display: flex;
gap: 30px;
padding: 10px;
}
.side-nav {
width: 120px;
flex-shrink: 0;
padding-top: 10px;
}
.main-form-content {
flex: 1;
max-height: 60vh; /* 开启滚动条防止内容过长 */
overflow-y: auto;
padding-right: 15px;
}
/* 每一个小节的样式 */
.form-section {
margin-bottom: 10px;
}
.section-title {
margin: 0 0 20px 0;
font-size: 15px;
color: #303133;
display: flex;
align-items: center;
}
.section-title i {
margin-right: 8px;
color: #409EFF;
font-size: 18px;
}
/* 优化后的模板选择框 */
.template-placeholder.mini-mode {
width: 100%;
padding: 15px;
min-height: 60px;
background: #f8f9fb;
}
.thumb-box-mini {
width: 100px;
height: 60px;
border: 1px solid #ddd;
overflow: hidden;
background: #fff;
border-radius: 4px;
margin-right: 15px;
}
.status-confirm-box {
padding: 15px;
background: #f0f9eb;
border-radius: 4px;
border: 1px inset #e1f3d8;
}
.selected-meta {
display: flex;
align-items: center;
}
/* 调整分割线间距 */
.el-divider--horizontal {
margin: 24px 0;
}
/* 表格内容排版 */
.user-info .name { font-weight: bold; color: #303133; margin-bottom: 2px; }
.user-info .email { color: #909399; font-size: 12px; }
.template-info-cell { display: flex; align-items: center; justify-content: space-between; }
.tpl-main { font-weight: 600; color: #409EFF; font-size: 13px; }
.tpl-sub { color: #909399; font-size: 12px; margin-top: 2px; }
.edit-icon-btn { font-size: 16px; margin-left: 8px; color: #909399; }
.edit-icon-btn:hover { color: #409EFF; }
.time-cell { color: #606266; display: flex; align-items: center; gap: 5px; }
/* 状态标签 (带圆点) */
.status-badge {
display: inline-flex;
align-items: center;
padding: 2px 10px;
border-radius: 12px;
font-size: 12px;
}
.status-badge::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 6px;
}
.is-waiting { color: #e67e22; background: #fff3e6; }
.is-waiting::before { background: #e67e22; }
.is-paused { color: #909399; background: #f4f4f5; }
.is-paused::before { background: #909399; }
/* 操作按钮 */
.action-btns .el-button {
font-size: 18px;
padding: 0 5px;
color: #606266;
}
.action-btns .el-button:hover { color: #409EFF; }
.action-btns .delete-btn { color: #F56C6C; }
.action-btns .delete-btn:hover { color: #F56C6C; }
.action-btns .delete-btn.el-button--text { color: #F56C6C !important; }
/* 深度选择器处理表格边距 */
.custom-table >>> .el-table__cell { padding: 12px 0; }
/* 邮件编辑弹窗专用样式 */
.mail-edit-wrapper {
padding: 10px 20px;
}
.editor-container {
border: 1px solid #dcdfe6;
border-radius: 4px;
background: #fff;
}
/* 模拟富文本编辑器的工具栏感 (如果你没有引入富文本) */
.editor-container >>> .el-textarea__inner {
border: none;
padding: 15px;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", sans-serif;
line-height: 1.6;
}
/* 让 Dialog 的 footer 更靠右 */
.el-dialog__footer {
padding: 20px;
border-top: 1px solid #f0f2f5;
}
/* 列表内的主题预览小字(可选) */
.tpl-sub.mail-subject {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #8c939d;
}
</style>