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; } } };