任务工厂
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user