任务工厂列表版

This commit is contained in:
2026-04-22 11:42:47 +08:00
parent 0d913e90a7
commit 711a3fe2ec
8 changed files with 2485 additions and 603 deletions

View File

@@ -19,8 +19,8 @@ const service = axios.create({
// baseURL: 'https://submission.tmrjournals.com/', //正式 记得切换 // baseURL: 'https://submission.tmrjournals.com/', //正式 记得切换
// baseURL: 'http://www.tougao.com/', //测试本地 记得切换 // baseURL: 'http://www.tougao.com/', //测试本地 记得切换
// baseURL: 'http://192.168.110.110/tougao/public/index.php/', // baseURL: 'http://192.168.110.110/tougao/public/index.php/',
// baseURL: '/api', //本地 baseURL: '/api', //本地
baseURL: '/', //正式 // baseURL: '/', //正式
}); });

View File

@@ -1107,6 +1107,11 @@ colTitle: 'Template title',
selectPromotionCountryTip: 'Multiple selection supported; leave empty for no country restriction. Uses the same API as fields until a dedicated country list is available.', selectPromotionCountryTip: 'Multiple selection supported; leave empty for no country restriction. Uses the same API as fields until a dedicated country list is available.',
fieldSearchPlaceholder: 'Search promotion fields', fieldSearchPlaceholder: 'Search promotion fields',
countrySearchPlaceholder: 'Search countries', countrySearchPlaceholder: 'Search countries',
countryQuickZone1: 'Partition 1',
countryQuickZone2: 'Partition 2',
countryQuickZone3: 'Partition 3',
countryQuickChina: 'China',
countryQuickIndia: 'India',
noFieldMatch: 'No matching fields', noFieldMatch: 'No matching fields',
noCountryMatch: 'No matching countries', noCountryMatch: 'No matching countries',
confirm: 'Confirm', confirm: 'Confirm',
@@ -1114,7 +1119,68 @@ colTitle: 'Template title',
countriesSaved: 'Promotion countries saved', countriesSaved: 'Promotion countries saved',
confirmAndEnable: 'Confirm and Enable', confirmAndEnable: 'Confirm and Enable',
onlySaveConfig: 'Save configuration only', onlySaveConfig: 'Save configuration only',
enableNowNextDay: 'Enable auto promotion now (starts next day)' enableNowNextDay: 'Enable auto promotion now (starts next day)',
factoryCreateBtn: 'Create automated promotion task',
factoryDialogTitle: 'Create task',
factoryJournal: 'Journal',
factoryJournalPlaceholder: 'Select a journal',
factorySendSettings: 'Sending & scenario',
factoryEmails: 'Sender accounts',
factoryEmailsPlaceholder: 'Select one or more sender accounts',
factorySendCount: 'Send count',
factoryType: 'Scenario',
factoryTypeEditor: 'Editor',
factoryTypeArticle: 'Promote article',
factoryExpertType: 'Expert type',
factoryExpertTypePlaceholder: 'Optional; follow backend rules',
factorySubmit: 'Submit task',
factorySubmitSuccess: 'Factory task created',
factorySubmitFailed: 'Create failed, please try again later',
factoryNeedJournal: 'Please select a journal first',
factoryNeedTemplate: 'Please select email template and style',
factoryNeedEmails: 'Please select at least one sender account',
factoryNeedExpertType: 'Please select target person type',
factoryEmailsPickJournal: 'Select a journal to load sender accounts',
factoryNoAccounts: 'No mailbox accounts for this journal',
factoryAccountRemaining: 'Remaining today',
factorySendMaxFromApi: 'limit: up to {max} per day',
factorySendMaxFallback: 'using mailbox quota sum ~{max} (or default cap)',
factoryStepNav1Title: 'Journal',
factoryStepNav1Desc: 'Select a journal first.',
factoryStepNav2Title: 'Email template and style',
factoryStepNav2Desc: 'Choose template and style.',
factoryStepNav3Title: 'Sending and scenario',
factoryStepNav3Desc: 'Choose accounts, send count, and target type.',
factoryStepNav4Title: 'Promotion fields',
factoryStepNav4Desc: 'Select at least one promotion field.',
factoryStepNav5Title: 'Country',
factoryStepNav5Desc: 'Select at least one country or partition.',
factoryStepNav6Title: 'Confirm and enable',
factoryStepNav6Desc: 'Choose save only or enable next day.',
factoryPromotionFieldsBlockTip: 'Open “Choose fields” and tick at least one item; do not submit with none selected.',
factoryPromotionCountryBlockTip: 'Tick at least one partition or country; do not submit with none selected.',
factoryNeedPromotionFields: 'Select at least one promotion field before submitting.',
factoryNeedPromotionCountry: 'Select at least one partition or country before submitting.',
factoryQuotaLabel: 'Quota',
factoryClickSelectTemplate: 'Click to select email template',
factoryClickConfigureFields: 'Click to configure subject fields',
factoryBtnModify: 'Edit',
factoryBtnReset: 'Reset',
factoryBtnCancel: 'Cancel',
factoryBtnSubmit: 'Submit task',
factoryFillRequired: 'Please complete journal, template, and at least one sender account',
factoryExpertChief: 'Editor-in-Chief',
factoryExpertBoard: 'Editorial board',
factoryExpertYoungBoard: 'Young editorial board',
factoryExpertAuthor: 'Author',
factoryExpertDb: 'Expert database',
factoryScenario: 'Scenario',
factoryScenarioPlaceholder: 'Select scenario',
factoryScenarioSolicit: 'Invite Submission',
factoryScenarioPromoteCitation: 'Promote Citation',
factoryScenarioGeneralThanks: 'General Thanks',
noFactoryTask: 'No tasks',
factoryCreateNow: 'Create now'
} }
, ,
autoPromotionLogs: { autoPromotionLogs: {

View File

@@ -1092,6 +1092,11 @@ const zh = {
selectPromotionCountryTip: '可多选;未选择则不限制国家。与领域接口一致,后续可对接独立国家数据。', selectPromotionCountryTip: '可多选;未选择则不限制国家。与领域接口一致,后续可对接独立国家数据。',
fieldSearchPlaceholder: '搜索推广领域', fieldSearchPlaceholder: '搜索推广领域',
countrySearchPlaceholder: '搜索国家', countrySearchPlaceholder: '搜索国家',
countryQuickZone1: '1区',
countryQuickZone2: '2区',
countryQuickZone3: '3区',
countryQuickChina: 'China',
countryQuickIndia: 'India',
noFieldMatch: '没有匹配的领域', noFieldMatch: '没有匹配的领域',
noCountryMatch: '没有匹配的国家', noCountryMatch: '没有匹配的国家',
confirm: '确定', confirm: '确定',
@@ -1099,7 +1104,68 @@ const zh = {
countriesSaved: '推广国家已保存', countriesSaved: '推广国家已保存',
confirmAndEnable: '确认并开启', confirmAndEnable: '确认并开启',
onlySaveConfig: '仅保存配置', onlySaveConfig: '仅保存配置',
enableNowNextDay: '立即激活自动推广(次日开始自动推广)' enableNowNextDay: '立即激活自动推广(次日开始自动推广)',
factoryCreateBtn: '创建自动化推广任务',
factoryDialogTitle: '创建任务',
factoryJournal: '期刊',
factoryJournalPlaceholder: '请选择期刊',
factorySendSettings: '发送与场景',
factoryEmails: '发送邮箱',
factoryEmailsPlaceholder: '请选择发送账号(可多选)',
factorySendCount: '发送数量',
factoryType: '场景类型',
factoryTypeEditor: '编辑',
factoryTypeArticle: '推广文章',
factoryExpertType: '专家类型',
factoryExpertTypePlaceholder: '可选,按后端要求填写',
factorySubmit: '提交任务',
factorySubmitSuccess: '工厂任务已创建',
factorySubmitFailed: '创建失败,请稍后重试',
factoryNeedJournal: '请先选择期刊',
factoryNeedTemplate: '请先选择邮件模板与样式',
factoryNeedEmails: '请至少选择一个发送邮箱',
factoryNeedExpertType: '请选择目标人类型',
factoryEmailsPickJournal: '请先选择期刊以加载邮箱列表',
factoryNoAccounts: '该期刊下暂无可用邮箱账号',
factoryAccountRemaining: '今日剩余',
factorySendMaxFromApi: '接口限制:单日最多 {max} 封',
factorySendMaxFallback: '未返回接口上限,当前按邮箱额度合计约 {max} 封(或默认上限)',
factoryStepNav1Title: '期刊',
factoryStepNav1Desc: '先选期刊,未选不能提交。',
factoryStepNav2Title: '邮件模版与样式',
factoryStepNav2Desc: '选好邮件模板和样式。',
factoryStepNav3Title: '发送与场景',
factoryStepNav3Desc: '选账号,填发送数量和目标人类型。',
factoryStepNav4Title: '推广领域',
factoryStepNav4Desc: '至少选择一个推广领域。',
factoryStepNav5Title: '国家',
factoryStepNav5Desc: '至少选择一个国家或分区。',
factoryStepNav6Title: '确认并开启',
factoryStepNav6Desc: '选择仅保存或次日自动开启。',
factoryPromotionFieldsBlockTip: '请打开「选择领域」,在列表中至少勾选一项;不得留空提交。',
factoryPromotionCountryBlockTip: '请至少勾选一项分区或国家;不得留空提交。',
factoryNeedPromotionFields: '请至少选择一项推广领域后再提交。',
factoryNeedPromotionCountry: '请至少选择一项分区或国家后再提交。',
factoryQuotaLabel: '额度',
factoryClickSelectTemplate: '点击选择邮件模板',
factoryClickConfigureFields: '点击配置学科字段',
factoryBtnModify: '修改',
factoryBtnReset: '重置',
factoryBtnCancel: '取消',
factoryBtnSubmit: '立即提交任务',
factoryFillRequired: '请完善必填信息(期刊、模板、账号)',
factoryExpertChief: '主编',
factoryExpertBoard: '编委',
factoryExpertYoungBoard: '青年编委',
factoryExpertAuthor: '作者',
factoryExpertDb: 'expert库',
factoryScenario: '场景',
factoryScenarioPlaceholder: '请选择场景',
factoryScenarioSolicit: '约稿',
factoryScenarioPromoteCitation: '推广引用',
factoryScenarioGeneralThanks: '常规感谢',
noFactoryTask: '没有任务',
factoryCreateNow: '立即创建'
} }
, ,
autoPromotionLogs: { autoPromotionLogs: {

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@
<el-tag type="success" size="small" effect="plain" style="margin-left: 10px"> <el-tag type="success" size="small" effect="plain" style="margin-left: 10px">
<i class="el-icon-circle-check"></i> {{ $t('autoPromotionLogs.configured') }} <i class="el-icon-circle-check"></i> {{ $t('autoPromotionLogs.configured') }}
</el-tag> </el-tag>
<el-button type="text" size="small" style="margin-left: 10px" @click="openWizardDialog"> <el-button type="text" size="small" style="margin-left: 10px" @click="openFactoryTaskDialogFromLogs">
<i class="el-icon-edit"></i> <i class="el-icon-edit"></i>
{{ config.initialized ? $t('autoPromotionLogs.editConfig') : $t('autoPromotionLogs.startConfig') }} {{ config.initialized ? $t('autoPromotionLogs.editConfig') : $t('autoPromotionLogs.startConfig') }}
</el-button> </el-button>
@@ -242,6 +242,15 @@
@confirm="handleTemplateApply" @confirm="handleTemplateApply"
@close-all-dialogs="closeAllDialogs" @close-all-dialogs="closeAllDialogs"
/> />
<promotion-factory-task-dialog
:visible.sync="showFactoryTaskDialog"
:initial-journal-id="factoryDialogInitialJournalId"
:initial-task="factoryDialogInitialTask"
@success="
fetchList();
fetchJournalDetail();
"
/>
<el-dialog :title="$t('autoPromotionLogs.previewEditTitle')" :visible.sync="showPreviewDialog" width="1200px" top="5vh"> <el-dialog :title="$t('autoPromotionLogs.previewEditTitle')" :visible.sync="showPreviewDialog" width="1200px" top="5vh">
<div class="mail-edit-wrapper" v-if="previewForm"> <div class="mail-edit-wrapper" v-if="previewForm">
<el-form label-width="120px" size="small"> <el-form label-width="120px" size="small">
@@ -287,6 +296,7 @@
import CkeditorMail from '@/components/page/components/email/CkeditorMail.vue'; import CkeditorMail from '@/components/page/components/email/CkeditorMail.vue';
import TemplateSelectorDialog from '@/components/page/components/email/TemplateSelectorDialog.vue'; import TemplateSelectorDialog from '@/components/page/components/email/TemplateSelectorDialog.vue';
import AutoPromotionWizard from '@/components/page/components/autoPromotion/AutoPromotionWizard.vue'; import AutoPromotionWizard from '@/components/page/components/autoPromotion/AutoPromotionWizard.vue';
import PromotionFactoryTaskDialog from '@/components/page/components/autoPromotion/PromotionFactoryTaskDialog.vue';
import PromotionDetailDrawer from '@/components/page/components/autoPromotion/PromotionDetailDrawer.vue'; import PromotionDetailDrawer from '@/components/page/components/autoPromotion/PromotionDetailDrawer.vue';
// 这里假设你已经定义了 API 地址 // 这里假设你已经定义了 API 地址
const API = { const API = {
@@ -300,7 +310,7 @@ const API = {
export default { export default {
name: 'autoPromotion', name: 'autoPromotion',
components: { TemplateSelectorDialog, AutoPromotionWizard, CkeditorMail, PromotionDetailDrawer }, components: { TemplateSelectorDialog, AutoPromotionWizard, PromotionFactoryTaskDialog, CkeditorMail, PromotionDetailDrawer },
data() { data() {
return { return {
handleRefreshList: [], handleRefreshList: [],
@@ -350,6 +360,9 @@ export default {
availableCountries: [], availableCountries: [],
fieldsLoading: false, fieldsLoading: false,
fieldsSaving: false, fieldsSaving: false,
showFactoryTaskDialog: false,
factoryDialogInitialJournalId: '',
factoryDialogInitialTask: null,
previewForm: { previewForm: {
id: '', id: '',
email: '', email: '',
@@ -378,6 +391,7 @@ export default {
closeAllDialogs() { closeAllDialogs() {
// 点击“去新增模板”后:关闭当前页面所有可能的弹窗 // 点击“去新增模板”后:关闭当前页面所有可能的弹窗
this.showWizardDialog = false; this.showWizardDialog = false;
this.showFactoryTaskDialog = false;
this.showTemplateDialog = false; this.showTemplateDialog = false;
this.showPreviewDialog = false; this.showPreviewDialog = false;
this.currentRow = null; this.currentRow = null;
@@ -659,6 +673,11 @@ export default {
} }
this.showWizardDialog = true; this.showWizardDialog = true;
}, },
openFactoryTaskDialogFromLogs() {
this.factoryDialogInitialJournalId = this.selectedJournalId ? String(this.selectedJournalId) : '';
this.factoryDialogInitialTask = this.list && this.list.length ? { ...this.list[0] } : null;
this.showFactoryTaskDialog = true;
},
// 切换期刊逻辑 // 切换期刊逻辑
async handleJournalChange() { async handleJournalChange() {

View File

@@ -101,13 +101,13 @@
<el-divider></el-divider> <el-divider></el-divider>
<!-- <section class="form-section"> <section class="form-section">
<h4 class="section-title"> <h4 class="section-title">
<i class="el-icon-location-outline"></i> 3. {{ $t('autoPromotion.selectPromotionCountry') }} <i class="el-icon-location-outline"></i> 3. {{ $t('autoPromotion.selectPromotionCountry') }}
<span class="selected-count"> <!-- <span class="selected-count">
{{ $t('autoPromotion.selectedCount', { count: selectedCountryIdsProxy.length }) }} {{ $t('autoPromotion.selectedCount', { count: selectedCountryIdsProxy.length }) }}
</span> </span> -->
<el-button <!-- <el-button
size="small" size="small"
type="primary" type="primary"
plain plain
@@ -116,21 +116,31 @@
@click="countryDialogVisible = true" @click="countryDialogVisible = true"
> >
{{ $t('autoPromotion.choosePromotionCountry') }} {{ $t('autoPromotion.choosePromotionCountry') }}
</el-button> </el-button> -->
</h4> </h4>
<div class="status-confirm-box"> <div class="status-confirm-box">
<div v-if="selectedCountryTagRows.length" class="selected-tags"> <div class="country-quick-checks">
<el-tag v-for="row in selectedCountryTagRows" :key="'c-' + row.id" size="mini" type="info" effect="plain">{{ row.text }}</el-tag> <div class="field-tip" style="margin-bottom: 10px;">{{ $t('autoPromotion.selectPromotionCountryTip') }}</div>
<el-checkbox-group v-model="selectedCountryIdsProxy" size="small">
<el-checkbox label="Partition1">{{ $t('autoPromotion.countryQuickZone1') }}</el-checkbox>
<el-checkbox label="Partition2">{{ $t('autoPromotion.countryQuickZone2') }}</el-checkbox>
<el-checkbox label="Partition3">{{ $t('autoPromotion.countryQuickZone3') }}</el-checkbox>
<el-checkbox label="country_china" value="239">{{ $t('autoPromotion.countryQuickChina') }}</el-checkbox>
<el-checkbox label="country_india" value="228">{{ $t('autoPromotion.countryQuickIndia') }}</el-checkbox>
</el-checkbox-group>
</div> </div>
<div class="field-tip">{{ $t('autoPromotion.selectPromotionCountryTip') }}</div> <!-- <div v-if="selectedCountryTagRows.length" class="selected-tags">
<el-tag v-for="row in selectedCountryTagRows" :key="'c-' + row.id" size="mini" type="info" effect="plain">{{ row.text }}</el-tag>
</div> -->
</div> </div>
</section> </section>
<el-divider></el-divider> --> <el-divider></el-divider>
<section class="form-section"> <section class="form-section">
<h4 class="section-title"> <h4 class="section-title">
<i class="el-icon-finished"></i> 3. {{ $t('autoPromotion.confirmAndEnable') }} <i class="el-icon-finished"></i> 4. {{ $t('autoPromotion.confirmAndEnable') }}
</h4> </h4>
<div class="status-confirm-box"> <div class="status-confirm-box">
@@ -317,10 +327,24 @@ export default {
(this.availableCountries || []).forEach((i) => { (this.availableCountries || []).forEach((i) => {
map[String(i.id)] = i.label; map[String(i.id)] = i.label;
}); });
return (this.selectedCountryIdsProxy || []).map((id) => ({ const quick = {
id: String(id), zone_1: this.$t('autoPromotion.countryQuickZone1'),
text: map[String(id)] != null && map[String(id)] !== '' ? map[String(id)] : String(id) zone_2: this.$t('autoPromotion.countryQuickZone2'),
})); zone_3: this.$t('autoPromotion.countryQuickZone3'),
country_china: this.$t('autoPromotion.countryQuickChina'),
country_india: this.$t('autoPromotion.countryQuickIndia')
};
return (this.selectedCountryIdsProxy || []).map((id) => {
const sid = String(id);
const fromList = map[sid];
const text =
fromList != null && fromList !== ''
? fromList
: quick[sid] != null
? quick[sid]
: sid;
return { id: sid, text };
});
} }
}, },
methods: { methods: {
@@ -694,5 +718,17 @@ export default {
font-size: 12px; font-size: 12px;
color: #909399; color: #909399;
} }
.country-quick-checks {
margin-bottom: 12px;
}
.country-quick-checks >>> .el-checkbox-group {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px 20px;
}
.country-quick-checks >>> .el-checkbox {
margin-right: 0;
}
</style> </style>

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@
:close-on-click-modal="false" :close-on-click-modal="false"
width="90%" width="90%"
top="5vh" top="5vh"
append-to-body
destroy-on-close destroy-on-close
:before-close="handleClose" :before-close="handleClose"
custom-class="template-modal" custom-class="template-modal"