任务工厂

This commit is contained in:
2026-04-24 11:05:22 +08:00
parent 632913aaad
commit fe4bd7c9b0
8 changed files with 1068 additions and 99 deletions

View File

@@ -16,20 +16,59 @@
<div class="left">
<span class="label">{{ $t('autoPromotion.journal') }} : </span>
{{ currentJournalName }}
<el-select
v-if="config.initialized && selectedJournalId"
v-model="headerPromotionFactoryId"
class="header-factory-task-select"
size="small"
filterable
:loading="factoryTasksHeaderLoading"
:placeholder="$t('autoPromotionLogs.factoryTaskSelectPlaceholder')"
@change="onHeaderFactoryTaskChange"
>
<el-option
v-for="opt in factoryTaskOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
<el-tag
v-if="config.initialized && selectedJournalId && headerFactoryTaskRunning !== null"
:type="headerFactoryTaskRunning ? 'success' : 'info'"
size="small"
effect="plain"
class="header-factory-status-tag"
>
<i v-if="headerFactoryTaskRunning" class="el-icon-circle-check"></i>
<span v-else class="header-factory-status-dot"></span>
{{ headerFactoryTaskRunning ? $t('autoPromotion.running') : $t('autoPromotion.stopped') }}
</el-tag>
<template v-if="config.initialized">
<el-tag type="success" size="small" effect="plain" style="margin-left: 10px">
<!-- <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-tag> -->
<el-button type="text" size="small" style="margin-left: 10px" @click="openFactoryTaskDialogFromLogs">
<i class="el-icon-edit"></i>
{{ config.initialized ? $t('autoPromotionLogs.editConfig') : $t('autoPromotionLogs.startConfig') }}
</el-button>
</template>
<!-- </template> -->
<el-tag v-else type="info" size="small" effect="plain" style="margin-left: 10px">
<!-- <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>
</el-tag> -->
</div>
<div v-if="config.initialized && selectedJournalId && headerPromotionFactoryId" class="right">
<el-button
type="primary"
size="small"
icon="el-icon-plus"
:loading="createTaskLoading"
@click="handleCreateEmailClientTask"
>
{{ $t('autoPromotion.emailClientCreateTaskBtn') }}
</el-button>
</div>
</div>
</el-card>
@@ -64,22 +103,24 @@
<div class="filter-header-row">
<div class="tmr-capsule-group">
<el-tabs v-model="query.state" type="card" @tab-click="handleTabClick">
<el-tab-pane :label="$t('autoPromotionLogs.statusAll')" name="all"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state0')" name="0"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state5')" name="5"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state1')" name="1"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state3')" name="3"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state2')" name="2"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state4')" name="4"></el-tab-pane>
</el-tabs>
</div>
<div class="tmr-capsule-group">
<el-tabs v-model="query.state" type="card" @tab-click="handleTabClick">
<el-tab-pane :label="$t('autoPromotionLogs.statusAll')" name="all"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state0')" name="0"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state5')" name="5"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state1')" name="1"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state3')" name="3"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state2')" name="2"></el-tab-pane>
<el-tab-pane :label="$t('autoPromotionLogs.state4')" name="4"></el-tab-pane>
</el-tabs>
</div>
<!-- <div class="filter-actions">
<el-button type="primary" icon="el-icon-search" @click="handleSearch">{{ $t('autoPromotionLogs.searchBtn') }}</el-button>
</div> -->
</div>
<div class="filter-actions">
<el-button type="primary" plain icon="el-icon-refresh" :loading="loading" @click="handleSearch">
{{ $t('autoPromotionLogs.logRefresh') }}
</el-button>
</div>
</div>
<el-table :data="list" border stripe size="small" class="custom-table exquisite-log-table">
<el-table-column
@@ -247,6 +288,7 @@
:initial-journal-id="factoryDialogInitialJournalId"
:initial-task="factoryDialogInitialTask"
@success="
fetchFactoryTasksForHeader();
fetchList();
fetchJournalDetail();
"
@@ -364,6 +406,10 @@ export default {
factoryDialogInitialJournalId: '',
factoryDialogInitialTask: null,
routePromotionFactoryId: '',
headerPromotionFactoryId: '',
factoryTaskOptions: [],
factoryTasksHeaderLoading: false,
createTaskLoading: false,
previewForm: {
id: '',
email: '',
@@ -373,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();
},
@@ -491,6 +558,7 @@ export default {
(this.$route.query && this.$route.query.taskId) ||
'';
this.routePromotionFactoryId = String(pfid || '');
this.headerPromotionFactoryId = this.routePromotionFactoryId;
this.selectedJournalId = String(journal_id);
this.loading = true;
try {
@@ -500,6 +568,7 @@ export default {
}
if (this.config.initialized) {
await this.fetchTemplates();
await this.fetchFactoryTasksForHeader();
await this.fetchList();
}
} finally {
@@ -568,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;
@@ -621,22 +834,7 @@ export default {
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);
}
this.selectedCountryIds = this.parseCountryIdsFromPromotionPayload(selectedPayload);
} catch (e) {
this.selectedFieldIds = [];
this.selectedCountryIds = [];
}
// 日志页不请求 getJournalPromotionFields该接口在此场景不可用已选字段/国家由向导内操作或他处回显
this.fieldsLoading = false;
},
async savePromotionFieldsNow() {
@@ -681,29 +879,37 @@ export default {
},
openFactoryTaskDialogFromLogs() {
this.factoryDialogInitialJournalId = this.selectedJournalId ? String(this.selectedJournalId) : '';
const targetTaskId = String(this.routePromotionFactoryId || '').trim();
const routePid = String(this.routePromotionFactoryId || '').trim();
const first = this.list && this.list.length ? this.list[0] : null;
const matched = targetTaskId
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 === targetTaskId || rid === targetTaskId || tid === targetTaskId;
return pid === routePid || rid === routePid || tid === routePid;
}) || first
: first;
this.factoryDialogInitialTask = matched
? {
...matched,
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)
: ''
}
: null;
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;
},
@@ -798,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',
this.journalPromotionFieldsPayload(this.selectedJournalId || '')
);
this.$message.success(this.$t('autoPromotionLogs.configUpdated'));
await this.fetchFactoryTasksForHeader();
await this.fetchList();
} finally {
this.saving = false;
}
@@ -826,6 +1033,7 @@ 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)
};
@@ -1025,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 {
@@ -1607,6 +1845,10 @@ export default {
border-radius: 6px;
transition: all 0.2s;
}
.filter-actions {
margin-left: auto;
}
/* 基础 Badge 样式 */
.status-badge {
display: inline-flex;