diff --git a/scripts/check-template-i18n-save-payload.mjs b/scripts/check-template-i18n-save-payload.mjs new file mode 100644 index 0000000..8c5cf1b --- /dev/null +++ b/scripts/check-template-i18n-save-payload.mjs @@ -0,0 +1,272 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import path from 'node:path' +import vm from 'node:vm' + +const ROOT = process.cwd() + +function cloneValue(value) { + return JSON.parse(JSON.stringify(value)) +} + +function loadApiPersistence({ getDecorationContent, saveTemplateContent, saveHeaderOrFooter }) { + const filename = path.resolve(ROOT, 'src/components/sp-web-decoration/adapters/apiPersistence.js') + let source = readFileSync(filename, 'utf8') + source = source + .replace(/import[\s\S]*?from '\.\.\/definitions\/documents\/global\.js'\n/, '') + .replace(/export default[\s\S]*$/, '') + .replace(/\bexport\s+(async function|function|const)\s+/g, '$1 ') + + const exports = {} + source += ` +exports.DSL_ROW_TYPE = DSL_ROW_TYPE; +exports.DEFAULT_COUNTRY_CODE = typeof DEFAULT_COUNTRY_CODE === 'undefined' ? undefined : DEFAULT_COUNTRY_CODE; +exports.buildPageContentPayload = typeof buildPageContentPayload === 'undefined' ? undefined : buildPageContentPayload; +exports.parsePageDslRows = typeof parsePageDslRows === 'undefined' ? undefined : parsePageDslRows; +exports.getCurrentCountryCode = typeof getCurrentCountryCode === 'undefined' ? undefined : getCurrentCountryCode; +exports.savePageDslToApi = savePageDslToApi; +exports.saveHeaderDslToApi = saveHeaderDslToApi; +exports.saveFooterDslToApi = saveFooterDslToApi; +` + + const context = { + exports, + console, + window: { + localStorage: { + getItem: (key) => (key === 'lang' ? 'en' : null) + } + }, + getDecorationContent, + saveTemplateContent, + saveHeaderOrFooter, + createHomeDsl: ({ pageId = '', scene = '1001' } = {}) => ({ + pageType: 'home', + pageId, + scene, + sections: {}, + order: [] + }), + createCustomDsl: ({ pageId = '', scene = '1001' } = {}) => ({ + pageType: 'custom', + pageId, + scene, + sections: {}, + order: [] + }), + createProductListDsl: ({ pageId = '', scene = '1001' } = {}) => ({ + pageType: 'product_list', + pageId, + scene, + sections: {}, + order: [] + }), + createGlobalDsl: () => ({ pageType: 'global', pageId: 'global', sections: {}, order: [] }), + createHeaderDsl: () => ({ pageType: 'header', pageId: 'header', sections: {}, order: [] }), + createFooterDsl: () => ({ pageType: 'footer', pageId: 'footer', sections: {}, order: [] }), + createGlobalSections: () => ({}), + ensureGlobalDslDefaults: (dsl) => dsl, + splitGlobalDsl: (dsl) => ({ headerDsl: dsl, footerDsl: dsl }), + serializeDsl: cloneValue, + deserializeDsl: cloneValue + } + vm.createContext(context) + vm.runInContext(source, context, { filename }) + return exports +} + +function loadRequestClient() { + const filename = path.resolve(ROOT, 'src/api/request/request.js') + let source = readFileSync(filename, 'utf8') + source = source + .replace(/import axios from 'axios'\n/, '') + .replace(/import \{ merge, bindMethods \} from '@\/utils'\n/, '') + .replace(/import \{ InterceptorManager \} from '\.\/interceptor'\n/, '') + .replace(/export \{ RequestClient \ }/, 'exports.RequestClient = RequestClient') + .replace(/export \{ RequestClient \}/, 'exports.RequestClient = RequestClient') + + const exports = {} + let lastRequest = null + const context = { + exports, + window: { + localStorage: { + getItem: (key) => (key === 'lang' ? 'en' : null) + } + }, + axios: { + create: () => ({ + request: async (config) => { + lastRequest = config + return { data: {}, config } + } + }) + }, + InterceptorManager: class { + constructor(instance) { + this.instance = instance + } + addRequestInterceptor() {} + addResponseInterceptor() {} + } + } + vm.createContext(context) + vm.runInContext(source, context, { filename }) + return { + RequestClient: exports.RequestClient, + getLastRequest: () => lastRequest + } +} + +const sampleDsl = { + pageType: 'home', + pageId: '140', + sections: { + hero: { + id: 'hero', + type: 'main-carousel', + settings: {}, + blocks: {}, + blockOrder: [] + } + }, + order: ['hero'] +} + +{ + const api = loadApiPersistence({ + getDecorationContent: async () => [], + saveTemplateContent: async () => {}, + saveHeaderOrFooter: async () => {} + }) + + assert.equal(typeof api.buildPageContentPayload, 'function') + const payload = api.buildPageContentPayload({ + normalizedDsl: sampleDsl, + contentRowId: 7084 + }) + assert.equal(payload.id, 7084) + assert.equal(payload.type, api.DSL_ROW_TYPE) + assert.equal(payload.pageType, 'home') + assert.equal(payload.pageId, '140') + assert.deepEqual(payload.order, ['hero']) + assert.ok(payload.sections.hero) + assert.equal(Object.hasOwn(payload, 'dsl'), false) +} + +{ + const api = loadApiPersistence({ + getDecorationContent: async () => [], + saveTemplateContent: async () => {}, + saveHeaderOrFooter: async () => {} + }) + + assert.equal(typeof api.parsePageDslRows, 'function') + const result = api.parsePageDslRows( + [ + { + id: 7084, + config: JSON.stringify([ + { + type: api.DSL_ROW_TYPE, + ...sampleDsl + } + ]) + } + ], + 140, + '1001', + 'home' + ) + assert.equal(result.contentRowId, 7084) + assert.equal(result.dsl.pageType, 'home') + assert.equal(result.dsl.pageId, '140') + assert.deepEqual(result.dsl.order, ['hero']) +} + +{ + const saves = [] + let defaultLoads = 0 + const api = loadApiPersistence({ + getDecorationContent: async (params) => { + if (params.country_code === 'zh-CN') { + defaultLoads += 1 + return defaultLoads === 1 + ? [] + : [ + { + id: 9001, + config: JSON.stringify([{ type: api.DSL_ROW_TYPE, ...sampleDsl }]) + } + ] + } + return [ + { + id: 9001, + config: JSON.stringify([{ type: api.DSL_ROW_TYPE, ...sampleDsl }]) + } + ] + }, + saveTemplateContent: async (body) => { + saves.push(body) + }, + saveHeaderOrFooter: async () => {} + }) + + const result = await api.savePageDslToApi({ + templateId: 140, + contentRowId: 0, + dsl: sampleDsl, + scene: '1001', + pageType: 'home' + }) + + assert.equal(saves.length, 2) + assert.equal(saves[0].country_code, 'zh-CN') + const defaultConfig = JSON.parse(saves[0].config) + assert.equal(defaultConfig[0].pageType, 'home') + assert.equal(Object.hasOwn(defaultConfig[0], 'dsl'), false) + const currentConfig = JSON.parse(saves[1].config) + assert.equal(currentConfig[0].id, 9001) + assert.equal(currentConfig[0].pageType, 'home') + assert.equal(result.contentRowId, 9001) +} + +{ + const saves = [] + const api = loadApiPersistence({ + getDecorationContent: async (params) => { + if (params.country_code === 'zh-CN') { + return { id: 1, config: JSON.stringify({ pageType: params.page_name }) } + } + return null + }, + saveTemplateContent: async () => {}, + saveHeaderOrFooter: async (body) => { + saves.push(body) + } + }) + + await api.saveHeaderDslToApi({ + dsl: { pageType: 'header', pageId: 'header', sections: {}, order: [] } + }) + assert.equal(saves.length, 1) + assert.equal(saves[0].page_name, 'header') + assert.equal(saves[0].country_code, undefined) + assert.equal(Array.isArray(JSON.parse(saves[0].config)), false) +} + +{ + const { RequestClient, getLastRequest } = loadRequestClient() + const client = new RequestClient() + await client.post('/example', { country_code: 'zh-CN', name: 'x' }) + assert.equal(getLastRequest().data.country_code, 'zh-CN') + + await client.post('/example', 'name=x&country_code=zh-CN') + assert.equal(getLastRequest().data, 'name=x&country_code=zh-CN') + + await client.post('/example', { name: 'x' }, { skipCountryCode: true }) + assert.equal(getLastRequest().data.country_code, undefined) +} + +console.log('Template i18n save payload checks passed') diff --git a/src/api/request/request.js b/src/api/request/request.js index 842c5fd..20dc2ef 100755 --- a/src/api/request/request.js +++ b/src/api/request/request.js @@ -63,12 +63,15 @@ class RequestClient { const countryCode = langMap[lang] if (config.data) { if (typeof config.data === 'string') { - config.data = - config.data + - (config.data ? '&' : '') + - 'country_code=' + - encodeURIComponent(countryCode) - } else { + const hasCountryCode = /(?:^|&)country_code=/.test(config.data) + if (!hasCountryCode) { + config.data = + config.data + + (config.data ? '&' : '') + + 'country_code=' + + encodeURIComponent(countryCode) + } + } else if (!config.data.country_code) { config.data.country_code = countryCode } } else { diff --git a/src/components/sp-web-decoration/adapters/apiPersistence.js b/src/components/sp-web-decoration/adapters/apiPersistence.js index a5b2e63..824e0ec 100644 --- a/src/components/sp-web-decoration/adapters/apiPersistence.js +++ b/src/components/sp-web-decoration/adapters/apiPersistence.js @@ -19,10 +19,22 @@ import { import { createGlobalSections } from '../definitions/documents/global.js' export const DSL_ROW_TYPE = 'ECX_SP_WEB_DECORATION_DSL_V1' +export const DEFAULT_COUNTRY_CODE = 'zh-CN' const LEGACY_GLOBAL_DECORATION_PAGE_NAME = 'ecx_sp_web_decoration_global' export const HEADER_DECORATION_PAGE_NAME = 'header' export const FOOTER_DECORATION_PAGE_NAME = 'footer' +export function getCurrentCountryCode() { + const lang = window.localStorage.getItem('lang') + const langMap = { + zhcn: 'zh-CN', + en: 'en-CN', + zhtw: 'zh-TW', + ar: 'ar-SA' + } + return langMap[lang] || DEFAULT_COUNTRY_CODE +} + function createPageFallback(templateId, scene, pageType = 'home') { if (pageType === 'custom') { return createCustomDsl({ pageId: String(templateId || ''), scene }) @@ -164,12 +176,12 @@ function unwrapPayload(res) { return current == null ? null : current } -function parsePageDslRows(payload, templateId, scene, pageType = 'home') { +export function parsePageDslRows(payload, templateId, scene, pageType = 'home') { const rows = Array.isArray(payload) ? payload : payload && typeof payload === 'object' - ? [payload] - : [] + ? [payload] + : [] if (!rows.length) return null for (const row of rows) { @@ -191,23 +203,22 @@ function parsePageDslRows(payload, templateId, scene, pageType = 'home') { continue } const target = Array.isArray(cfg) - ? cfg.find((item) => item && item.type === DSL_ROW_TYPE) - : cfg && cfg.type === DSL_ROW_TYPE && cfg.dsl - ? cfg - : cfg && cfg.pageType && cfg.sections - ? { dsl: cfg } - : null + ? cfg.find((item) => item && (item.type === DSL_ROW_TYPE || (item.pageType && item.sections))) + : cfg && (cfg.type === DSL_ROW_TYPE || (cfg.pageType && cfg.sections)) + ? cfg + : null + const targetDsl = target?.dsl || target console.log('[sp-web-decoration] page config parsed target', { templateId, rowId: row?.id, isArray: Array.isArray(cfg), hasTarget: Boolean(target), - targetDsl: target?.dsl + targetDsl }) - if (!target || !target.dsl) continue + if (!targetDsl) continue return { - dsl: normalizePageDsl(target.dsl, templateId, scene, pageType), - contentRowId: row.id || 0, + dsl: normalizePageDsl(targetDsl, templateId, scene, pageType), + contentRowId: row.id || target.id || 0, source: 'api' } } @@ -262,6 +273,54 @@ export async function loadPageDslFromApi({ templateId, scene, pageType = 'home' return fallback } +export function buildPageContentPayload({ normalizedDsl, contentRowId }) { + const payload = { + type: DSL_ROW_TYPE, + ...serializeDsl(normalizedDsl) + } + + if (contentRowId) { + payload.id = contentRowId + } + + return payload +} + +async function loadDefaultPageRow({ templateId, scene, pageType }) { + const raw = await getDecorationContent({ + page_name: 'page', + theme_pc_template_id: templateId, + country_code: DEFAULT_COUNTRY_CODE + }) + const list = unwrapPayload(raw) + return parsePageDslRows(list, templateId, scene, pageType) +} + +async function ensureDefaultPageRow({ templateId, contentRowId, normalizedDsl, scene, pageType }) { + if (getCurrentCountryCode() === DEFAULT_COUNTRY_CODE) { + return contentRowId || 0 + } + + const existing = await loadDefaultPageRow({ templateId, scene, pageType }) + if (existing?.contentRowId) { + return existing.contentRowId + } + + const payload = buildPageContentPayload({ + normalizedDsl, + contentRowId: 0 + }) + + await saveTemplateContent({ + theme_pc_template_id: templateId, + config: JSON.stringify([payload]), + country_code: DEFAULT_COUNTRY_CODE + }) + + const created = await loadDefaultPageRow({ templateId, scene, pageType }) + return created?.contentRowId || contentRowId || 0 +} + export async function savePageDslToApi({ templateId, contentRowId, @@ -270,10 +329,17 @@ export async function savePageDslToApi({ pageType = 'home' }) { const normalizedDsl = normalizePageDsl(dsl, templateId, scene, pageType) - const payload = { - type: DSL_ROW_TYPE, - dsl: serializeDsl(normalizedDsl) - } + const stableContentRowId = await ensureDefaultPageRow({ + templateId, + contentRowId, + normalizedDsl, + scene, + pageType + }) + const payload = buildPageContentPayload({ + normalizedDsl, + contentRowId: stableContentRowId || contentRowId + }) const config = JSON.stringify([payload]) const body = { theme_pc_template_id: templateId, @@ -286,9 +352,15 @@ export async function savePageDslToApi({ await saveTemplateContent(body) + const latest = await loadPageDslFromApi({ + templateId, + scene, + pageType + }) + return { dsl: normalizedDsl, - contentRowId: contentRowId || 0 + contentRowId: latest.contentRowId || stableContentRowId || contentRowId || 0 } } @@ -426,12 +498,34 @@ export async function loadDecorationContentFromApi({ templateId, scene, pageType } } -function saveScopedDslToApi({ pageName, dsl }) { - const payload = { +async function ensureDefaultScopedRow({ pageName, dsl }) { + if (getCurrentCountryCode() === DEFAULT_COUNTRY_CODE) { + return + } + + const raw = await getDecorationContent({ + page_name: pageName, + page_type: pageName, + country_code: DEFAULT_COUNTRY_CODE + }) + const payload = unwrapPayload(raw) + if (payload?.id || payload?.config) { + return + } + + await saveHeaderOrFooter({ + page_name: pageName, + config: JSON.stringify(serializeDsl(dsl)), + country_code: DEFAULT_COUNTRY_CODE + }) +} + +async function saveScopedDslToApi({ pageName, dsl }) { + await ensureDefaultScopedRow({ pageName, dsl }) + return saveHeaderOrFooter({ page_name: pageName, config: JSON.stringify(serializeDsl(dsl)) - } - return saveHeaderOrFooter(payload) + }) } export async function saveHeaderDslToApi({ dsl }) { diff --git a/src/views/decoration/web-template/decorationRouteMixin.js b/src/views/decoration/web-template/decorationRouteMixin.js index 975941a..9068436 100644 --- a/src/views/decoration/web-template/decorationRouteMixin.js +++ b/src/views/decoration/web-template/decorationRouteMixin.js @@ -80,7 +80,7 @@ export default { footerDsl }) if (this.templateId) { - await Promise.all([ + const [, pageResult] = await Promise.all([ saveHeaderDslToApi({ dsl: headerDsl }), savePageDslToApi({ templateId: this.templateId, @@ -90,6 +90,9 @@ export default { }), saveFooterDslToApi({ dsl: footerDsl }) ]) + if (pageResult?.contentRowId) { + this.contentRowId = pageResult.contentRowId + } this.$message.success(this.$t('732e5057.3b1083')) } else { const result = await saveHomeDecoration({