From 186e19d652c0641acd2a0eedb867a2ed8eb567e5 Mon Sep 17 00:00:00 2001 From: chilingling Date: Thu, 5 Jun 2025 11:09:07 +0800 Subject: [PATCH 1/3] fix: generate code should convert quote on vue template --- .../generator/vue/sfc/generateAttribute.js | 59 +++++++++++++---- .../sfc/accessor/expected/Accessor.vue | 2 +- .../test/testcases/sfc/accessor/schema.json | 2 +- .../sfc/templateQuote/components-map.json | 9 +++ .../templateQuote/expected/templateQuote.vue | 66 +++++++++++++++++++ .../sfc/templateQuote/page.schema.json | 46 +++++++++++++ .../sfc/templateQuote/templateQuote.test.js | 33 ++++++++++ 7 files changed, 202 insertions(+), 15 deletions(-) create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/components-map.json create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/page.schema.json create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/templateQuote.test.js diff --git a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js index 901617be5d..e155d33161 100644 --- a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js +++ b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js @@ -70,7 +70,7 @@ export const checkHasSpecialType = (obj) => { return false } -const handleJSExpressionBinding = (key, value, isJSX) => { +const handleJSExpressionBinding = (key, value, isJSX, globalHooks) => { const expressValue = value.value.replace(isJSX ? thisRegexp : thisPropsBindRe, '') if (isJSX) { @@ -85,7 +85,19 @@ const handleJSExpressionBinding = (key, value, isJSX) => { } // expression 使用 v-bind 绑定 - return `:${key}="${expressValue}"` + if (expressValue.includes('"')) { + let stateKey = `${key}_${randomString()}` + let addSuccess = globalHooks.addState(stateKey, `${stateKey}:${expressValue}`) + + while (!addSuccess) { + stateKey = `${key}_${randomString()}` + addSuccess = globalHooks.addState(stateKey, `${stateKey}:${expressValue}`) + } + + return `:${key}="state.${stateKey}"` + } else { + return `:${key}="${expressValue}"` + } } const handleBindI18n = (key, value, isJSX) => { @@ -182,7 +194,19 @@ export const handleLoopAttrHook = (schemaData = {}, globalHooks, config) => { const iterVar = [...loopArgs] if (!isJSX) { - attributes.push(`v-for="(${iterVar.join(',')}) in ${source}"`) + if (source.includes('"')) { + let stateKey = `loop_${randomString()}` + let addSuccess = globalHooks.addState(stateKey, `${stateKey}:${source}`) + + while (!addSuccess) { + stateKey = `loop_${randomString()}` + addSuccess = globalHooks.addState(stateKey, `${stateKey}:${source}`) + } + + attributes.push(`v-for="(${iterVar.join(',')}) in state.${stateKey}"`) + } else { + attributes.push(`v-for="(${iterVar.join(',')}) in ${source}"`) + } return } @@ -352,7 +376,7 @@ export const handleExpressionAttrHook = (schemaData, globalHooks, config) => { Object.entries(props).forEach(([key, value]) => { if (value?.type === JS_EXPRESSION && !isOn(key)) { specialTypeHandler[JS_RESOURCE](value, globalHooks, config) - attributes.push(handleJSExpressionBinding(key, value, isJSX)) + attributes.push(handleJSExpressionBinding(key, value, isJSX, globalHooks)) delete props[key] } @@ -384,7 +408,7 @@ export const handleJSFunctionAttrHook = (schemaData, globalHooks, config) => { functionName = value.value } - attributes.push(handleJSExpressionBinding(key, { value: functionName }, isJSX)) + attributes.push(handleJSExpressionBinding(key, { value: functionName }, isJSX, globalHooks)) delete props[key] } @@ -451,11 +475,15 @@ const genStateAccessor = (value, globalHooks) => { } } -const transformObjValue = (renderKey, value, globalHooks, config, transformObjType) => { +const transformObjValue = (renderKey, value, globalHooks, config, transformObjType, shouldConvertQuote = false) => { const result = { shouldBindToState: false, res: null } if (typeof value === 'string') { - result.res = `${renderKey}"${value.replaceAll("'", "\\'").replaceAll(/"/g, "'")}"` + if (shouldConvertQuote) { + result.res = `${renderKey}'${value.replaceAll(/"/g, "'").replaceAll(/'/g, "\\'")}'` + } else { + result.res = `${renderKey}"${value.replaceAll("'", "\\'").replaceAll(/"/g, "'")}"` + } return result } @@ -468,7 +496,11 @@ const transformObjValue = (renderKey, value, globalHooks, config, transformObjTy if (specialTypeHandler[value?.type]) { const specialVal = specialTypeHandler[value.type](value, globalHooks, config)?.value || '' - result.res = `${renderKey}${specialVal}` + if (shouldConvertQuote) { + result.res = `${renderKey}${specialVal.replaceAll(/"/g, "'")}` + } else { + result.res = `${renderKey}${specialVal}` + } if (specialTypes.includes(value.type)) { result.shouldBindToState = true @@ -503,7 +535,7 @@ const transformObjValue = (renderKey, value, globalHooks, config, transformObjTy } const normalKeyRegexp = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/ -export const transformObjType = (obj, globalHooks, config) => { +export const transformObjType = (obj, globalHooks, config, shouldConvertQuote = false) => { if (!obj || typeof obj !== 'object') { return { res: obj @@ -527,7 +559,8 @@ export const transformObjType = (obj, globalHooks, config) => { value, globalHooks, config, - transformObjType + transformObjType, + shouldConvertQuote ) if (tmpShouldBindToState) { @@ -541,7 +574,7 @@ export const transformObjType = (obj, globalHooks, config) => { // 复杂的 object 类型,需要递归处理 const { res: tempRes, shouldBindToState: tempShouldBindToState } = - transformObjType(value, globalHooks, config) || {} + transformObjType(value, globalHooks, config, shouldConvertQuote) || {} resStr.push(`${renderKey}${tempRes}`) @@ -570,7 +603,7 @@ export const handleObjBindAttrHook = (schemaData, globalHooks, config) => { return } - const { res, shouldBindToState } = transformObjType(value, globalHooks, config) + const { res, shouldBindToState } = transformObjType(value, globalHooks, config, true) if (shouldBindToState && !isJSX) { let stateKey = key @@ -583,7 +616,7 @@ export const handleObjBindAttrHook = (schemaData, globalHooks, config) => { attributes.push(`:${key}="state.${stateKey}"`) } else { - attributes.push(isJSX ? `${key}={${res}}` : `:${key}="${res.replaceAll(/"/g, "'")}"`) + attributes.push(isJSX ? `${key}={${res}}` : `:${key}="${res}"`) } delete props[key] diff --git a/packages/vue-generator/test/testcases/sfc/accessor/expected/Accessor.vue b/packages/vue-generator/test/testcases/sfc/accessor/expected/Accessor.vue index c515a9711d..b524540906 100644 --- a/packages/vue-generator/test/testcases/sfc/accessor/expected/Accessor.vue +++ b/packages/vue-generator/test/testcases/sfc/accessor/expected/Accessor.vue @@ -26,7 +26,7 @@ const state = vue.reactive({ nullValue: null, numberValue: 0, emptyStr: '', - strVal: 'i am str.', + strVal: "i am 'str'.", trueVal: true, falseVal: false, arrVal: [1, '2', { aaa: 'aaa' }, [3, 4], true, false], diff --git a/packages/vue-generator/test/testcases/sfc/accessor/schema.json b/packages/vue-generator/test/testcases/sfc/accessor/schema.json index 204121eec1..0935a984a8 100644 --- a/packages/vue-generator/test/testcases/sfc/accessor/schema.json +++ b/packages/vue-generator/test/testcases/sfc/accessor/schema.json @@ -44,7 +44,7 @@ } }, "strVal": { - "defaultValue": "i am str.", + "defaultValue": "i am 'str'.", "accessor": { "getter": { "type": "JSFunction", diff --git a/packages/vue-generator/test/testcases/sfc/templateQuote/components-map.json b/packages/vue-generator/test/testcases/sfc/templateQuote/components-map.json new file mode 100644 index 0000000000..b84fe3b474 --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/templateQuote/components-map.json @@ -0,0 +1,9 @@ +[ + { + "componentName": "TinyButton", + "exportName": "Button", + "package": "@opentiny/vue", + "version": "^3.10.0", + "destructuring": true + } +] diff --git a/packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue new file mode 100644 index 0000000000..a660e68df5 --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue @@ -0,0 +1,66 @@ + + + + diff --git a/packages/vue-generator/test/testcases/sfc/templateQuote/page.schema.json b/packages/vue-generator/test/testcases/sfc/templateQuote/page.schema.json new file mode 100644 index 0000000000..534af857a8 --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/templateQuote/page.schema.json @@ -0,0 +1,46 @@ +{ + "state": { + "customAttrTest": { + "value": [ + { + "defaultValue": "{\"class\": \"test-class\", \"id\": \"test-id\"}" + } + ] + } + }, + "methods": {}, + "componentName": "Page", + "css": "", + "props": {}, + "lifeCycles": {}, + "children": [ + { + "componentName": "TinyButton", + "props": { + "type": "primary", + "text": "test", + "subStr": "pri\"ma\"ry'subStr'", + "customAttrTest": { + "value": [ + { + "defaultValue": "{\"class\": \"test-class\", \"id\": \"test-id\", \"class2\": \"te'st'-class2\"}", + "subStr": "test-'cl'ass2" + } + ] + }, + "customExpressionTest": { + "type": "JSExpression", + "value": "{\n \"value\": [\n {\n \"defaultValue\": \"{\\\"class\\\": \\\"test-class\\\", \\\"id\\\": \\\"test-id\\\"}\"\n }\n ]\n}" + } + }, + "loopArgs": ["item", "index"], + "loop": { + "type": "JSExpression", + "value": "[\n {\n type: 'primary'\n, subStr: \"primary'subStr'\"\n },\n {\n type: \"\"\n },\n {\n type: \"info\"\n },\n {\n type: \"success\"\n },\n {\n type: \"warning\"\n },\n {\n type: \"danger\"\n }\n]" + }, + "id": "63623253", + "children": [] + } + ], + "fileName": "testTemplateQuote" +} diff --git a/packages/vue-generator/test/testcases/sfc/templateQuote/templateQuote.test.js b/packages/vue-generator/test/testcases/sfc/templateQuote/templateQuote.test.js new file mode 100644 index 0000000000..4f6d0672e4 --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/templateQuote/templateQuote.test.js @@ -0,0 +1,33 @@ +import { expect, test, beforeEach, afterEach, vi } from 'vitest' +import { genSFCWithDefaultPlugin } from '@/generator/vue/sfc' +import pageSchema from './page.schema.json' +import { formatCode } from '@/utils/formatCode' + +let count = 0 +const mockValue = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] + +beforeEach(() => { + // 伪随机数,保证每次快照都一致 + vi.spyOn(global.Math, 'random').mockImplementation(() => { + const res = mockValue[count] + + count++ + if (count > 10) { + count = 0 + } + + return res + }) +}) + +afterEach(() => { + vi.spyOn(global.Math, 'random').mockRestore() +}) + +test('should generate template quote correctly', async () => { + const res = genSFCWithDefaultPlugin(pageSchema, []) + + const formattedCode = formatCode(res, 'vue') + + await expect(formattedCode).toMatchFileSnapshot('./expected/templateQuote.vue') +}) From 3b78770761adfe71ce923a38a3ba25d50d752cd7 Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 16 Jun 2025 14:23:27 +0800 Subject: [PATCH 2/3] fix: only save to state when contain both double and single quote --- .../src/generator/vue/sfc/generateAttribute.js | 10 +++++----- .../sfc/templateQuote/expected/templateQuote.vue | 15 +++++++-------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js index e155d33161..46a0919a4d 100644 --- a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js +++ b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js @@ -85,7 +85,7 @@ const handleJSExpressionBinding = (key, value, isJSX, globalHooks) => { } // expression 使用 v-bind 绑定 - if (expressValue.includes('"')) { + if (expressValue.includes('"') && expressValue.includes("'")) { let stateKey = `${key}_${randomString()}` let addSuccess = globalHooks.addState(stateKey, `${stateKey}:${expressValue}`) @@ -96,7 +96,7 @@ const handleJSExpressionBinding = (key, value, isJSX, globalHooks) => { return `:${key}="state.${stateKey}"` } else { - return `:${key}="${expressValue}"` + return `:${key}="${expressValue.replaceAll(/"/g, "'")}"` } } @@ -188,13 +188,13 @@ export const handleLoopAttrHook = (schemaData = {}, globalHooks, config) => { if (loop?.value && loop?.type) { source = loop.value.replace(isJSX ? thisRegexp : thisPropsBindRe, '') } else { - source = JSON.stringify(loop).replaceAll("'", "\\'").replaceAll(/"/g, "'") + source = JSON.stringify(loop) } const iterVar = [...loopArgs] if (!isJSX) { - if (source.includes('"')) { + if (source.includes('"') && source.includes("'")) { let stateKey = `loop_${randomString()}` let addSuccess = globalHooks.addState(stateKey, `${stateKey}:${source}`) @@ -205,7 +205,7 @@ export const handleLoopAttrHook = (schemaData = {}, globalHooks, config) => { attributes.push(`v-for="(${iterVar.join(',')}) in state.${stateKey}"`) } else { - attributes.push(`v-for="(${iterVar.join(',')}) in ${source}"`) + attributes.push(`v-for="(${iterVar.join(',')}) in ${source.replaceAll(/"/g, "'")}"`) } return diff --git a/packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue index a660e68df5..27197c4583 100644 --- a/packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue +++ b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue @@ -5,7 +5,13 @@ type="primary" text="test" subStr="pri'ma'ry'subStr'" - :customExpressionTest="state.customExpressionTest_vBHN" + :customExpressionTest="{ + value: [ + { + defaultValue: '{\'class\': \'test-class\', \'id\': \'test-id\'}' + } + ] + }" :customAttrTest="{ value: [ { @@ -52,13 +58,6 @@ const state = vue.reactive({ type: 'danger' } ], - customExpressionTest_vBHN: { - value: [ - { - defaultValue: '{"class": "test-class", "id": "test-id"}' - } - ] - }, customAttrTest: { value: [{ defaultValue: "{'class': 'test-class', 'id': 'test-id'}" }] } }) wrap({ state }) From 8b4b104928b21c3ed93b791c053810cc96da60de Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 23 Mar 2026 11:13:42 +0800 Subject: [PATCH 3/3] feat(vue-generator): optimize quote escaping in template attributes and unique state key generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 引号转义优化:将 handleExpressionAttrHook 和 handlePrimitiveAttributeHook 中的引号处理逻辑由替换为单引号改为替换为 HTML 实体 "。这确保了在生成的 Vue 模板中,属性值内的双引号能够被正确转义,避免模板解析错误。 2. 增强鲁棒性:在 generateAttribute.js 的循环中增加了 retryCount 限制(100次),防止在生成唯一的 stateKey 时出现潜在的死循环。 --- .../generator/vue/sfc/generateAttribute.js | 71 ++++++------------- .../sfc/case01/expected/FormTable.vue | 2 +- .../sfc/templateQuote/expected/jsxQuote.vue | 27 +++++++ .../sfc/templateQuote/expected/multiline.vue | 28 ++++++++ .../templateQuote/expected/primitiveQuote.vue | 29 ++++++++ .../sfc/templateQuote/expected/quoteScope.vue | 31 ++++++++ .../templateQuote/expected/templateQuote.vue | 52 +++++++------- .../jsxQuote.components-map.json | 9 +++ .../sfc/templateQuote/jsxQuote.schema.json | 45 ++++++++++++ .../sfc/templateQuote/multiline.schema.json | 23 ++++++ .../templateQuote/primitiveQuote.schema.json | 24 +++++++ .../sfc/templateQuote/scope.schema.json | 29 ++++++++ .../sfc/templateQuote/templateQuote.test.js | 52 +++++++++----- 13 files changed, 329 insertions(+), 93 deletions(-) create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/expected/jsxQuote.vue create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/expected/multiline.vue create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/expected/primitiveQuote.vue create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/expected/quoteScope.vue create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/jsxQuote.components-map.json create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/jsxQuote.schema.json create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/multiline.schema.json create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/primitiveQuote.schema.json create mode 100644 packages/vue-generator/test/testcases/sfc/templateQuote/scope.schema.json diff --git a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js index 46a0919a4d..d44fa91c47 100644 --- a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js +++ b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js @@ -70,7 +70,7 @@ export const checkHasSpecialType = (obj) => { return false } -const handleJSExpressionBinding = (key, value, isJSX, globalHooks) => { +const handleJSExpressionBinding = (key, value, isJSX) => { const expressValue = value.value.replace(isJSX ? thisRegexp : thisPropsBindRe, '') if (isJSX) { @@ -85,19 +85,10 @@ const handleJSExpressionBinding = (key, value, isJSX, globalHooks) => { } // expression 使用 v-bind 绑定 - if (expressValue.includes('"') && expressValue.includes("'")) { - let stateKey = `${key}_${randomString()}` - let addSuccess = globalHooks.addState(stateKey, `${stateKey}:${expressValue}`) - - while (!addSuccess) { - stateKey = `${key}_${randomString()}` - addSuccess = globalHooks.addState(stateKey, `${stateKey}:${expressValue}`) - } - - return `:${key}="state.${stateKey}"` - } else { - return `:${key}="${expressValue.replaceAll(/"/g, "'")}"` - } + // 如果包含双引号,通过 " 编码避免与属性分隔符冲突 + // 比如绑定的值为:[{ "name": "test" }] + // 则转换为: :key="[{ "name": "test" }]" + return `:${key}="${expressValue.replaceAll(/"/g, '"')}"` } const handleBindI18n = (key, value, isJSX) => { @@ -194,19 +185,7 @@ export const handleLoopAttrHook = (schemaData = {}, globalHooks, config) => { const iterVar = [...loopArgs] if (!isJSX) { - if (source.includes('"') && source.includes("'")) { - let stateKey = `loop_${randomString()}` - let addSuccess = globalHooks.addState(stateKey, `${stateKey}:${source}`) - - while (!addSuccess) { - stateKey = `loop_${randomString()}` - addSuccess = globalHooks.addState(stateKey, `${stateKey}:${source}`) - } - - attributes.push(`v-for="(${iterVar.join(',')}) in state.${stateKey}"`) - } else { - attributes.push(`v-for="(${iterVar.join(',')}) in ${source.replaceAll(/"/g, "'")}"`) - } + attributes.push(`v-for="(${iterVar.join(',')}) in ${source.replaceAll(/"/g, '"')}"`) return } @@ -376,7 +355,7 @@ export const handleExpressionAttrHook = (schemaData, globalHooks, config) => { Object.entries(props).forEach(([key, value]) => { if (value?.type === JS_EXPRESSION && !isOn(key)) { specialTypeHandler[JS_RESOURCE](value, globalHooks, config) - attributes.push(handleJSExpressionBinding(key, value, isJSX, globalHooks)) + attributes.push(handleJSExpressionBinding(key, value, isJSX)) delete props[key] } @@ -408,7 +387,7 @@ export const handleJSFunctionAttrHook = (schemaData, globalHooks, config) => { functionName = value.value } - attributes.push(handleJSExpressionBinding(key, { value: functionName }, isJSX, globalHooks)) + attributes.push(handleJSExpressionBinding(key, { value: functionName }, isJSX)) delete props[key] } @@ -475,15 +454,15 @@ const genStateAccessor = (value, globalHooks) => { } } -const transformObjValue = (renderKey, value, globalHooks, config, transformObjType, shouldConvertQuote = false) => { +const transformObjValue = (renderKey, value, globalHooks, config, transformObjType) => { const result = { shouldBindToState: false, res: null } if (typeof value === 'string') { - if (shouldConvertQuote) { - result.res = `${renderKey}'${value.replaceAll(/"/g, "'").replaceAll(/'/g, "\\'")}'` - } else { - result.res = `${renderKey}"${value.replaceAll("'", "\\'").replaceAll(/"/g, "'")}"` - } + result.res = `${renderKey}"${value + .replaceAll(/\\/g, '\\\\') + .replaceAll(/\n/g, '\\n') + .replaceAll(/\r/g, '\\r') + .replaceAll(/"/g, '\\"')}"` return result } @@ -496,11 +475,7 @@ const transformObjValue = (renderKey, value, globalHooks, config, transformObjTy if (specialTypeHandler[value?.type]) { const specialVal = specialTypeHandler[value.type](value, globalHooks, config)?.value || '' - if (shouldConvertQuote) { - result.res = `${renderKey}${specialVal.replaceAll(/"/g, "'")}` - } else { - result.res = `${renderKey}${specialVal}` - } + result.res = `${renderKey}${specialVal}` if (specialTypes.includes(value.type)) { result.shouldBindToState = true @@ -535,7 +510,7 @@ const transformObjValue = (renderKey, value, globalHooks, config, transformObjTy } const normalKeyRegexp = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/ -export const transformObjType = (obj, globalHooks, config, shouldConvertQuote = false) => { +export const transformObjType = (obj, globalHooks, config) => { if (!obj || typeof obj !== 'object') { return { res: obj @@ -559,8 +534,7 @@ export const transformObjType = (obj, globalHooks, config, shouldConvertQuote = value, globalHooks, config, - transformObjType, - shouldConvertQuote + transformObjType ) if (tmpShouldBindToState) { @@ -574,7 +548,7 @@ export const transformObjType = (obj, globalHooks, config, shouldConvertQuote = // 复杂的 object 类型,需要递归处理 const { res: tempRes, shouldBindToState: tempShouldBindToState } = - transformObjType(value, globalHooks, config, shouldConvertQuote) || {} + transformObjType(value, globalHooks, config) || {} resStr.push(`${renderKey}${tempRes}`) @@ -603,20 +577,21 @@ export const handleObjBindAttrHook = (schemaData, globalHooks, config) => { return } - const { res, shouldBindToState } = transformObjType(value, globalHooks, config, true) + const { res, shouldBindToState } = transformObjType(value, globalHooks, config) if (shouldBindToState && !isJSX) { let stateKey = key let addSuccess = globalHooks.addState(stateKey, `${stateKey}:${res}`) + let retryCount = 0 - while (!addSuccess) { + while (!addSuccess && retryCount++ < 100) { stateKey = `${key}${randomString()}` addSuccess = globalHooks.addState(stateKey, `${stateKey}:${res}`) } attributes.push(`:${key}="state.${stateKey}"`) } else { - attributes.push(isJSX ? `${key}={${res}}` : `:${key}="${res}"`) + attributes.push(isJSX ? `${key}={${res}}` : `:${key}="${res.replaceAll(/"/g, '"')}"`) } delete props[key] @@ -633,7 +608,7 @@ export const handlePrimitiveAttributeHook = (schemaData, globalHooks, config) => const valueType = typeof value if (valueType === 'string') { - attributes.push(`${key}="${value.replaceAll(/"/g, "'")}"`) + attributes.push(`${key}="${value.replaceAll(/"/g, '"')}"`) delete props[key] } diff --git a/packages/vue-generator/test/testcases/sfc/case01/expected/FormTable.vue b/packages/vue-generator/test/testcases/sfc/case01/expected/FormTable.vue index dfdb69494b..5b494b970f 100644 --- a/packages/vue-generator/test/testcases/sfc/case01/expected/FormTable.vue +++ b/packages/vue-generator/test/testcases/sfc/case01/expected/FormTable.vue @@ -118,7 +118,7 @@ const { utils } = wrap(function () { })() const state = vue.reactive({ IconPlusSquare: utils.IconPlusSquare(), - theme: "{ 'id': 22, 'name': '@cloud/tinybuilder-theme-dark', 'description': '黑暗主题' }", + theme: '{ "id": 22, "name": "@cloud/tinybuilder-theme-dark", "description": "黑暗主题" }', companyName: '', companyOptions: null, companyCity: '', diff --git a/packages/vue-generator/test/testcases/sfc/templateQuote/expected/jsxQuote.vue b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/jsxQuote.vue new file mode 100644 index 0000000000..49e5490e77 --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/jsxQuote.vue @@ -0,0 +1,27 @@ + + + + diff --git a/packages/vue-generator/test/testcases/sfc/templateQuote/expected/multiline.vue b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/multiline.vue new file mode 100644 index 0000000000..2269d8b75c --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/multiline.vue @@ -0,0 +1,28 @@ + + + + diff --git a/packages/vue-generator/test/testcases/sfc/templateQuote/expected/primitiveQuote.vue b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/primitiveQuote.vue new file mode 100644 index 0000000000..191d978010 --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/primitiveQuote.vue @@ -0,0 +1,29 @@ + + + + diff --git a/packages/vue-generator/test/testcases/sfc/templateQuote/expected/quoteScope.vue b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/quoteScope.vue new file mode 100644 index 0000000000..ad5ed0d31e --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/quoteScope.vue @@ -0,0 +1,31 @@ + + + + diff --git a/packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue index 27197c4583..1ad616adb2 100644 --- a/packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue +++ b/packages/vue-generator/test/testcases/sfc/templateQuote/expected/templateQuote.vue @@ -1,21 +1,42 @@