From efd8805f4009a44246a63d5c68f13d4d7ba3b2df Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 6 Jan 2025 23:14:26 -0800 Subject: [PATCH 1/4] fix: state accessor defaultValue support expression #977 --- .../generator/vue/sfc/generateAttribute.js | 72 ++++++++++++------- .../sfc/accessor/expected/Accessor.vue | 40 ++++++++++- .../test/testcases/sfc/accessor/schema.json | 67 +++++++++++++++++ 3 files changed, 153 insertions(+), 26 deletions(-) diff --git a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js index a713728e70..a1ff1a5933 100644 --- a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js +++ b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js @@ -377,6 +377,50 @@ export const handleTinyIconPropsHook = (schemaData, globalHooks, config) => { }) } +// 生成 watchEffect +const genStateAccessor = (value, globalHooks) => { + if (isSetter(value?.accessor)) { + globalHooks.addStatement({ + position: INSERT_POSITION.AFTER_METHODS, + value: `vue.watchEffect(wrap(${value.accessor.setter?.value ?? ''}))` + }) + } + + if (isGetter(value?.accessor)) { + globalHooks.addStatement({ + position: INSERT_POSITION.AFTER_METHODS, + value: `vue.watchEffect(wrap(${value.accessor.getter?.value ?? ''}))` + }) + } +} + +// 针对 state 有 getter 的场景进行处理 +export const handleAccessorBinding = (renderKey, value, globalHooks, config) => { + genStateAccessor(value, globalHooks) + + const result = { shouldBindToState: false, res: '' } + + if (typeof value.defaultValue === 'string') { + result.res = `${renderKey}"${value.defaultValue.replaceAll("'", "\\'").replaceAll(/"/g, "'")}"` + } else if (specialTypeHandler[value?.defaultValue?.type]) { + const specialVal = specialTypeHandler[value.defaultValue.type](value.defaultValue, globalHooks, config)?.value || '' + + if (specialTypes.includes(value.defaultValue.type)) { + result.shouldBindToState = true + } + + result.res = `${renderKey}${specialVal}` + } else { + const { res: tempRes } = + // eslint-disable-next-line no-use-before-define + transformObjType(value.defaultValue, globalHooks, config) || {} + + result.res = `${renderKey}${tempRes}` + } + + return result +} + export const transformObjType = (obj, globalHooks, config) => { if (!obj || typeof obj !== 'object') { return { @@ -415,32 +459,10 @@ export const transformObjType = (obj, globalHooks, config) => { } if (hasAccessor(value?.accessor)) { - if (typeof value.defaultValue === 'string') { - resStr.push(`${renderKey}"${value.defaultValue.replaceAll("'", "\\'").replaceAll(/"/g, "'")}"`) - } else { - const { res: tempRes, shouldBindToState: tempShouldBindToState } = - transformObjType(value.defaultValue, globalHooks, config) || {} - - resStr.push(`${renderKey}${tempRes}`) - - if (tempShouldBindToState) { - shouldBindToState = true - } - } + // 递归处理其他类型 + const { res } = handleAccessorBinding(renderKey, value, globalHooks, config) - if (isSetter(value?.accessor)) { - globalHooks.addStatement({ - position: INSERT_POSITION.AFTER_METHODS, - value: `vue.watchEffect(wrap(${value.accessor.setter?.value ?? ''}))` - }) - } - - if (isGetter(value?.accessor)) { - globalHooks.addStatement({ - position: INSERT_POSITION.AFTER_METHODS, - value: `vue.watchEffect(wrap(${value.accessor.getter?.value ?? ''}))` - }) - } + resStr.push(res) continue } 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 00ffda6f0b..fa5fc1b75e 100644 --- a/packages/vue-generator/test/testcases/sfc/accessor/expected/Accessor.vue +++ b/packages/vue-generator/test/testcases/sfc/accessor/expected/Accessor.vue @@ -16,6 +16,9 @@ const { t, lowcodeWrap, stores } = vue.inject(I18nInjectionKey).lowcode() const wrap = lowcodeWrap(props, { emit }) wrap({ stores }) +const { utils } = wrap(function () { + return this +})() const state = vue.reactive({ firstName: 'Opentiny', lastName: 'TinyEngine', @@ -26,7 +29,22 @@ const state = vue.reactive({ trueVal: true, falseVal: false, arrVal: [1, '2', { aaa: 'aaa' }, [3, 4], true, false], - objVal: { aaa: 'aaa', arr: [1, '2', true, false, 0], ccc: { bbb: 'bbb' }, d: 32432, e: '', f: null, g: false } + objVal: { aaa: 'aaa', arr: [1, '2', true, false, 0], ccc: { bbb: 'bbb' }, d: 32432, e: '', f: null, g: false }, + IconPlusSquare: utils.IconPlusSquare(), + editConfig: { + trigger: 'click', + mode: 'cell', + showStatus: true, + activeMethod: () => { + return props.isEdit + } + }, + status: vue.computed(statusData), + buttons: [ + { type: 'primary', text: '主要操作' }, + { type: 'success', text: '成功操作' }, + { type: 'danger', text: t('operation.danger') } + ] }) wrap({ state }) @@ -70,5 +88,25 @@ vue.watchEffect( this.state.objVal = `${this.state.firstName} ${this.state.lastName}` }) ) +vue.watchEffect( + wrap(function () { + this.state.IconPlusSquare = `${this.state.firstName} ${this.state.lastName}` + }) +) +vue.watchEffect( + wrap(function () { + this.state.editConfig = `${this.state.firstName} ${this.state.lastName}` + }) +) +vue.watchEffect( + wrap(function () { + this.state.status = `${this.state.firstName} ${this.state.lastName}` + }) +) +vue.watchEffect( + wrap(function () { + this.state.buttons = `${this.state.firstName} ${this.state.lastName}` + }) +) diff --git a/packages/vue-generator/test/testcases/sfc/accessor/schema.json b/packages/vue-generator/test/testcases/sfc/accessor/schema.json index 27a6c664dd..a72472ba0f 100644 --- a/packages/vue-generator/test/testcases/sfc/accessor/schema.json +++ b/packages/vue-generator/test/testcases/sfc/accessor/schema.json @@ -94,6 +94,73 @@ "value": "function() { this.state.objVal = `${this.state.firstName} ${this.state.lastName}` }" } } + }, + "IconPlusSquare": { + "defaultValue": { + "type": "JSResource", + "value": "this.utils.IconPlusSquare()" + }, + "accessor": { + "getter": { + "type": "JSFunction", + "value": "function() { this.state.IconPlusSquare = `${this.state.firstName} ${this.state.lastName}` }" + } + } + }, + "editConfig": { + "defaultValue": { + "trigger": "click", + "mode": "cell", + "showStatus": true, + "activeMethod": { + "type": "JSFunction", + "value": "function() { return this.props.isEdit }" + } + }, + "accessor": { + "getter": { + "type": "JSFunction", + "value": "function() { this.state.editConfig = `${this.state.firstName} ${this.state.lastName}` }" + } + } + }, + "status": { + "defaultValue": { + "type": "JSExpression", + "value": "this.statusData", + "computed": true + }, + "accessor": { + "getter": { + "type": "JSFunction", + "value": "function() { this.state.status = `${this.state.firstName} ${this.state.lastName}` }" + } + } + }, + "buttons": { + "defaultValue": [ + { + "type": "primary", + "text": "主要操作" + }, + { + "type": "success", + "text": "成功操作" + }, + { + "type": "danger", + "text": { + "type": "i18n", + "key": "operation.danger" + } + } + ], + "accessor": { + "getter": { + "type": "JSFunction", + "value": "function() { this.state.buttons = `${this.state.firstName} ${this.state.lastName}` }" + } + } } }, "lifeCycles": {}, From d17120d7e2945c2221dcf041b870d518c347aed1 Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 6 Jan 2025 23:23:58 -0800 Subject: [PATCH 2/4] fix: add empty check for accessor value --- .../src/generator/vue/sfc/generateAttribute.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js index a1ff1a5933..c44c005a86 100644 --- a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js +++ b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js @@ -379,17 +379,17 @@ export const handleTinyIconPropsHook = (schemaData, globalHooks, config) => { // 生成 watchEffect const genStateAccessor = (value, globalHooks) => { - if (isSetter(value?.accessor)) { + if (isSetter(value?.accessor) && value.accessor.setter?.value) { globalHooks.addStatement({ position: INSERT_POSITION.AFTER_METHODS, - value: `vue.watchEffect(wrap(${value.accessor.setter?.value ?? ''}))` + value: `vue.watchEffect(wrap(${value.accessor.setter?.value}))` }) } - if (isGetter(value?.accessor)) { + if (isGetter(value?.accessor) && value.accessor.getter?.value) { globalHooks.addStatement({ position: INSERT_POSITION.AFTER_METHODS, - value: `vue.watchEffect(wrap(${value.accessor.getter?.value ?? ''}))` + value: `vue.watchEffect(wrap(${value.accessor.getter?.value}))` }) } } From 59f087e4cc00979cefeea4a33be5596637ed1c16 Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 6 Jan 2025 23:26:55 -0800 Subject: [PATCH 3/4] fix: remove optional chaining after have empty check --- .../vue-generator/src/generator/vue/sfc/generateAttribute.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js index c44c005a86..fba4d15bb1 100644 --- a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js +++ b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js @@ -382,14 +382,14 @@ const genStateAccessor = (value, globalHooks) => { if (isSetter(value?.accessor) && value.accessor.setter?.value) { globalHooks.addStatement({ position: INSERT_POSITION.AFTER_METHODS, - value: `vue.watchEffect(wrap(${value.accessor.setter?.value}))` + value: `vue.watchEffect(wrap(${value.accessor.setter.value}))` }) } if (isGetter(value?.accessor) && value.accessor.getter?.value) { globalHooks.addStatement({ position: INSERT_POSITION.AFTER_METHODS, - value: `vue.watchEffect(wrap(${value.accessor.getter?.value}))` + value: `vue.watchEffect(wrap(${value.accessor.getter.value}))` }) } } From f80743efaf08d7163dc15ef5bb52197c5e342a78 Mon Sep 17 00:00:00 2001 From: chilingling Date: Tue, 7 Jan 2025 01:09:12 -0800 Subject: [PATCH 4/4] fix: optimize transformObjType function --- .../generator/vue/sfc/generateAttribute.js | 90 ++++++++++--------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js index fba4d15bb1..2004628cfd 100644 --- a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js +++ b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js @@ -394,28 +394,52 @@ const genStateAccessor = (value, globalHooks) => { } } -// 针对 state 有 getter 的场景进行处理 -export const handleAccessorBinding = (renderKey, value, globalHooks, config) => { - genStateAccessor(value, globalHooks) +const transformObjValue = (renderKey, value, globalHooks, config, transformObjType) => { + const result = { shouldBindToState: false, res: null } - const result = { shouldBindToState: false, res: '' } + if (typeof value === 'string') { + result.res = `${renderKey}"${value.replaceAll("'", "\\'").replaceAll(/"/g, "'")}"` - if (typeof value.defaultValue === 'string') { - result.res = `${renderKey}"${value.defaultValue.replaceAll("'", "\\'").replaceAll(/"/g, "'")}"` - } else if (specialTypeHandler[value?.defaultValue?.type]) { - const specialVal = specialTypeHandler[value.defaultValue.type](value.defaultValue, globalHooks, config)?.value || '' + return result + } + + if (typeof value !== 'object' || value === null) { + result.res = `${renderKey}${value}` + + return result + } - if (specialTypes.includes(value.defaultValue.type)) { + if (specialTypeHandler[value?.type]) { + const specialVal = specialTypeHandler[value.type](value, globalHooks, config)?.value || '' + result.res = `${renderKey}${specialVal}` + + if (specialTypes.includes(value.type)) { result.shouldBindToState = true } - result.res = `${renderKey}${specialVal}` - } else { - const { res: tempRes } = - // eslint-disable-next-line no-use-before-define - transformObjType(value.defaultValue, globalHooks, config) || {} + return result + } + + if (hasAccessor(value?.accessor)) { + // 递归处理其他类型 + genStateAccessor(value, globalHooks) + + const { res } = transformObjValue(renderKey, value.defaultValue, globalHooks, config, transformObjType) + + if (typeof res === 'string') { + result.res = res - result.res = `${renderKey}${tempRes}` + return result + } else { + const { res: tempRes, shouldBindToState: tempShouldBindToState } = + transformObjType(value.defaultValue, globalHooks, config) || {} + + result.res = `${renderKey}${tempRes}` + + if (tempShouldBindToState) { + result.shouldBindToState = true + } + } } return result @@ -435,38 +459,24 @@ export const transformObjType = (obj, globalHooks, config) => { for (const [key, value] of Object.entries(obj)) { let renderKey = shouldRenderKey ? `${key}: ` : '' - if (typeof value === 'string') { - resStr.push(`${renderKey}"${value.replaceAll("'", "\\'").replaceAll(/"/g, "'")}"`) - - continue - } - - if (typeof value !== 'object' || value === null) { - resStr.push(`${renderKey}${value}`) - - continue - } + const { res, shouldBindToState: tmpShouldBindToState } = transformObjValue( + renderKey, + value, + globalHooks, + config, + transformObjType + ) - if (specialTypeHandler[value?.type]) { - const specialVal = specialTypeHandler[value.type](value, globalHooks, config)?.value || '' - resStr.push(`${renderKey}${specialVal}`) - - if (specialTypes.includes(value.type)) { - shouldBindToState = true - } - - continue + if (tmpShouldBindToState) { + shouldBindToState = true } - if (hasAccessor(value?.accessor)) { - // 递归处理其他类型 - const { res } = handleAccessorBinding(renderKey, value, globalHooks, config) - + if (typeof res === 'string') { resStr.push(res) - continue } + // 复杂的 object 类型,需要递归处理 const { res: tempRes, shouldBindToState: tempShouldBindToState } = transformObjType(value, globalHooks, config) || {}