forked from opentiny/tiny-engine
fix: jsx slot modelvalue can't update value (#734)
* fix: jsx slot modelvalue can't update value * fix: add unit test for updateModel event
This commit is contained in:
parent
c35c34e036
commit
9ccfc9fa8a
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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` }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
|
@ -0,0 +1,96 @@
|
|||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<tiny-grid
|
||||
:editConfig="{ trigger: 'click', mode: 'cell', showStatus: true }"
|
||||
:columns="state.columns"
|
||||
: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
|
||||
}
|
||||
]"
|
||||
></tiny-grid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="jsx">
|
||||
import { Grid as TinyGrid, Numeric as TinyNumeric, Input as TinyInput } 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({
|
||||
columns: [
|
||||
{ type: 'index', width: 60 },
|
||||
{ type: 'selection', width: 60 },
|
||||
{
|
||||
field: 'employees',
|
||||
title: '员工数',
|
||||
slots: {
|
||||
default: ({ row, column, rowIndex }, h) => (
|
||||
<div>
|
||||
<TinyNumeric
|
||||
allow-empty={true}
|
||||
placeholder="请输入"
|
||||
controlsPosition="right"
|
||||
step={1}
|
||||
modelValue={row.employees}
|
||||
onChange={(...eventArgs) => onChangeNumber(eventArgs, row, column, rowIndex)}
|
||||
onUpdate:modelValue={(value) => (row.employees = value)}
|
||||
></TinyNumeric>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{ field: 'created_date', title: '创建日期' },
|
||||
{
|
||||
field: 'city',
|
||||
title: '城市',
|
||||
slots: {
|
||||
default: ({ row, column, rowIndex }, h) => (
|
||||
<div>
|
||||
<TinyInput
|
||||
placeholder="请输入"
|
||||
modelValue={row.city}
|
||||
onChange={(...eventArgs) => onChangeInput(eventArgs, row, column, rowIndex)}
|
||||
onUpdate:modelValue={(value) => (row.city = value)}
|
||||
></TinyInput>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
wrap({ state })
|
||||
|
||||
const onChangeInput = wrap(function onChangeInput(eventArgs, args0, args1, args2) {
|
||||
console.log('onChangeInput', eventArgs)
|
||||
})
|
||||
const onChangeNumber = wrap(function onChangeNumber(eventArgs, args0, args1, args2) {
|
||||
console.log('onChangeNumber', eventArgs)
|
||||
})
|
||||
|
||||
wrap({ onChangeInput, onChangeNumber })
|
||||
</script>
|
||||
<style scoped></style>
|
|
@ -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"
|
||||
}
|
|
@ -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')
|
||||
})
|
Loading…
Reference in New Issue