diff --git a/packages/vue-generator/.eslintrc.cjs b/packages/vue-generator/.eslintrc.cjs index 0131b38e36..94ab4fed28 100644 --- a/packages/vue-generator/.eslintrc.cjs +++ b/packages/vue-generator/.eslintrc.cjs @@ -1,3 +1,5 @@ +const { rules } = require('../../.eslintrc') + /* eslint-env node */ require('@rushstack/eslint-patch/modern-module-resolution') @@ -17,5 +19,6 @@ module.exports = { } }, // 忽略 expected 中的内容 - ignorePatterns: ['**/**/expected/*', '**/**.ts'] + ignorePatterns: ['**/**/expected/*', '**/**.ts'], + rules } diff --git a/packages/vue-generator/src/generator/vue/sfc/genSetupSFC.js b/packages/vue-generator/src/generator/vue/sfc/genSetupSFC.js index 29d07a55d7..ff8ac1a536 100644 --- a/packages/vue-generator/src/generator/vue/sfc/genSetupSFC.js +++ b/packages/vue-generator/src/generator/vue/sfc/genSetupSFC.js @@ -19,7 +19,8 @@ import { handleI18nAttrHook, handleObjBindAttrHook, handleEventAttrHook, - handleTinyIconPropsHook + handleTinyIconPropsHook, + handleJsxModelValueUpdate } from './generateAttribute' import { GEN_SCRIPT_HOOKS, @@ -213,6 +214,7 @@ export const genSFCWithDefaultPlugin = (schema, componentsMap, config = {}) => { const defaultAttributeHook = [ handleTinyGrid, + handleJsxModelValueUpdate, handleConditionAttrHook, handleLoopAttrHook, handleSlotBindAttrHook, diff --git a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js index d4d7d0aa2b..9b623ca76a 100644 --- a/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js +++ b/packages/vue-generator/src/generator/vue/sfc/generateAttribute.js @@ -515,3 +515,28 @@ export const handleBindUtilsHooks = (schemaData, globalHooks, config) => { } }) } + +/** + * 为 modelvalue 绑定自动生成 onUpdate:modelValue 事件绑定 + * @param {*} schemaData + * @param {*} globalHooks + * @param {*} config + */ +export const handleJsxModelValueUpdate = (schemaData, globalHooks, config) => { + const { schema: { props = {} } = {} } = schemaData || {} + const isJSX = config.isJSX + + if (!isJSX) { + return + } + + const propsEntries = Object.entries(props) + const modelValue = propsEntries.find(([_key, value]) => value?.type === JS_EXPRESSION && value?.model === true) + const hasUpdateModelValue = propsEntries.find(([key]) => isOn(key) && key.startsWith(`onUpdate:${modelValue?.[0]}`)) + + // jsx 形式的 modelvalue, 如果 schema 没有声明,出码需要同时声明 onUpdate:modelValue,否则更新失效 + if (modelValue && !hasUpdateModelValue) { + // 添加 onUpdate:modelKey 事件,让后续钩子生成 对应的事件声明 + props[`onUpdate:${modelValue?.[0]}`] = { type: JS_EXPRESSION, value: `(value) => ${modelValue[1].value}=value` } + } +} diff --git a/packages/vue-generator/test/testcases/sfc/slotModelValue/components-map.json b/packages/vue-generator/test/testcases/sfc/slotModelValue/components-map.json new file mode 100644 index 0000000000..88f7765261 --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/slotModelValue/components-map.json @@ -0,0 +1,23 @@ +[ + { + "componentName": "TinyGrid", + "exportName": "Grid", + "package": "@opentiny/vue", + "version": "^3.10.0", + "destructuring": true + }, + { + "componentName": "TinyNumeric", + "exportName": "Numeric", + "package": "@opentiny/vue", + "version": "^3.10.0", + "destructuring": true + }, + { + "componentName": "TinyInput", + "exportName": "Input", + "package": "@opentiny/vue", + "version": "^3.10.0", + "destructuring": true + } +] diff --git a/packages/vue-generator/test/testcases/sfc/slotModelValue/expected/slotModelValueTest.vue b/packages/vue-generator/test/testcases/sfc/slotModelValue/expected/slotModelValueTest.vue new file mode 100644 index 0000000000..7d5e5f083a --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/slotModelValue/expected/slotModelValueTest.vue @@ -0,0 +1,96 @@ + + + + diff --git a/packages/vue-generator/test/testcases/sfc/slotModelValue/page.schema.json b/packages/vue-generator/test/testcases/sfc/slotModelValue/page.schema.json new file mode 100644 index 0000000000..e5da94f0cc --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/slotModelValue/page.schema.json @@ -0,0 +1,150 @@ +{ + "state": {}, + "methods": { + "onChangeInput": { + "type": "JSFunction", + "value": "function onChangeInput(eventArgs,args0,args1,args2) {\n console.log('onChangeInput', eventArgs);\n}" + }, + "onChangeNumber": { + "type": "JSFunction", + "value": "function onChangeNumber(eventArgs, args0, args1, args2) {\n console.log('onChangeNumber', eventArgs)\n}" + } + }, + "componentName": "Page", + "css": "", + "props": {}, + "lifeCycles": {}, + "children": [ + { + "componentName": "div", + "props": {}, + "id": "85375559", + "children": [ + { + "componentName": "TinyGrid", + "props": { + "editConfig": { + "trigger": "click", + "mode": "cell", + "showStatus": true + }, + "columns": [ + { + "type": "index", + "width": 60 + }, + { + "type": "selection", + "width": 60 + }, + { + "field": "employees", + "title": "员工数", + "slots": { + "default": { + "type": "JSSlot", + "value": [ + { + "componentName": "div", + "id": "44523622", + "children": [ + { + "componentName": "TinyNumeric", + "props": { + "allow-empty": true, + "placeholder": "请输入", + "controlsPosition": "right", + "step": 1, + "modelValue": { + "type": "JSExpression", + "value": "row.employees", + "model": true + }, + "onChange": { + "type": "JSExpression", + "value": "this.onChangeNumber", + "params": ["row", "column", "rowIndex"] + } + }, + "id": "62166343" + } + ] + } + ], + "params": ["row", "column", "rowIndex"] + } + } + }, + { + "field": "created_date", + "title": "创建日期" + }, + { + "field": "city", + "title": "城市", + "slots": { + "default": { + "type": "JSSlot", + "value": [ + { + "componentName": "div", + "id": "66326314", + "children": [ + { + "componentName": "TinyInput", + "props": { + "placeholder": "请输入", + "modelValue": { + "type": "JSExpression", + "value": "row.city", + "model": true + }, + "onChange": { + "type": "JSExpression", + "value": "this.onChangeInput", + "params": ["row", "column", "rowIndex"] + } + }, + "id": "22396a2a" + } + ] + } + ], + "params": ["row", "column", "rowIndex"] + } + } + } + ], + "data": [ + { + "id": "1", + "name": "GFD科技有限公司", + "city": "福州", + "employees": 800, + "created_date": "2014-04-30 00:56:00", + "boole": false + }, + { + "id": "2", + "name": "WWW科技有限公司", + "city": "深圳", + "employees": 300, + "created_date": "2016-07-08 12:36:22", + "boole": true + } + ] + }, + "id": "63623253" + } + ] + } + ], + "dataSource": { + "list": [] + }, + "utils": [], + "bridge": [], + "inputs": [], + "outputs": [], + "fileName": "slotModelValueTest" +} diff --git a/packages/vue-generator/test/testcases/sfc/slotModelValue/slotModel.test.js b/packages/vue-generator/test/testcases/sfc/slotModelValue/slotModel.test.js new file mode 100644 index 0000000000..c1e48b6f01 --- /dev/null +++ b/packages/vue-generator/test/testcases/sfc/slotModelValue/slotModel.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 onUpdate:modelValue event', async () => { + const res = genSFCWithDefaultPlugin(schema, componentsMap) + const formattedCode = formatCode(res, 'vue') + + await expect(formattedCode).toMatchFileSnapshot('./expected/slotModelValueTest.vue') +})