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