feat: gen code support attr with function (#1369)

This commit is contained in:
chilingling 2025-05-28 11:20:07 +08:00 committed by GitHub
parent 8b91cf12ce
commit b63169431c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 177 additions and 6 deletions

View File

@ -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,

View File

@ -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

View File

@ -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()

View File

@ -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({})

View File

@ -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({})

View File

@ -0,0 +1,9 @@
[
{
"componentName": "TinyTree",
"exportName": "Tree",
"package": "@opentiny/vue",
"version": "^3.10.0",
"destructuring": true
}
]

View File

@ -0,0 +1,42 @@
<template>
<div>
<div>
<tiny-tree
class="component-base-style"
:render-content="render"
: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' }] }
]
}
]"
></tiny-tree>
</div>
</div>
</template>
<script setup lang="jsx">
import { Tree as TinyTree } from '@opentiny/vue'
import * as vue from 'vue'
import { defineProps, defineEmits } from 'vue'
import { I18nInjectionKey } from 'vue-i18n'
const props = defineProps({})
const emit = defineEmits([])
const { t, lowcodeWrap, stores } = vue.inject(I18nInjectionKey).lowcode()
const wrap = lowcodeWrap(props, { emit })
wrap({ stores })
const state = vue.reactive({})
wrap({ state })
const render = wrap(function render(h, { node, data }) {
return <span style="color: red;font-size: 20px;">{node.label}</span>
})
</script>
<style scoped></style>

View File

@ -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')
})

View File

@ -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 <span style=\"color: red;font-size: 20px;\">{node.label}</span>\n}\n"
}
},
"children": [],
"id": "43235432"
}
]
}
],
"dataSource": {
"list": []
},
"utils": [],
"bridge": [],
"inputs": [],
"outputs": [],
"fileName": "jsFunctionAttrTest"
}

View File

@ -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({})