From b63169431cd89082cbf8575a59799720b24e1528 Mon Sep 17 00:00:00 2001 From: chilingling <26962197+chilingling@users.noreply.github.com> Date: Wed, 28 May 2025 11:20:07 +0800 Subject: [PATCH] feat: gen code support attr with function (#1369) --- .../src/generator/vue/sfc/genSetupSFC.js | 4 +- .../generator/vue/sfc/generateAttribute.js | 33 +++++++++ .../sfc/case01/expected/FormTable.vue | 4 +- .../sfc/case02/expected/UsePropAccessor.vue | 2 +- .../sfc/case03/expected/UseStateAccessor.vue | 2 +- .../sfc/jsFunctionAttr/components-map.json | 9 +++ .../expected/jsFunctionAttrTest.vue | 42 +++++++++++ .../sfc/jsFunctionAttr/jsFunctionAttr.test.js | 12 +++ .../sfc/jsFunctionAttr/page.schema.json | 73 +++++++++++++++++++ .../slotParams/expected/slotParamsPage.vue | 2 +- 10 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 packages/vue-generator/test/testcases/sfc/jsFunctionAttr/components-map.json create mode 100644 packages/vue-generator/test/testcases/sfc/jsFunctionAttr/expected/jsFunctionAttrTest.vue create mode 100644 packages/vue-generator/test/testcases/sfc/jsFunctionAttr/jsFunctionAttr.test.js create mode 100644 packages/vue-generator/test/testcases/sfc/jsFunctionAttr/page.schema.json diff --git a/packages/vue-generator/src/generator/vue/sfc/genSetupSFC.js b/packages/vue-generator/src/generator/vue/sfc/genSetupSFC.js index 6914b149..075bb67a 100644 --- a/packages/vue-generator/src/generator/vue/sfc/genSetupSFC.js +++ b/packages/vue-generator/src/generator/vue/sfc/genSetupSFC.js @@ -21,7 +21,8 @@ import { handleObjBindAttrHook, handleEventAttrHook, handleTinyIconPropsHook, - handleJsxModelValueUpdate + handleJsxModelValueUpdate, + handleJSFunctionAttrHook } from './generateAttribute' import { GEN_SCRIPT_HOOKS, @@ -227,6 +228,7 @@ export const genSFCWithDefaultPlugin = (schema, componentsMap, config = {}, next handleAttrKeyHook, handlePrimitiveAttributeHook, handleExpressionAttrHook, + handleJSFunctionAttrHook, handleI18nAttrHook, handleTinyIconPropsHook, handleObjBindAttrHook, diff --git a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js index 6ec0eb14..2f4c8e89 100644 --- a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js +++ b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js @@ -1,3 +1,4 @@ +import { parse } from '@babel/parser' import { JS_EXPRESSION, JS_FUNCTION, @@ -358,6 +359,38 @@ export const handleExpressionAttrHook = (schemaData, globalHooks, config) => { }) } +export const handleJSFunctionAttrHook = (schemaData, globalHooks, config) => { + const { attributes, schema: { props = {} } = {} } = schemaData || {} + const isJSX = config.isJSX + Object.entries(props).forEach(([key, value]) => { + if (value?.type === JS_FUNCTION && !isOn(key)) { + let functionName = `${key}Fun` + + try { + const ast = parse(value.value, { sourceType: 'module', plugins: ['typescript', 'jsx'] }) + // 这里可能会存在命名冲突的场景,应该在设计态阻止命名冲突 + functionName = ast.program.body[0].id.name + + globalHooks.addStatement({ + position: INSERT_POSITION.AFTER_METHODS, + value: `const ${functionName} = wrap(${value.value})`, + key: functionName + }) + } catch (error) { + const logger = console + logger.warn(`解析失败属性 ${key} 失败: ${error.message || error}`) + + // 解析失败,不处理,原样绑定到属性上 + functionName = value.value + } + + attributes.push(handleJSExpressionBinding(key, { value: functionName }, isJSX)) + + delete props[key] + } + }) +} + export const handleI18nAttrHook = (schemaData, globalHooks, config) => { const { attributes, schema: { props = {} } = {} } = schemaData || {} const isJSX = config.isJSX 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 8328bbfe..dfdb6949 100644 --- a/packages/vue-generator/test/testcases/sfc/case01/expected/FormTable.vue +++ b/packages/vue-generator/test/testcases/sfc/case01/expected/FormTable.vue @@ -98,9 +98,9 @@ import { IconSearch, IconDel, iconHelpCircle, IconEdit } from '@opentiny/vue-ico import * as vue from 'vue' import { defineProps, defineEmits } from 'vue' import { I18nInjectionKey } from 'vue-i18n' -import CrmQuoteListGridStatus from '../components/CrmQuoteListGridStatus.vue' +import CrmQuoteListGridStatus from '@/components/CrmQuoteListGridStatus.vue' -import ImageTitle from '../components/ImageTitle.vue' +import ImageTitle from '@/components/ImageTitle.vue' const TinyIconSearch = IconSearch() const TinyIconDel = IconDel() diff --git a/packages/vue-generator/test/testcases/sfc/case02/expected/UsePropAccessor.vue b/packages/vue-generator/test/testcases/sfc/case02/expected/UsePropAccessor.vue index 69bfc22b..95382f3a 100644 --- a/packages/vue-generator/test/testcases/sfc/case02/expected/UsePropAccessor.vue +++ b/packages/vue-generator/test/testcases/sfc/case02/expected/UsePropAccessor.vue @@ -11,7 +11,7 @@ import { Form as TinyForm, FormItem as TinyFormItem } from '@opentiny/vue' import * as vue from 'vue' import { defineProps, defineEmits } from 'vue' import { I18nInjectionKey } from 'vue-i18n' -import PropAccessor from '../components/PropAccessor.vue' +import PropAccessor from '@/components/PropAccessor.vue' const props = defineProps({}) diff --git a/packages/vue-generator/test/testcases/sfc/case03/expected/UseStateAccessor.vue b/packages/vue-generator/test/testcases/sfc/case03/expected/UseStateAccessor.vue index 82302754..f4a3e4fa 100644 --- a/packages/vue-generator/test/testcases/sfc/case03/expected/UseStateAccessor.vue +++ b/packages/vue-generator/test/testcases/sfc/case03/expected/UseStateAccessor.vue @@ -12,7 +12,7 @@ import { Form as TinyForm, FormItem as TinyFormItem } from '@opentiny/vue' import * as vue from 'vue' import { defineProps, defineEmits } from 'vue' import { I18nInjectionKey } from 'vue-i18n' -import StateAccessor from '../components/StateAccessor.vue' +import StateAccessor from '@/components/StateAccessor.vue' const props = defineProps({}) diff --git a/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/components-map.json b/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/components-map.json new file mode 100644 index 00000000..eda80675 --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/components-map.json @@ -0,0 +1,9 @@ +[ + { + "componentName": "TinyTree", + "exportName": "Tree", + "package": "@opentiny/vue", + "version": "^3.10.0", + "destructuring": true + } +] diff --git a/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/expected/jsFunctionAttrTest.vue b/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/expected/jsFunctionAttrTest.vue new file mode 100644 index 00000000..95b56185 --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/expected/jsFunctionAttrTest.vue @@ -0,0 +1,42 @@ + + + + diff --git a/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/jsFunctionAttr.test.js b/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/jsFunctionAttr.test.js new file mode 100644 index 00000000..bd7b6d5b --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/jsFunctionAttr.test.js @@ -0,0 +1,12 @@ +import { expect, test } from 'vitest' +import { genSFCWithDefaultPlugin } from '@/generator/vue/sfc' +import schema from './page.schema.json' +import componentsMap from './components-map.json' +import { formatCode } from '@/utils/formatCode' + +test('should generate extra jsFunction on setup', async () => { + const res = genSFCWithDefaultPlugin(schema, componentsMap) + const formattedCode = formatCode(res, 'vue') + + await expect(formattedCode).toMatchFileSnapshot('./expected/jsFunctionAttrTest.vue') +}) diff --git a/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/page.schema.json b/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/page.schema.json new file mode 100644 index 00000000..64e00d32 --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/jsFunctionAttr/page.schema.json @@ -0,0 +1,73 @@ +{ + "state": {}, + "methods": {}, + "componentName": "Page", + "css": "", + "props": {}, + "lifeCycles": {}, + "children": [ + { + "componentName": "div", + "props": {}, + "id": "85375559", + "children": [ + { + "componentName": "TinyTree", + "props": { + "data": [ + { + "label": "一级 1", + "children": [ + { + "label": "二级 1-1", + "children": [ + { + "label": "三级 1-1-1" + } + ] + } + ] + }, + { + "label": "一级 2", + "children": [ + { + "label": "二级 2-1", + "children": [ + { + "label": "三级 2-1-1" + } + ] + }, + { + "label": "二级 2-2", + "children": [ + { + "label": "三级 2-2-1" + } + ] + } + ] + } + ], + "className": "component-base-style", + "render-content": { + "type": "JSFunction", + "value": "function render(h, { node, data }) {\n return {node.label}\n}\n" + } + }, + "children": [], + "id": "43235432" + } + ] + } + ], + "dataSource": { + "list": [] + }, + "utils": [], + "bridge": [], + "inputs": [], + "outputs": [], + "fileName": "jsFunctionAttrTest" +} diff --git a/packages/vue-generator/test/testcases/sfc/slotParams/expected/slotParamsPage.vue b/packages/vue-generator/test/testcases/sfc/slotParams/expected/slotParamsPage.vue index bfa219de..34834d65 100644 --- a/packages/vue-generator/test/testcases/sfc/slotParams/expected/slotParamsPage.vue +++ b/packages/vue-generator/test/testcases/sfc/slotParams/expected/slotParamsPage.vue @@ -12,7 +12,7 @@ import * as vue from 'vue' import { defineProps, defineEmits } from 'vue' import { I18nInjectionKey } from 'vue-i18n' -import BlockSlotParams from '../components/BlockSlotParams.vue' +import BlockSlotParams from '@/components/BlockSlotParams.vue' const props = defineProps({})