diff --git a/examples/sites/demos/apis/grid-select.js b/examples/sites/demos/apis/grid-select.js new file mode 100644 index 000000000..bfc456fed --- /dev/null +++ b/examples/sites/demos/apis/grid-select.js @@ -0,0 +1,84 @@ +export default { + mode: ['pc'], + apis: [ + { + name: 'grid-select', + type: 'component', + props: [ + { + name: 'grid-op', + typeAnchorName: 'IGridOption', + type: 'IGridOption', + defaultValue: '', + desc: { + 'zh-CN': '下拉表格时,内置表格组件的配置,用法同 Grid 组件。', + 'en-US': '' + }, + mode: ['pc'], + pcDemo: 'basic-usage' + }, + { + name: 'modelValue / v-model', + type: 'string | number | Array', + defaultValue: '', + desc: { + 'zh-CN': '绑定值', + 'en-US': 'Bind value' + }, + mode: ['pc'], + pcDemo: 'basic-usage' + }, + { + name: 'multiple', + type: 'boolean', + defaultValue: 'false', + desc: { + 'zh-CN': '是否允许选择多个选项', + 'en-US': 'Allow multiple options to be selected' + }, + mode: ['pc'], + pcDemo: 'multiple' + }, + { + name: 'text-field', + type: 'string', + defaultValue: "''", + desc: { + 'zh-CN': '显示值字段', + 'en-US': 'Show Value Fields' + }, + mode: ['pc'], + pcDemo: 'basic-usage' + }, + { + name: 'value-field', + type: 'string', + defaultValue: "''", + desc: { + 'zh-CN': '绑定值字段', + 'en-US': 'Bind Value Field' + }, + mode: ['pc'], + pcDemo: 'basic-usage' + } + ] + } + ], + types: [ + { + name: 'IGridOption', + type: 'interface', + code: ` +interface IGridOption { + data: Record + columns: { + type: string + field: string + title: string + width: number + }[] // 表格列数据,用法同 Grid +} +` + } + ] +} diff --git a/examples/sites/demos/pc/app/grid-select/basic-usage-composition-api.vue b/examples/sites/demos/pc/app/grid-select/basic-usage-composition-api.vue new file mode 100644 index 000000000..3ac081c4a --- /dev/null +++ b/examples/sites/demos/pc/app/grid-select/basic-usage-composition-api.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/examples/sites/demos/pc/app/grid-select/basic-usage.spec.ts b/examples/sites/demos/pc/app/grid-select/basic-usage.spec.ts new file mode 100644 index 000000000..441d1635b --- /dev/null +++ b/examples/sites/demos/pc/app/grid-select/basic-usage.spec.ts @@ -0,0 +1,25 @@ +import { expect, test } from '@playwright/test' + +test('测试下拉表格单选', async ({ page }) => { + page.on('pageerror', (exception) => expect(exception).toBeNull()) + await page.goto('grid-select#basic-usage') + + const wrap = page.locator('#basic-usage') + const select = wrap.locator('.tiny-grid-select').nth(0) + const input = select.locator('.tiny-input__inner') + const dropdown = page.locator('body > .tiny-select-dropdown') + const suffixSvg = select.locator('.tiny-base-select__caret') + const row = dropdown.getByRole('row') + + await expect(suffixSvg).toHaveCount(1) + await expect(suffixSvg).toBeVisible() + + await input.click() + await expect(dropdown).toBeVisible() + await expect(row).toHaveCount(6) + + await row.nth(1).getByRole('cell').first().click() + await expect(input).toHaveValue('广州市') + await input.click() + await expect(row.filter({ hasText: '广州市' })).toHaveClass(/tiny-grid-body__row row__radio/) +}) diff --git a/examples/sites/demos/pc/app/grid-select/basic-usage.vue b/examples/sites/demos/pc/app/grid-select/basic-usage.vue new file mode 100644 index 000000000..853b49b5f --- /dev/null +++ b/examples/sites/demos/pc/app/grid-select/basic-usage.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/examples/sites/demos/pc/app/grid-select/multiple-composition-api.vue b/examples/sites/demos/pc/app/grid-select/multiple-composition-api.vue new file mode 100644 index 000000000..38db08c1c --- /dev/null +++ b/examples/sites/demos/pc/app/grid-select/multiple-composition-api.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/examples/sites/demos/pc/app/grid-select/multiple.vue b/examples/sites/demos/pc/app/grid-select/multiple.vue new file mode 100644 index 000000000..78ff4c778 --- /dev/null +++ b/examples/sites/demos/pc/app/grid-select/multiple.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/examples/sites/demos/pc/app/grid-select/webdoc/grid-select.cn.md b/examples/sites/demos/pc/app/grid-select/webdoc/grid-select.cn.md new file mode 100644 index 000000000..bd7d7b5da --- /dev/null +++ b/examples/sites/demos/pc/app/grid-select/webdoc/grid-select.cn.md @@ -0,0 +1,7 @@ +--- +title: GridSelect 下拉表格选择器 +--- + +# GridSelect 下拉表格选择器 + +结合了 BaseSelect 和 Grid 组件的选择器,用于从一个下拉表格中选择一个或多个选项。 diff --git a/examples/sites/demos/pc/app/grid-select/webdoc/grid-select.en.md b/examples/sites/demos/pc/app/grid-select/webdoc/grid-select.en.md new file mode 100644 index 000000000..5772a2283 --- /dev/null +++ b/examples/sites/demos/pc/app/grid-select/webdoc/grid-select.en.md @@ -0,0 +1,7 @@ +--- +title: GridSelect +--- + +# GridSelect + +A selector that combines the BaseSelect and Grid components to select one or more options from a drop-down table. diff --git a/examples/sites/demos/pc/app/grid-select/webdoc/grid-select.js b/examples/sites/demos/pc/app/grid-select/webdoc/grid-select.js new file mode 100644 index 000000000..d751cd5e3 --- /dev/null +++ b/examples/sites/demos/pc/app/grid-select/webdoc/grid-select.js @@ -0,0 +1,35 @@ +export default { + column: '2', + owner: '', + meta: { + experimental: '3.20.0' + }, + demos: [ + { + demoId: 'basic-usage', + name: { + 'zh-CN': '基本用法', + 'en-US': 'Basic Usage' + }, + desc: { + 'zh-CN': + '

