From 0d913e90a76d0c8d64b7c45a254a0dbe713fafd0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=A7=8B=E4=BA=8E=E5=88=9D=E8=A7=81?= <752204717@qq.com>
Date: Fri, 17 Apr 2026 13:35:56 +0800
Subject: [PATCH] =?UTF-8?q?=E5=9B=BD=E5=AE=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/index.js | 4 +-
src/components/common/langs/en.js | 10 +-
src/components/common/langs/zh.js | 6 +
src/components/page/autoPromotion.vue | 65 ++++++++--
src/components/page/autoPromotionLogs.vue | 65 ++++++++--
.../autoPromotion/AutoPromotionWizard.vue | 21 +++-
.../AutoPromotionWizardContent.vue | 118 +++++++++++++++++-
7 files changed, 266 insertions(+), 23 deletions(-)
diff --git a/src/api/index.js b/src/api/index.js
index 487fb66..525753d 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -19,8 +19,8 @@ const service = axios.create({
// baseURL: 'https://submission.tmrjournals.com/', //正式 记得切换
// baseURL: 'http://www.tougao.com/', //测试本地 记得切换
// baseURL: 'http://192.168.110.110/tougao/public/index.php/',
- baseURL: '/api', //本地
- // baseURL: '/', //正式
+ // baseURL: '/api', //本地
+ baseURL: '/', //正式
});
diff --git a/src/components/common/langs/en.js b/src/components/common/langs/en.js
index 2bd3758..e9a8701 100644
--- a/src/components/common/langs/en.js
+++ b/src/components/common/langs/en.js
@@ -330,8 +330,8 @@ const en = {
exportFailed: 'Export failed'
},
countryManagement: {
- title: 'Country maintenance',
- keywordPlaceholder: 'Chinese / English / code',
+ title: 'Country Management',
+ keywordPlaceholder: 'Chinese / English / Code',
partitionLabel: 'Partition',
partitionAll: 'All partitions',
partition1: 'Partition 1',
@@ -1098,14 +1098,20 @@ colTitle: 'Template title',
changeTemplate: 'Change Template',
selectPromotionFields: 'Select Promotion Fields',
choosePromotionFields: 'Choose Fields',
+ selectPromotionCountry: 'Select Country',
+ choosePromotionCountry: 'Choose Countries',
selectedCount: 'Selected {count}',
selectAll: 'Select All',
clearAll: 'Clear All',
selectPromotionFieldsTip: 'Multiple selection supported; leave empty for no field restriction.',
+ 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',
+ countrySearchPlaceholder: 'Search countries',
noFieldMatch: 'No matching fields',
+ noCountryMatch: 'No matching countries',
confirm: 'Confirm',
fieldsSaved: 'Promotion fields saved',
+ countriesSaved: 'Promotion countries saved',
confirmAndEnable: 'Confirm and Enable',
onlySaveConfig: 'Save configuration only',
enableNowNextDay: 'Enable auto promotion now (starts next day)'
diff --git a/src/components/common/langs/zh.js b/src/components/common/langs/zh.js
index 3d582e0..eb1dd68 100644
--- a/src/components/common/langs/zh.js
+++ b/src/components/common/langs/zh.js
@@ -1083,14 +1083,20 @@ const zh = {
changeTemplate: '更换模版',
selectPromotionFields: '选择推广领域',
choosePromotionFields: '选择领域',
+ selectPromotionCountry: '选择国家',
+ choosePromotionCountry: '选择国家',
selectedCount: '已选 {count} 项',
selectAll: '全选',
clearAll: '取消全选',
selectPromotionFieldsTip: '可多选;未选择则不限制推广领域。',
+ selectPromotionCountryTip: '可多选;未选择则不限制国家。与领域接口一致,后续可对接独立国家数据。',
fieldSearchPlaceholder: '搜索推广领域',
+ countrySearchPlaceholder: '搜索国家',
noFieldMatch: '没有匹配的领域',
+ noCountryMatch: '没有匹配的国家',
confirm: '确定',
fieldsSaved: '推广领域已保存',
+ countriesSaved: '推广国家已保存',
confirmAndEnable: '确认并开启',
onlySaveConfig: '仅保存配置',
enableNowNextDay: '立即激活自动推广(次日开始自动推广)'
diff --git a/src/components/page/autoPromotion.vue b/src/components/page/autoPromotion.vue
index ddf3b95..425231c 100644
--- a/src/components/page/autoPromotion.vue
+++ b/src/components/page/autoPromotion.vue
@@ -99,7 +99,9 @@
:config="wizardConfig"
:wizardStartDate.sync="wizardStartDate"
:selectedFieldIds.sync="selectedFieldIds"
+ :selectedCountryIds.sync="selectedCountryIds"
:availableFields="availableFields"
+ :availableCountries="availableCountries"
:fieldsLoading="fieldsLoading"
:fieldsSaving="fieldsSaving"
:currentJournalName="wizardJournal ? wizardJournal.title : ''"
@@ -110,6 +112,7 @@
:title="`${$t('autoPromotion.journalManage')}: ${wizardJournal ? wizardJournal.title : ''}`"
@open-template-selector="openTemplateSelector"
@confirm-fields="savePromotionFieldsNow"
+ @confirm-countries="savePromotionCountriesNow"
@cancel="showWizardDialog = false"
@confirm="saveWizardConfig"
/>
@@ -156,7 +159,9 @@ export default {
selectedTemplateName: '',
selectedStyleName: '',
selectedFieldIds: [],
+ selectedCountryIds: [],
availableFields: [],
+ availableCountries: [],
fieldsLoading: false,
fieldsSaving: false,
templateDialogInitialStyleId: '',
@@ -326,10 +331,33 @@ export default {
if (values.length && Array.isArray(values[0])) return values[0];
return null;
},
+ /** 与 getJournalPromotionFields 返回体对齐;后端未返回时为空数组 */
+ 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) });
@@ -344,8 +372,10 @@ 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 {
@@ -364,10 +394,12 @@ export default {
} else if (typeof selectedPayload.fetch_ids === 'string') {
this.selectedFieldIds = selectedPayload.fetch_ids.split(',').map((s) => s.trim()).filter(Boolean);
}
+ this.selectedCountryIds = this.parseCountryIdsFromPromotionPayload(selectedPayload);
console.log('[getJournalPromotionFields] parsed selected:', this.selectedFieldIds);
} catch (e) {
console.error('[getJournalPromotionFields] error:', e);
this.selectedFieldIds = [];
+ this.selectedCountryIds = [];
}
this.fieldsLoading = false;
@@ -376,10 +408,10 @@ export default {
if (!this.wizardJournal || !this.wizardJournal.journal_id) return;
this.fieldsSaving = true;
try {
- await this.$api.post('api/email_client/setJournalPromotionFields', {
- journal_id: String(this.wizardJournal.journal_id),
- fetch_ids: (this.selectedFieldIds || []).join(',')
- });
+ await this.$api.post(
+ 'api/email_client/setJournalPromotionFields',
+ this.journalPromotionFieldsPayload(this.wizardJournal.journal_id)
+ );
this.$message.success(this.$t('autoPromotion.fieldsSaved'));
} catch (e) {
this.$message.error(this.$t('autoPromotion.saveFailed'));
@@ -387,6 +419,21 @@ export default {
this.fieldsSaving = false;
}
},
+ async savePromotionCountriesNow() {
+ if (!this.wizardJournal || !this.wizardJournal.journal_id) return;
+ this.fieldsSaving = true;
+ try {
+ await this.$api.post(
+ 'api/email_client/setJournalPromotionFields',
+ this.journalPromotionFieldsPayload(this.wizardJournal.journal_id)
+ );
+ this.$message.success(this.$t('autoPromotion.countriesSaved'));
+ } catch (e) {
+ this.$message.error(this.$t('autoPromotion.saveFailed'));
+ } finally {
+ this.fieldsSaving = false;
+ }
+ },
async handleSolicitAction(journal) {
if (!this.isSolicitConfigured(journal)) {
@@ -437,7 +484,9 @@ export default {
this.selectedStyleName = s.styleName || '';
this.selectedTemplateThumbHtml = `
${s.html || ''}
`;
this.selectedFieldIds = [];
+ this.selectedCountryIds = [];
this.availableFields = [];
+ this.availableCountries = [];
if (journal && journal.journal_id) {
await this.loadPromotionFields(journal.journal_id);
}
@@ -473,10 +522,10 @@ export default {
start_promotion: this.wizardConfig.enabled ? '1' : '0',
user_id: userId
});
- await this.$api.post('api/email_client/setJournalPromotionFields', {
- journal_id: String(this.wizardJournal.journal_id),
- fetch_ids: (this.selectedFieldIds || []).join(',')
- });
+ await this.$api.post(
+ 'api/email_client/setJournalPromotionFields',
+ this.journalPromotionFieldsPayload(this.wizardJournal.journal_id)
+ );
await this.refreshJournalByDetail(this.wizardJournal);
this.$message.success(this.$t('autoPromotion.configSaved'));
this.showWizardDialog = false;
diff --git a/src/components/page/autoPromotionLogs.vue b/src/components/page/autoPromotionLogs.vue
index eb2f421..b420452 100644
--- a/src/components/page/autoPromotionLogs.vue
+++ b/src/components/page/autoPromotionLogs.vue
@@ -41,7 +41,9 @@
:config="config"
:wizardStartDate.sync="wizardStartDate"
:selectedFieldIds.sync="selectedFieldIds"
+ :selectedCountryIds.sync="selectedCountryIds"
:availableFields="availableFields"
+ :availableCountries="availableCountries"
:fieldsLoading="fieldsLoading"
:fieldsSaving="fieldsSaving"
:currentJournalName="currentJournalName"
@@ -52,6 +54,7 @@
:title="$t('autoPromotion.title')"
@open-template-selector="showTemplateDialog = true"
@confirm-fields="savePromotionFieldsNow"
+ @confirm-countries="savePromotionCountriesNow"
@confirm="completeInitialization"
/>
@@ -210,7 +213,9 @@
:config="config"
:wizardStartDate.sync="wizardStartDate"
:selectedFieldIds.sync="selectedFieldIds"
+ :selectedCountryIds.sync="selectedCountryIds"
:availableFields="availableFields"
+ :availableCountries="availableCountries"
:fieldsLoading="fieldsLoading"
:fieldsSaving="fieldsSaving"
:currentJournalName="currentJournalName"
@@ -221,6 +226,7 @@
:title="$t('autoPromotion.title')"
@open-template-selector="showTemplateDialog = true"
@confirm-fields="savePromotionFieldsNow"
+ @confirm-countries="savePromotionCountriesNow"
@cancel="showWizardDialog = false"
@confirm="completeInitialization"
/>
@@ -339,7 +345,9 @@ export default {
templateDialogInitialTemplateId: '',
togglingTaskId: '',
selectedFieldIds: [],
+ selectedCountryIds: [],
availableFields: [],
+ availableCountries: [],
fieldsLoading: false,
fieldsSaving: false,
previewForm: {
@@ -552,10 +560,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,8 +596,10 @@ 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) });
@@ -580,8 +612,10 @@ export default {
} 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 = [];
}
this.fieldsLoading = false;
},
@@ -589,10 +623,10 @@ export default {
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 +634,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) {
@@ -704,10 +753,10 @@ export default {
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'));
} finally {
this.saving = false;
diff --git a/src/components/page/components/autoPromotion/AutoPromotionWizard.vue b/src/components/page/components/autoPromotion/AutoPromotionWizard.vue
index a3f9e66..a8d9897 100644
--- a/src/components/page/components/autoPromotion/AutoPromotionWizard.vue
+++ b/src/components/page/components/autoPromotion/AutoPromotionWizard.vue
@@ -17,11 +17,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"
/>
@@ -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"
/>
@@ -159,7 +221,9 @@ export default {
data() {
return {
fieldSearchText: '',
- fieldDialogVisible: false
+ fieldDialogVisible: false,
+ countrySearchText: '',
+ countryDialogVisible: false
};
},
props: {
@@ -170,9 +234,11 @@ export default {
selectedTemplateName: { type: String, default: '' },
selectedStyleName: { type: String, default: '' },
availableFields: { type: Array, default: () => [] },
+ availableCountries: { type: Array, default: () => [] },
fieldsLoading: { type: Boolean, default: false },
fieldsSaving: { type: Boolean, default: false },
- selectedFieldIds: { type: Array, default: () => [] }
+ selectedFieldIds: { type: Array, default: () => [] },
+ selectedCountryIds: { type: Array, default: () => [] }
},
computed: {
hasSelectedTemplate() {
@@ -190,6 +256,14 @@ export default {
this.$emit('update:selectedFieldIds', val);
}
},
+ selectedCountryIdsProxy: {
+ get() {
+ return this.selectedCountryIds;
+ },
+ set(val) {
+ this.$emit('update:selectedCountryIds', val);
+ }
+ },
sortedFilteredFields() {
const kwRaw = String(this.fieldSearchText || '');
const normalize = (s) =>
@@ -211,12 +285,42 @@ export default {
});
return list.slice().sort((a, b) => String(a.label || '').localeCompare(String(b.label || '')));
},
+ sortedFilteredCountries() {
+ const kwRaw = String(this.countrySearchText || '');
+ const normalize = (s) =>
+ String(s || '')
+ .trim()
+ .replace(/\s+/g, ' ')
+ .toLowerCase();
+ const tokens = kwRaw
+ ? kwRaw
+ .split(/[\r\n,,;;]+/g)
+ .map((s) => normalize(s))
+ .filter(Boolean)
+ : [];
+ const list = (this.availableCountries || []).filter((item) => {
+ if (!tokens.length) return true;
+ const label = normalize(item.label || '');
+ return tokens.some((t) => t === label);
+ });
+ return list.slice().sort((a, b) => String(a.label || '').localeCompare(String(b.label || '')));
+ },
selectedFieldLabels() {
const map = {};
(this.availableFields || []).forEach((i) => { map[String(i.id)] = i.label; });
return (this.selectedFieldIdsProxy || [])
.map((id) => map[String(id)])
.filter(Boolean);
+ },
+ selectedCountryTagRows() {
+ const map = {};
+ (this.availableCountries || []).forEach((i) => {
+ map[String(i.id)] = i.label;
+ });
+ return (this.selectedCountryIdsProxy || []).map((id) => ({
+ id: String(id),
+ text: map[String(id)] != null && map[String(id)] !== '' ? map[String(id)] : String(id)
+ }));
}
},
methods: {
@@ -232,6 +336,16 @@ export default {
emitConfirmFields() {
this.$emit('confirm-fields');
this.fieldDialogVisible = false;
+ },
+ selectAllCountries() {
+ this.selectedCountryIdsProxy = (this.availableCountries || []).map((c) => String(c.id));
+ },
+ clearAllCountries() {
+ this.selectedCountryIdsProxy = [];
+ },
+ emitConfirmCountries() {
+ this.$emit('confirm-countries');
+ this.countryDialogVisible = false;
}
}
};