feat(grid-select): [grid-select] add grid select component and implement single/multiple select features (#2509)
* feat(grid-select): add basic grid-select * feat(grid-select): add single/multiple select
This commit is contained in:
parent
43723c1475
commit
a3e30c3ddc
|
@ -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<string|number>',
|
||||
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<string, any>
|
||||
columns: {
|
||||
type: string
|
||||
field: string
|
||||
title: string
|
||||
width: number
|
||||
}[] // 表格列数据,用法同 Grid
|
||||
}
|
||||
`
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<tiny-grid-select v-model="value" :grid-op="gridOpSingle" value-field="id" text-field="city"></tiny-grid-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { TinyGridSelect } from '@opentiny/vue'
|
||||
|
||||
const value = ref('')
|
||||
|
||||
const gridOpSingle = reactive({
|
||||
data: [
|
||||
{ id: '001', area: '华南区', province: '广东省', city: '广州市' },
|
||||
{ id: '002', area: '华南区', province: '广东省', city: '深圳市' },
|
||||
{ id: '003', area: '华南区', province: '广东省', city: '珠海市' },
|
||||
{ id: '004', area: '华南区', province: '广东省', city: '佛山市' },
|
||||
{ id: '005', area: '华南区', province: '广东省', city: '中山市' }
|
||||
],
|
||||
columns: [
|
||||
{ type: 'radio', title: '' },
|
||||
{ field: 'area', title: '区域', width: 90 },
|
||||
{ field: 'province', title: '省份', width: 60 },
|
||||
{ field: 'city', title: '城市', width: 60 }
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tiny-grid-select {
|
||||
width: 280px;
|
||||
}
|
||||
</style>
|
|
@ -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/)
|
||||
})
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<tiny-grid-select v-model="value" :grid-op="gridOpSingle" value-field="id" text-field="city"></tiny-grid-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { TinyGridSelect } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinyGridSelect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: '',
|
||||
gridOpSingle: {
|
||||
data: [
|
||||
{ id: '001', area: '华南区', province: '广东省', city: '广州市' },
|
||||
{ id: '002', area: '华南区', province: '广东省', city: '深圳市' },
|
||||
{ id: '003', area: '华南区', province: '广东省', city: '珠海市' },
|
||||
{ id: '004', area: '华南区', province: '广东省', city: '佛山市' },
|
||||
{ id: '005', area: '华南区', province: '广东省', city: '中山市' }
|
||||
],
|
||||
columns: [
|
||||
{ type: 'radio', title: '' },
|
||||
{ field: 'area', title: '区域', width: 90 },
|
||||
{ field: 'province', title: '省份', width: 60 },
|
||||
{ field: 'city', title: '城市', width: 60 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tiny-grid-select {
|
||||
width: 280px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<tiny-grid-select
|
||||
v-model="value"
|
||||
multiple
|
||||
:grid-op="gridOpMulti"
|
||||
value-field="id"
|
||||
text-field="city"
|
||||
></tiny-grid-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { GridSelect as TinyGridSelect } from '@opentiny/vue'
|
||||
|
||||
const value = ref('')
|
||||
|
||||
const gridOpMulti = reactive({
|
||||
data: [
|
||||
{ id: '001', area: '华南区', province: '广东省', city: '广州市' },
|
||||
{ id: '002', area: '华南区', province: '广东省', city: '深圳市' },
|
||||
{ id: '003', area: '华南区', province: '广东省', city: '珠海市' },
|
||||
{ id: '004', area: '华南区', province: '广东省', city: '佛山市' },
|
||||
{ id: '005', area: '华南区', province: '广东省', city: '中山市' }
|
||||
],
|
||||
columns: [
|
||||
{ type: 'selection', title: '' },
|
||||
{ field: 'area', title: '区域', width: 90 },
|
||||
{ field: 'province', title: '省份', width: 60 },
|
||||
{ field: 'city', title: '城市', width: 60 }
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tiny-grid-select {
|
||||
width: 280px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<tiny-grid-select
|
||||
v-model="value"
|
||||
multiple
|
||||
:grid-op="gridOpMulti"
|
||||
value-field="id"
|
||||
text-field="city"
|
||||
></tiny-grid-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { TinyGridSelect } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinyGridSelect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: '',
|
||||
treeOp: {
|
||||
data: [
|
||||
{ id: '001', area: '华南区', province: '广东省', city: '广州市' },
|
||||
{ id: '002', area: '华南区', province: '广东省', city: '深圳市' },
|
||||
{ id: '003', area: '华南区', province: '广东省', city: '珠海市' },
|
||||
{ id: '004', area: '华南区', province: '广东省', city: '佛山市' },
|
||||
{ id: '005', area: '华南区', province: '广东省', city: '中山市' }
|
||||
],
|
||||
columns: [
|
||||
{ type: 'selection', title: '' },
|
||||
{ field: 'area', title: '区域', width: 90 },
|
||||
{ field: 'province', title: '省份', width: 60 },
|
||||
{ field: 'city', title: '城市', width: 60 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tiny-grid-select {
|
||||
width: 280px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: GridSelect 下拉表格选择器
|
||||
---
|
||||
|
||||
# GridSelect 下拉表格选择器
|
||||
|
||||
结合了 BaseSelect 和 Grid 组件的选择器,用于从一个下拉表格中选择一个或多个选项。
|
|
@ -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.
|
|
@ -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':
|
||||
'<p>最基础的用法,通过 <code>grid-op</code> 设置下拉表格的数据源,<code>v-model</code> 设置绑定值。</p>',
|
||||
'en-US': ''
|
||||
},
|
||||
codeFiles: ['basic-usage.vue']
|
||||
},
|
||||
{
|
||||
demoId: 'multiple',
|
||||
name: {
|
||||
'zh-CN': '多选',
|
||||
'en-US': 'Multiple'
|
||||
},
|
||||
desc: {
|
||||
'zh-CN':
|
||||
'<p>通过 <code>multiple</code> 属性启用多选功能,此时 <code>v-model</code> 的值为当前选中值所组成的数组,默认选中值会以标签形式展示。</p>',
|
||||
'en-US': ''
|
||||
},
|
||||
codeFiles: ['multiple.vue']
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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' },
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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:~",
|
||||
|
|
|
@ -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
|
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<tiny-base-select ref="baseSelectRef" class="tiny-grid-select" v-model="state.value" :multiple="multiple">
|
||||
<template #panel>
|
||||
<tiny-grid
|
||||
ref="gridRef"
|
||||
auto-resize
|
||||
:row-id="valueField"
|
||||
:highlight-current-row="true"
|
||||
:columns="state.gridData.columns"
|
||||
:data="state.gridData"
|
||||
@select-all="selectChange"
|
||||
@select-change="selectChange"
|
||||
@radio-change="radioChange"
|
||||
@mousedown.stop
|
||||
v-bind="state.gridData"
|
||||
></tiny-grid>
|
||||
</template>
|
||||
</tiny-base-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { $prefix, defineComponent, setup } from '@opentiny/vue-common'
|
||||
import { renderless, api } from '@opentiny/vue-renderless/grid-select/vue'
|
||||
import Grid from '@opentiny/vue-grid'
|
||||
import BaseSelect from '@opentiny/vue-base-select'
|
||||
|
||||
export default defineComponent({
|
||||
name: $prefix + 'GridSelect',
|
||||
components: {
|
||||
TinyGrid: Grid,
|
||||
TinyBaseSelect: BaseSelect
|
||||
},
|
||||
props: {
|
||||
clearable: Boolean,
|
||||
filterable: Boolean,
|
||||
filterMethod: Function,
|
||||
gridOp: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
modelValue: {},
|
||||
multiple: Boolean,
|
||||
textField: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
valueField: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
}
|
||||
},
|
||||
setup(props, context) {
|
||||
return setup({ props, context, renderless, api })
|
||||
}
|
||||
})
|
||||
</script>
|
Loading…
Reference in New Issue