@@ -281,6 +338,7 @@
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';
+import PromotionFactoryTaskDialog from '@/components/page/components/autoPromotion/PromotionFactoryTaskDialog.vue';
import PromotionDetailDrawer from '@/components/page/components/autoPromotion/PromotionDetailDrawer.vue';
// 这里假设你已经定义了 API 地址
const API = {
@@ -294,7 +352,7 @@ const API = {
export default {
name: 'autoPromotion',
- components: { TemplateSelectorDialog, AutoPromotionWizard, CkeditorMail, PromotionDetailDrawer },
+ components: { TemplateSelectorDialog, AutoPromotionWizard, PromotionFactoryTaskDialog, CkeditorMail, PromotionDetailDrawer },
data() {
return {
handleRefreshList: [],
@@ -323,7 +381,7 @@ export default {
selectedStyleName: '',
// 列表数据
- query: { keyword: '', state: 'all', pageIndex: 1, pageSize: 15 },
+ query: { keyword: '', state: 'all', pageIndex: 1, pageSize: 10 },
list: [],
total: 0,
@@ -339,9 +397,19 @@ export default {
templateDialogInitialTemplateId: '',
togglingTaskId: '',
selectedFieldIds: [],
+ selectedCountryIds: [],
availableFields: [],
+ availableCountries: [],
fieldsLoading: false,
fieldsSaving: false,
+ showFactoryTaskDialog: false,
+ factoryDialogInitialJournalId: '',
+ factoryDialogInitialTask: null,
+ routePromotionFactoryId: '',
+ headerPromotionFactoryId: '',
+ factoryTaskOptions: [],
+ factoryTasksHeaderLoading: false,
+ createTaskLoading: false,
previewForm: {
id: '',
email: '',
@@ -351,7 +419,28 @@ export default {
}
};
},
- computed: {},
+ computed: {
+ /** 当前选中工厂任务是否运行中(与下拉 options 中 running 一致) */
+ headerFactoryTaskRunning() {
+ const id = String(this.headerPromotionFactoryId || '').trim();
+ if (!id || !this.factoryTaskOptions || !this.factoryTaskOptions.length) return null;
+ const opt = this.factoryTaskOptions.find((o) => String(o.value) === id);
+ if (opt && typeof opt.running === 'boolean') return opt.running;
+ return null;
+ }
+ },
+ watch: {
+ '$route.query.promotion_factory_id'(val) {
+ const s = String(val != null ? val : '').trim();
+ if (s === String(this.headerPromotionFactoryId || '').trim()) return;
+ this.headerPromotionFactoryId = s;
+ this.routePromotionFactoryId = s;
+ if (this.config.initialized && this.selectedJournalId) {
+ this.query.pageIndex = 1;
+ this.fetchList();
+ }
+ }
+ },
created() {
this.initPage();
},
@@ -370,6 +459,7 @@ export default {
closeAllDialogs() {
// 点击“去新增模板”后:关闭当前页面所有可能的弹窗
this.showWizardDialog = false;
+ this.showFactoryTaskDialog = false;
this.showTemplateDialog = false;
this.showPreviewDialog = false;
this.currentRow = null;
@@ -463,6 +553,12 @@ export default {
async initPage() {
this.hidePage = false;
var journal_id = (this.$route.query && this.$route.query.journal_id) || '';
+ var pfid =
+ (this.$route.query && this.$route.query.promotion_factory_id) ||
+ (this.$route.query && this.$route.query.taskId) ||
+ '';
+ this.routePromotionFactoryId = String(pfid || '');
+ this.headerPromotionFactoryId = this.routePromotionFactoryId;
this.selectedJournalId = String(journal_id);
this.loading = true;
try {
@@ -472,6 +568,7 @@ export default {
}
if (this.config.initialized) {
await this.fetchTemplates();
+ await this.fetchFactoryTasksForHeader();
await this.fetchList();
}
} finally {
@@ -540,6 +637,150 @@ export default {
}
},
+ /** 下拉项:场景类型文案(优先 scene,否则按 type 映射) */
+ getFactoryHeaderTaskTypeLabel(task) {
+ if (!task || typeof task !== 'object') return '';
+ const scene = String(task.scene || task.scene_name || '').trim();
+ if (scene) return scene;
+ const type = String(task.type != null ? task.type : '').trim();
+ if (type === '1') return this.$t('autoPromotion.factoryScenarioSolicit');
+ if (type === '2') return this.$t('autoPromotion.factoryScenarioPromoteCitation');
+ if (type === '3') return this.$t('autoPromotion.factoryScenarioGeneralThanks');
+ if (type === '4') return this.$t('autoPromotion.autoSolicit');
+ return String(task.task_name || task.name || '').trim();
+ },
+ formatFactoryHeaderTaskCreateTime(task) {
+ if (!task || typeof task !== 'object') return '';
+ const raw = task.ctime || task.create_time || task.created_at || task.time || '';
+ if (raw == null || String(raw).trim() === '') return '';
+ const n = Number(raw);
+ let dt = null;
+ if (!isNaN(n) && n > 0) {
+ dt = new Date(n > 1e12 ? n : n * 1000);
+ } else {
+ dt = new Date(String(raw));
+ }
+ if (!dt || isNaN(dt.getTime())) return String(raw).trim();
+ const pad = (v) => String(v).padStart(2, '0');
+ return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}:${pad(dt.getSeconds())}`;
+ },
+ isFactoryHeaderTaskRunning(task) {
+ if (!task || typeof task !== 'object') return false;
+ if (task.start_promotion != null && String(task.start_promotion).trim() !== '') {
+ return String(task.start_promotion) === '1';
+ }
+ if (task.state != null && String(task.state).trim() !== '') {
+ return String(task.state) === '1';
+ }
+ return false;
+ },
+ /** 下拉仅展示「类型 - 创建日期」,运行状态单独用 el-tag */
+ buildFactoryHeaderOptionMainLabel(task, pidFallback) {
+ const typePart = this.getFactoryHeaderTaskTypeLabel(task) || String(pidFallback || '').trim() || '—';
+ const datePart = this.formatFactoryHeaderTaskCreateTime(task);
+ return datePart ? `${typePart} - ${datePart}` : typePart;
+ },
+ replacePromotionFactoryIdInUrl(promotionFactoryId) {
+ try {
+ const url = new URL(window.location.href);
+ url.searchParams.set('promotion_factory_id', String(promotionFactoryId));
+ window.history.replaceState({}, document.title, url.toString());
+ } catch (e) {
+ console.error(e);
+ }
+ },
+ async fetchFactoryTasksForHeader() {
+ if (!this.selectedJournalId || !this.config.initialized) {
+ this.factoryTaskOptions = [];
+ return;
+ }
+ this.factoryTasksHeaderLoading = true;
+ try {
+ const userId = localStorage.getItem('U_id') || '';
+ const res = await this.$api.post('api/promotion_factory/getList', {
+ journal_id: String(this.selectedJournalId),
+ user_id: String(userId),
+ state: '-1'
+ });
+ const payload = (res && res.data) || {};
+ const list = this.findArray(payload) || this.findArray(res) || [];
+ let opts = (Array.isArray(list) ? list : []).map((task, idx) => {
+ const pid =
+ task && task.promotion_factory_id != null
+ ? String(task.promotion_factory_id)
+ : task && task.id != null
+ ? String(task.id)
+ : '';
+ if (!pid) return null;
+ const label = this.buildFactoryHeaderOptionMainLabel(task, pid);
+ const running = this.isFactoryHeaderTaskRunning(task);
+ return { value: pid, label, running };
+ }).filter(Boolean);
+
+ let cur = String(this.routePromotionFactoryId || this.headerPromotionFactoryId || '').trim();
+ const ids = new Set(opts.map((o) => o.value));
+ if (cur && !ids.has(cur)) {
+ opts = [{ value: cur, label: cur, running: false }].concat(opts);
+ }
+ this.factoryTaskOptions = opts;
+
+ if (!cur && opts.length) {
+ cur = opts[0].value;
+ }
+ if (cur) {
+ this.headerPromotionFactoryId = cur;
+ this.routePromotionFactoryId = cur;
+ const routePid = String((this.$route.query && this.$route.query.promotion_factory_id) || '').trim();
+ if (cur !== routePid) {
+ this.replacePromotionFactoryIdInUrl(cur);
+ }
+ } else {
+ this.headerPromotionFactoryId = '';
+ }
+ } catch (e) {
+ this.factoryTaskOptions = [];
+ } finally {
+ this.factoryTasksHeaderLoading = false;
+ }
+ },
+
+ onHeaderFactoryTaskChange(id) {
+ const next = String(id != null ? id : '').trim();
+ if (!next) return;
+ this.routePromotionFactoryId = next;
+ this.headerPromotionFactoryId = next;
+ this.query.pageIndex = 1;
+ this.replacePromotionFactoryIdInUrl(next);
+ this.fetchList();
+ },
+ async handleCreateEmailClientTask() {
+ const pid = String(this.headerPromotionFactoryId || this.routePromotionFactoryId || '').trim();
+ if (!pid) {
+ this.$message.warning(this.$t('autoPromotion.emailClientCreateTaskNeedFactory'));
+ return;
+ }
+ this.createTaskLoading = true;
+ try {
+ const res = await this.$api.post('api/email_client/createTask', { promotion_factory_id: pid });
+ if (res && Number(res.code) === 0) {
+ const taskId = String(res.task_id || (res.data && res.data.task_id) || '').trim();
+ if (taskId) {
+ // Fire-and-forget: prepare recipient list in background.
+ this.$api.post('api/email_client/prepareTask', { task_id: taskId }).catch(() => {});
+ }
+ this.$message.success(this.$t('autoPromotion.emailClientCreateTaskPreparingHint'));
+ this.query.pageIndex = 1;
+ this.fetchList();
+ } else {
+ this.$message.error((res && res.msg) || this.$t('autoPromotion.emailClientCreateTaskFailed'));
+ }
+ } catch (e) {
+ this.$message.error(this.$t('autoPromotion.emailClientCreateTaskFailed'));
+ } finally {
+ this.createTaskLoading = false;
+ }
+ },
+
// 打开向导弹窗:用于“修改期刊自动推广配置”
findArray(obj) {
if (Array.isArray(obj)) return obj;
@@ -552,10 +793,32 @@ export default {
if (values.length && Array.isArray(values[0])) return values[0];
return null;
},
+ parseCountryIdsFromPromotionPayload(selectedPayload) {
+ if (!selectedPayload || typeof selectedPayload !== 'object') return [];
+ const raw =
+ selectedPayload.country_fetch_ids != null
+ ? selectedPayload.country_fetch_ids
+ : selectedPayload.country_ids != null
+ ? selectedPayload.country_ids
+ : '';
+ if (typeof raw === 'string' && raw.trim()) {
+ return raw.split(',').map((s) => s.trim()).filter(Boolean).map(String);
+ }
+ return [];
+ },
+ journalPromotionFieldsPayload(journalId) {
+ return {
+ journal_id: String(journalId),
+ fetch_ids: (this.selectedFieldIds || []).join(','),
+ country_fetch_ids: (this.selectedCountryIds || []).join(',')
+ };
+ },
async loadPromotionFields(journalId) {
this.fieldsLoading = true;
this.availableFields = [];
+ this.availableCountries = [];
this.selectedFieldIds = [];
+ this.selectedCountryIds = [];
try {
const availableRes = await this.$api.post('api/email_client/getAvailableFields', { journal_id: String(journalId) });
const availablePayload = (availableRes && availableRes.data) || availableRes || {};
@@ -566,33 +829,22 @@ export default {
const label = item.field || item.title || item.name || item.label || String(id);
return { id: String(id), label };
});
+ this.availableCountries = this.availableFields.map((x) => ({ id: String(x.id), label: x.label }));
} catch (e) {
this.availableFields = [];
+ this.availableCountries = [];
}
- try {
- const selectedRes = await this.$api.post('api/email_client/getJournalPromotionFields', { journal_id: String(journalId) });
- const selectedPayload = (selectedRes && selectedRes.data) || selectedRes || {};
- let selectedArr = this.findArray(selectedPayload);
- if (selectedArr) {
- this.selectedFieldIds = selectedArr.map((it) => String(it.expert_fetch_id || it.fetch_id || it.id || it.field_id || it));
- } else if (typeof selectedPayload === 'string') {
- this.selectedFieldIds = selectedPayload.split(',').map((s) => s.trim()).filter(Boolean);
- } else if (typeof selectedPayload.fetch_ids === 'string') {
- this.selectedFieldIds = selectedPayload.fetch_ids.split(',').map((s) => s.trim()).filter(Boolean);
- }
- } catch (e) {
- this.selectedFieldIds = [];
- }
+ // 日志页不请求 getJournalPromotionFields(该接口在此场景不可用);已选字段/国家由向导内操作或他处回显
this.fieldsLoading = false;
},
async savePromotionFieldsNow() {
if (!this.selectedJournalId) return;
this.fieldsSaving = true;
try {
- await this.$api.post('api/email_client/setJournalPromotionFields', {
- journal_id: String(this.selectedJournalId),
- fetch_ids: (this.selectedFieldIds || []).join(',')
- });
+ await this.$api.post(
+ 'api/email_client/setJournalPromotionFields',
+ this.journalPromotionFieldsPayload(this.selectedJournalId)
+ );
this.$message.success(this.$t('autoPromotion.fieldsSaved'));
} catch (e) {
this.$message.error(this.$t('autoPromotion.saveFailed'));
@@ -600,6 +852,21 @@ export default {
this.fieldsSaving = false;
}
},
+ async savePromotionCountriesNow() {
+ if (!this.selectedJournalId) return;
+ this.fieldsSaving = true;
+ try {
+ await this.$api.post(
+ 'api/email_client/setJournalPromotionFields',
+ this.journalPromotionFieldsPayload(this.selectedJournalId)
+ );
+ this.$message.success(this.$t('autoPromotion.countriesSaved'));
+ } catch (e) {
+ this.$message.error(this.$t('autoPromotion.saveFailed'));
+ } finally {
+ this.fieldsSaving = false;
+ }
+ },
async openWizardDialog() {
this.wizardStep = 0;
if (this.config && this.config.start_date) {
@@ -610,6 +877,41 @@ export default {
}
this.showWizardDialog = true;
},
+ openFactoryTaskDialogFromLogs() {
+ this.factoryDialogInitialJournalId = this.selectedJournalId ? String(this.selectedJournalId) : '';
+ const routePid = String(this.routePromotionFactoryId || '').trim();
+ const first = this.list && this.list.length ? this.list[0] : null;
+ const matched = routePid
+ ? (this.list || []).find((row) => {
+ const pid = row && row.promotion_factory_id != null ? String(row.promotion_factory_id) : '';
+ const rid = row && row.id != null ? String(row.id) : '';
+ const tid = row && row.task_id != null ? String(row.task_id) : '';
+ return pid === routePid || rid === routePid || tid === routePid;
+ }) || first
+ : first;
+
+ let task = null;
+ if (matched) {
+ task = { ...matched };
+ if (!routePid) {
+ task.promotion_factory_id =
+ matched.promotion_factory_id != null
+ ? String(matched.promotion_factory_id)
+ : matched.id != null
+ ? String(matched.id)
+ : matched.task_id != null
+ ? String(matched.task_id)
+ : '';
+ }
+ }
+ // promotion_factory/getDetail 必须使用地址栏 promotion_factory_id,避免列表首行 id 与路由不一致
+ if (routePid) {
+ task = { ...(task || {}), promotion_factory_id: routePid };
+ }
+
+ this.factoryDialogInitialTask = task && Object.keys(task).length ? task : null;
+ this.showFactoryTaskDialog = true;
+ },
// 切换期刊逻辑
async handleJournalChange() {
@@ -702,13 +1004,14 @@ export default {
};
this.config.initialized = true;
this.showWizardDialog = false;
- this.fetchList();
await this.$api.post(API.saveConfig, payload);
- await this.$api.post('api/email_client/setJournalPromotionFields', {
- journal_id: String(this.selectedJournalId || ''),
- fetch_ids: (this.selectedFieldIds || []).join(',')
- });
+ await this.$api.post(
+ 'api/email_client/setJournalPromotionFields',
+ this.journalPromotionFieldsPayload(this.selectedJournalId || '')
+ );
this.$message.success(this.$t('autoPromotionLogs.configUpdated'));
+ await this.fetchFactoryTasksForHeader();
+ await this.fetchList();
} finally {
this.saving = false;
}
@@ -730,8 +1033,9 @@ export default {
try {
const params = {
journal_id: String(this.selectedJournalId || ''),
+ factory_id: String(this.routePromotionFactoryId || ''),
page: Number(this.query.pageIndex || 1),
- per_page: Number(this.query.pageSize || 15)
+ per_page: Number(this.query.pageSize || 10)
};
if (this.query.state !== 'all' && this.query.state !== '' && this.query.state != null) {
params.state = String(this.query.state);
@@ -743,8 +1047,15 @@ export default {
this.list = rawList.map((item, idx) => {
const runAt = item.run_at || item.run_time || item.plan_time || item.execute_time || item.send_date || '';
const state = String(item.state != null ? item.state : '');
+ const promotionFactoryId =
+ item.promotion_factory_id != null
+ ? item.promotion_factory_id
+ : item.id != null
+ ? item.id
+ : item.task_id;
return {
id: item.id || item.task_id || `task_${idx + 1}`,
+ promotion_factory_id: promotionFactoryId != null ? String(promotionFactoryId) : '',
task_id: String(item.task_id != null ? item.task_id : item.id || ''),
task_name: item.task_name || item.name || '',
scene: item.scene || '',
@@ -922,6 +1233,36 @@ export default {
justify-content: space-between;
align-items: center;
}
+.config-bar .left {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 4px 10px;
+}
+.config-bar .right {
+ flex-shrink: 0;
+ margin-left: 12px;
+}
+.header-factory-task-select {
+ width: min(380px, 46vw);
+ min-width: 200px;
+}
+.header-factory-status-tag {
+ margin-left: 8px;
+ vertical-align: middle;
+}
+.header-factory-status-tag .el-icon-circle-check {
+ margin-right: 4px;
+}
+.header-factory-status-dot {
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: #909399;
+ margin-right: 6px;
+ vertical-align: middle;
+}
/* 向导样式 */
.wizard-card {
@@ -1504,6 +1845,10 @@ export default {
border-radius: 6px;
transition: all 0.2s;
}
+
+.filter-actions {
+ margin-left: auto;
+}
/* 基础 Badge 样式 */
.status-badge {
display: inline-flex;
diff --git a/src/components/page/cenper_ter.vue b/src/components/page/cenper_ter.vue
index 482394c..574f5f6 100644
--- a/src/components/page/cenper_ter.vue
+++ b/src/components/page/cenper_ter.vue
@@ -198,8 +198,8 @@
{{coreForm.account}}
-
- {{coreForm.email}}
+
+
@@ -49,11 +52,14 @@
:selectedTemplateName="selectedTemplateName"
:selectedStyleName="selectedStyleName"
:availableFields="availableFields"
+ :availableCountries="availableCountries"
:fieldsLoading="fieldsLoading"
:fieldsSaving="fieldsSaving"
:selectedFieldIds.sync="selectedFieldIdsProxy"
+ :selectedCountryIds.sync="selectedCountryIdsProxy"
@open-template-selector="emitOpenTemplateSelector"
@confirm-fields="emitConfirmFields"
+ @confirm-countries="emitConfirmCountries"
@update:wizardStartDate="onWizardStartDateUpdate"
/>
-
+ > -->
@@ -62,6 +62,10 @@ export default {
default: ''
},
+ language: {
+ type: String,
+ default: 'en'
+ },
placeholder: {
type: String,
default: ''
@@ -87,13 +91,29 @@ export default {
expert_name: "John Doe", // 专家姓名
expert_field: "Biomedical Engineering", // 专家研究领域
representative_work_title: "Advanced Applications of AI in Medical Imaging", // 专家代表作标题
- // ai_content_analysis: "", // AI 约稿理由分析
+ ai_content_analysis: "【AI分析文章,一句话总结】", // AI solicitation rationale
+ ai_advised_topics: "Based on your research expertise, we would particularly welcome submissions on topics such as 【这里是AI针对学者领域给特定约稿主题】, or other closely related areas that align with your work.", // AI suggested directions
}
}
},
computed: {
+ isZhLanguage() {
+ return String(this.language || '').toLowerCase() === 'zh';
+ },
+ localizedAiMockData() {
+ if (this.isZhLanguage) {
+ return {
+ ai_content_analysis: '【AI分析这篇文章,一句话总结】。【我们希望也关注个领域】',
+ ai_advised_topics: '我们尤其关注如【方向/题目建议1】、【方向/题目建议2】以及【方向/题目建议3】等相关议题的研究进展。'
+ };
+ }
+ return {
+ ai_content_analysis: '【AI分析文章,一句话总结】',
+ ai_advised_topics: 'Based on your research expertise, we would particularly welcome submissions on topics such as 【这里是AI针对学者领域给特定约稿主题】, or other closely related areas that align with your work.'
+ };
+ },
resolvedPlaceholder() {
return this.placeholder || (this.$t && this.$t('tmrEmailEditor.placeholder')) || '请输入邮件内容...';
},
@@ -141,6 +161,7 @@ const deadlineStr = oneMonthLater.toISOString().split('T')[0];
const map = {
...this.variableMockData,
+ ...this.localizedAiMockData,
journal_abbr: journal_info.jabbr, // 期刊缩写
journal_name: journal_info.title,// 期刊全称
journal_url: journal_info.website, // 期刊官网链接
diff --git a/src/components/page/components/reviewArticle/author.vue b/src/components/page/components/reviewArticle/author.vue
index c990abd..070732e 100644
--- a/src/components/page/components/reviewArticle/author.vue
+++ b/src/components/page/components/reviewArticle/author.vue
@@ -17,9 +17,6 @@
:form="baseQuestionform"
@update="(e) => (questionform = e)"
>
-
-
-