最基础的用法,通过 grid-op 设置下拉表格的数据源,v-model 设置绑定值。

', + 'en-US': '' + }, + codeFiles: ['basic-usage.vue'] + }, + { + demoId: 'multiple', + name: { + 'zh-CN': '多选', + 'en-US': 'Multiple' + }, + desc: { + 'zh-CN': + '

通过 multiple 属性启用多选功能,此时 v-model 的值为当前选中值所组成的数组,默认选中值会以标签形式展示。

', + 'en-US': '' + }, + codeFiles: ['multiple.vue'] + } + ] +} diff --git a/examples/sites/demos/pc/menus.js b/examples/sites/demos/pc/menus.js index 9362db4ce..2fef4cf4b 100644 --- a/examples/sites/demos/pc/menus.js +++ b/examples/sites/demos/pc/menus.js @@ -136,6 +136,14 @@ export const cmpMenus = [ { 'nameCn': '文件上传', 'name': 'FileUpload', 'key': 'file-upload' }, { 'nameCn': '富文本', 'name': 'FluentEditor', 'key': 'fluent-editor' }, { 'nameCn': '表单', 'name': 'Form', 'key': 'form' }, + { + 'nameCn': '下拉表格选择器', + 'name': 'GridSelect', + 'key': 'grid-select', + 'meta': { + 'experimental': '3.20.0' + } + }, { 'nameCn': '输入框', 'name': 'Input', 'key': 'input' }, { 'nameCn': ' IP地址输入框', 'name': 'IpAddress', 'key': 'ip-address' }, { 'nameCn': '数字输入框', 'name': 'Numeric', 'key': 'numeric' }, diff --git a/examples/sites/src/views/components/cmp-config.js b/examples/sites/src/views/components/cmp-config.js index 7b6b712d4..94c9124c8 100644 --- a/examples/sites/src/views/components/cmp-config.js +++ b/examples/sites/src/views/components/cmp-config.js @@ -28,7 +28,7 @@ const faqMdConfig = { } const getWebdocPath = (path) => { - if (path?.startsWith('grid-')) { + if (path?.startsWith('grid-') && path !== 'grid-select') { return 'grid' } else if (path?.startsWith('chart-')) { return 'chart' diff --git a/packages/modules.json b/packages/modules.json index 2670f6f83..9ed51496b 100644 --- a/packages/modules.json +++ b/packages/modules.json @@ -1322,6 +1322,12 @@ "type": "component", "exclude": false }, + "GridSelect": { + "path": "vue/src/grid-select/index.ts", + "type": "component", + "exclude": false, + "mode": ["pc"] + }, "GridToolbar": { "path": "vue/src/grid-toolbar/index.ts", "type": "component", diff --git a/packages/renderless/src/grid-select/index.ts b/packages/renderless/src/grid-select/index.ts new file mode 100644 index 000000000..6dbfdd6f6 --- /dev/null +++ b/packages/renderless/src/grid-select/index.ts @@ -0,0 +1,38 @@ +export const radioChange = + ({ props, vm, emit }) => + ({ row }) => { + if (!props.multiple) { + vm.$refs.baseSelectRef.updateSelectedData({ + ...row, + currentLabel: row[props.textField], + value: row[props.valueField], + state: { + currentLabel: row[props.textField] + } + }) + + vm.$refs.baseSelectRef.hidePanel() + + emit('update:modelValue', row) + emit('change', row) + } + } + +export const selectChange = + ({ props, vm, emit }) => + ({ $table, selection, checked, row }) => { + if (props.multiple) { + vm.$refs.baseSelectRef.updateSelectedData( + selection.map((node) => { + return { + ...node, + currentLabel: node[props.textField], + value: node[props.valueField] + } + }) + ) + + emit('update:modelValue', selection) + emit('change', selection) + } + } diff --git a/packages/renderless/src/grid-select/vue.ts b/packages/renderless/src/grid-select/vue.ts new file mode 100644 index 000000000..d45bea784 --- /dev/null +++ b/packages/renderless/src/grid-select/vue.ts @@ -0,0 +1,20 @@ +import { radioChange, selectChange } from './index' + +export const api = ['state', 'radioChange', 'selectChange'] + +export const renderless = (props, { reactive }, { vm, emit }) => { + const api = {} + + const state = reactive({ + value: props.modelValue, + gridData: props.gridOp + }) + + Object.assign(api, { + state, + radioChange: radioChange({ props, vm, emit }), + selectChange: selectChange({ props, vm, emit }) + }) + + return api +} diff --git a/packages/vue/package.json b/packages/vue/package.json index 9c8ccea19..36bd9b05e 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -113,6 +113,7 @@ "@opentiny/vue-grid": "workspace:~", "@opentiny/vue-grid-column": "workspace:~", "@opentiny/vue-grid-manager": "workspace:~", + "@opentiny/vue-grid-select": "workspace:~", "@opentiny/vue-grid-toolbar": "workspace:~", "@opentiny/vue-guide": "workspace:~", "@opentiny/vue-hrapprover": "workspace:~", diff --git a/packages/vue/src/grid-select/index.ts b/packages/vue/src/grid-select/index.ts new file mode 100644 index 000000000..b0c67e66c --- /dev/null +++ b/packages/vue/src/grid-select/index.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2022 - present TinyVue Authors. + * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ +import GridSelect from './src/pc.vue' +import { version } from './package.json' + +/* istanbul ignore next */ +GridSelect.install = function (Vue) { + Vue.component(GridSelect.name, GridSelect) +} + +GridSelect.version = version + +/* istanbul ignore next */ +if (process.env.BUILD_TARGET === 'runtime') { + if (typeof window !== 'undefined' && window.Vue) { + GridSelect.install(window.Vue) + } +} + +export default GridSelect diff --git a/packages/vue/src/grid-select/package.json b/packages/vue/src/grid-select/package.json new file mode 100644 index 000000000..d25bef50a --- /dev/null +++ b/packages/vue/src/grid-select/package.json @@ -0,0 +1,24 @@ +{ + "name": "@opentiny/vue-grid-select", + "version": "3.19.0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "sideEffects": false, + "type": "module", + "devDependencies": { + "@opentiny-internal/vue-test-utils": "workspace:*", + "vitest": "^0.31.0" + }, + "scripts": { + "build": "pnpm -w build:ui $npm_package_name", + "//postversion": "pnpm build" + }, + "dependencies": { + "@opentiny/vue-common": "workspace:~", + "@opentiny/vue-renderless": "workspace:~", + "@opentiny/vue-base-select": "workspace:~", + "@opentiny/vue-grid": "workspace:~" + }, + "license": "MIT" +} diff --git a/packages/vue/src/grid-select/src/pc.vue b/packages/vue/src/grid-select/src/pc.vue new file mode 100644 index 000000000..cbba91dbe --- /dev/null +++ b/packages/vue/src/grid-select/src/pc.vue @@ -0,0 +1,56 @@ + + +