@opentinyvue/vue-docs 3.29.2 → 3.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/demos/apis/action-menu.js +22 -0
  2. package/demos/apis/alert.js +19 -22
  3. package/demos/apis/button-group.js +15 -0
  4. package/demos/apis/calendar-view.js +5 -5
  5. package/demos/apis/cascader-view.js +5 -5
  6. package/demos/apis/dialog-box.js +39 -38
  7. package/demos/apis/drawer.js +15 -0
  8. package/demos/apis/fluent-editor.js +30 -0
  9. package/demos/apis/fullscreen.js +10 -11
  10. package/demos/apis/grid.js +100 -17
  11. package/demos/apis/link.js +11 -0
  12. package/demos/apis/modal.js +32 -30
  13. package/demos/apis/notify.js +13 -13
  14. package/demos/apis/select.js +14 -2
  15. package/demos/apis/tree.js +23 -23
  16. package/demos/mobile-first/app/button-group/display-mode.vue +31 -0
  17. package/demos/mobile-first/app/button-group/webdoc/button-group.js +13 -0
  18. package/demos/mobile-first/app/calendar-view/webdoc/calendar-view.js +5 -4
  19. package/demos/pc/app/base-select/copy-multi.spec.ts +1 -1
  20. package/demos/pc/app/button-group/display-mode-composition-api.vue +23 -0
  21. package/demos/pc/app/button-group/display-mode.spec.ts +0 -0
  22. package/demos/pc/app/button-group/display-mode.vue +31 -0
  23. package/demos/pc/app/button-group/sup-composition-api.vue +1 -0
  24. package/demos/pc/app/button-group/sup.vue +1 -0
  25. package/demos/pc/app/button-group/webdoc/button-group.js +14 -0
  26. package/demos/pc/app/calendar-view/custom-day-bg-color.vue +10 -0
  27. package/demos/pc/app/calendar-view/webdoc/calendar-view.js +4 -4
  28. package/demos/pc/app/checkbox/checked-composition-api.vue +3 -3
  29. package/demos/pc/app/checkbox/checked.vue +3 -3
  30. package/demos/pc/app/drawer/basic-usage.spec.ts +4 -3
  31. package/demos/pc/app/drawer/placement.spec.ts +4 -4
  32. package/demos/pc/app/drawer/show-header.spec.ts +3 -2
  33. package/demos/pc/app/dropdown/show-icon-composition-api.vue +1 -1
  34. package/demos/pc/app/dropdown/show-icon.vue +1 -1
  35. package/demos/pc/app/flowchart/basic-usage-composition-api.vue +10 -260
  36. package/demos/pc/app/flowchart/basic-usage.spec.ts +2 -1
  37. package/demos/pc/app/flowchart/basic-usage.vue +4 -249
  38. package/demos/pc/app/flowchart/slots-composition-api.vue +182 -0
  39. package/demos/pc/app/flowchart/slots.spec.ts +24 -0
  40. package/demos/pc/app/flowchart/slots.vue +193 -0
  41. package/demos/pc/app/flowchart/webdoc/flowchart.js +14 -2
  42. package/demos/pc/app/form/group-form-composition-api.vue +2 -2
  43. package/demos/pc/app/form/group-form.vue +2 -2
  44. package/demos/pc/app/grid/filter/advanced-filter-composition-api.vue +1 -1
  45. package/demos/pc/app/grid/filter/advanced-filter.spec.js +1 -0
  46. package/demos/pc/app/grid/filter/advanced-filter.vue +1 -1
  47. package/demos/pc/app/grid/filter/custom-filter-composition-api.vue +46 -23
  48. package/demos/pc/app/grid/filter/custom-filter.spec.js +1 -1
  49. package/demos/pc/app/grid/filter/custom-filter.vue +47 -24
  50. package/demos/pc/app/grid/filter/default-filter.spec.ts +1 -0
  51. package/demos/pc/app/grid/filter/filter-label-value-composition-api.vue +71 -0
  52. package/demos/pc/app/grid/filter/filter-label-value.spec.ts +27 -0
  53. package/demos/pc/app/grid/filter/filter-label-value.vue +78 -0
  54. package/demos/pc/app/grid/filter/input-filter-custom-component-composition-api.vue +116 -0
  55. package/demos/pc/app/grid/filter/input-filter-custom-component.spec.ts +20 -0
  56. package/demos/pc/app/grid/filter/input-filter-custom-component.vue +126 -0
  57. package/demos/pc/app/grid/filter/layout-order-filter-composition-api.vue +114 -0
  58. package/demos/pc/app/grid/filter/layout-order-filter.spec.ts +31 -0
  59. package/demos/pc/app/grid/filter/layout-order-filter.vue +123 -0
  60. package/demos/pc/app/grid/webdoc/grid-filter.js +89 -41
  61. package/demos/pc/app/input/resize.spec.ts +2 -2
  62. package/demos/pc/app/link/custom-icon-composition-api.vue +1 -1
  63. package/demos/pc/app/link/custom-icon.vue +1 -1
  64. package/demos/pc/app/link/target-composition-api.vue +24 -0
  65. package/demos/pc/app/link/target.spec.ts +41 -0
  66. package/demos/pc/app/link/target.vue +30 -0
  67. package/demos/pc/app/link/webdoc/link.js +14 -0
  68. package/demos/pc/app/nav-menu/overflow.spec.ts +1 -1
  69. package/demos/pc/app/select/copy-multi.spec.ts +1 -1
  70. package/demos/pc/app/select-wrapper/copy-multi.spec.ts +2 -1
  71. package/demos/pc/app/tabs/tabs-events-close-composition-api.vue +8 -2
  72. package/demos/pc/app/tabs/tabs-events-close.vue +8 -2
  73. package/demos/pc/app/tag/basic-usage.spec.ts +1 -1
  74. package/demos/pc/app/tag/color-border.spec.ts +2 -2
  75. package/demos/pc/app/tag/max-width.spec.ts +1 -1
  76. package/demos/pc/app/tag/size.spec.ts +3 -3
  77. package/demos/pc/app/tag/slot-default.spec.ts +3 -3
  78. package/demos/pc/app/tag-group/basic-usage.spec.js +5 -5
  79. package/demos/pc/app/tag-group/tag-group-effect.spec.js +10 -10
  80. package/demos/pc/app/tag-group/tag-group-event.spec.js +1 -1
  81. package/demos/pc/menus.js +3 -3
  82. package/demos/pc/webdoc/changelog-en.md +121 -86
  83. package/demos/pc/webdoc/changelog.md +121 -86
  84. package/demos/pc/webdoc/develop-demo-en.md +13 -13
  85. package/demos/pc/webdoc/import-components.md +1 -1
  86. package/demos/pc/webdoc/introduce.md +2 -0
  87. package/demos/pc/webdoc/skills-en.md +107 -0
  88. package/demos/pc/webdoc/skills.md +107 -0
  89. package/demos/pc/webdoc/theme-en.md +82 -79
  90. package/demos/saas/menus.js +1 -0
  91. package/package.json +19 -20
  92. package/src/i18n/index.js +1 -2
  93. package/src/main.js +8 -5
  94. package/src/views/components-doc/common.vue +0 -25
  95. package/demos/pc/app/grid/webdoc/grid-ai-agent.js +0 -23
  96. package/demos/pc/webdoc/mcp-en.md +0 -101
  97. package/demos/pc/webdoc/mcp.md +0 -101
  98. package/src/components/mcp-docs.vue +0 -33
  99. package/src/composable/useTinyRemoter.ts +0 -176
@@ -3,28 +3,23 @@
3
3
  <tiny-grid-column type="index" width="60"></tiny-grid-column>
4
4
  <tiny-grid-column field="name" title="名称" :filter="customFilter">
5
5
  <template #filter="data">
6
- <input v-model="customFilterData.input" />
7
- <button @click="data.context.commitFilter(customFilter)">confirm</button>
8
- <button
9
- @click="
10
- () => {
11
- customFilterData.input = ''
12
- data.context.clearFilter()
13
- }
14
- "
15
- >
16
- clear
17
- </button>
18
- <button
19
- @click="
20
- () => {
21
- customFilterData.input = ''
22
- data.context.resetFilter()
23
- }
24
- "
25
- >
26
- reset
27
- </button>
6
+ <ul class="tiny-grid__filter-panel custom-filter-panel">
7
+ <li class="filter-option__input">
8
+ <tiny-input
9
+ v-model="customFilterData.input"
10
+ placeholder="输入名称关键词筛选"
11
+ clearable
12
+ size="small"
13
+ />
14
+ </li>
15
+ <li class="tiny-grid__filter-option filter-option__btns">
16
+ <tiny-button type="primary" size="small" @click="data.context.commitFilter(customFilter)">
17
+ 确定
18
+ </tiny-button>
19
+ <tiny-button size="small" @click="handleReset(data.context)">重置</tiny-button>
20
+ <tiny-button size="small" @click="handleClear(data.context)">清除全部</tiny-button>
21
+ </li>
22
+ </ul>
28
23
  </template>
29
24
  </tiny-grid-column>
30
25
  <tiny-grid-column field="area" title="区域"></tiny-grid-column>
@@ -34,12 +29,14 @@
34
29
  </template>
35
30
 
36
31
  <script lang="jsx">
37
- import { TinyGrid, TinyGridColumn } from '@opentiny/vue'
32
+ import { TinyGrid, TinyGridColumn, TinyInput, TinyButton } from '@opentiny/vue'
38
33
 
39
34
  export default {
40
35
  components: {
41
36
  TinyGrid,
42
- TinyGridColumn
37
+ TinyGridColumn,
38
+ TinyInput,
39
+ TinyButton
43
40
  },
44
41
  data() {
45
42
  return {
@@ -96,7 +93,33 @@ export default {
96
93
  methods: {
97
94
  customFilterMethod({ row }) {
98
95
  return row.name.includes(this.customFilterData.input)
96
+ },
97
+ handleReset(context) {
98
+ this.customFilterData.input = ''
99
+ context.resetFilter()
100
+ },
101
+ handleClear(context) {
102
+ this.customFilterData.input = ''
103
+ context.clearFilter()
99
104
  }
100
105
  }
101
106
  }
102
107
  </script>
108
+
109
+ <style scoped>
110
+ .custom-filter-panel {
111
+ padding: 12px 16px;
112
+ min-width: 260px;
113
+ }
114
+ .custom-filter-panel .filter-option__input {
115
+ margin-bottom: 12px;
116
+ }
117
+ .custom-filter-panel .filter-option__input :deep(.tiny-input) {
118
+ width: 100%;
119
+ }
120
+ .custom-filter-panel .filter-option__btns {
121
+ display: flex;
122
+ gap: 8px;
123
+ justify-content: flex-end;
124
+ }
125
+ </style>
@@ -3,6 +3,7 @@ import { test, expect } from '@playwright/test'
3
3
  test('表格过滤', async ({ page }) => {
4
4
  page.on('pageerror', (exception) => expect(exception).toBeNull())
5
5
  await page.goto('grid-filter#filter-default-filter')
6
+ await page.setViewportSize({ width: 1920, height: 1980 })
6
7
  await page
7
8
  .getByRole('cell', { name: '公司名称鼠标移入可以动态提示 tooltip,公司名称鼠标移入可以动态提示 tooltip' })
8
9
  .getByRole('img')
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <tiny-grid :data="tableData" @filter-change="filterChangeEvent">
3
+ <tiny-grid-column type="index" width="60"></tiny-grid-column>
4
+ <tiny-grid-column
5
+ field="status"
6
+ title="状态"
7
+ :filter="statusFilter"
8
+ :formatter="statusFormatter"
9
+ ></tiny-grid-column>
10
+ <tiny-grid-column
11
+ field="region"
12
+ title="区域"
13
+ :filter="regionFilter"
14
+ :formatter="regionFormatter"
15
+ ></tiny-grid-column>
16
+ </tiny-grid>
17
+ </template>
18
+
19
+ <script setup>
20
+ import { ref } from 'vue'
21
+ import { TinyGrid, TinyGridColumn, TinyModal } from '@opentiny/vue'
22
+
23
+ const tableData = ref([
24
+ { id: '1', status: 'active', region: 'east' },
25
+ { id: '2', status: 'inactive', region: 'south' },
26
+ { id: '3', status: 'active', region: 'west' },
27
+ { id: '4', status: 'pending', region: 'east' }
28
+ ])
29
+
30
+ const statusFilter = ref({
31
+ multi: true,
32
+ enumable: true,
33
+ inputFilter: false,
34
+ defaultFilter: false,
35
+ label: 'name',
36
+ value: 'code',
37
+ values: [
38
+ { code: 'active', name: '已启用' },
39
+ { code: 'inactive', name: '已停用' },
40
+ { code: 'pending', name: '待审核' }
41
+ ]
42
+ })
43
+
44
+ const regionFilter = ref({
45
+ multi: true,
46
+ enumable: true,
47
+ inputFilter: false,
48
+ defaultFilter: false,
49
+ label: 'regionName',
50
+ value: 'regionCode',
51
+ values: [
52
+ { regionCode: 'east', regionName: '华东区' },
53
+ { regionCode: 'south', regionName: '华南区' },
54
+ { regionCode: 'west', regionName: '华西区' }
55
+ ]
56
+ })
57
+
58
+ function filterChangeEvent({ filters }) {
59
+ TinyModal.message({ message: `筛选条件:${JSON.stringify(filters)}`, status: 'info' })
60
+ }
61
+
62
+ function statusFormatter({ cellValue }) {
63
+ const map = { active: '已启用', inactive: '已停用', pending: '待审核' }
64
+ return map[cellValue] || cellValue
65
+ }
66
+
67
+ function regionFormatter({ cellValue }) {
68
+ const map = { east: '华东区', south: '华南区', west: '华西区' }
69
+ return map[cellValue] || cellValue
70
+ }
71
+ </script>
@@ -0,0 +1,27 @@
1
+ import { test, expect } from '@playwright/test'
2
+
3
+ test('label/value 字段映射 - 状态列筛选', async ({ page }) => {
4
+ page.on('pageerror', (exception) => expect(exception).toBeNull())
5
+ await page.goto('grid-filter#filter-label-value-filter')
6
+
7
+ // 状态列使用 label:name value:code,选项显示 已启用/已停用/待审核
8
+ await page.getByRole('cell', { name: '状态' }).getByRole('img').first().click()
9
+ await page.getByTitle('已启用').click()
10
+ await page.getByRole('button', { name: '确定' }).click()
11
+
12
+ // 筛选 status=active 的行,应显示 2 条
13
+ await expect(page.locator('.tiny-grid-body__row:visible')).toHaveCount(2)
14
+ })
15
+
16
+ test('label/value 字段映射 - 区域列筛选', async ({ page }) => {
17
+ page.on('pageerror', (exception) => expect(exception).toBeNull())
18
+ await page.goto('grid-filter#filter-label-value-filter')
19
+
20
+ // 区域列使用 label:regionName value:regionCode
21
+ await page.getByRole('cell', { name: '区域' }).getByRole('img').first().click()
22
+ await page.getByTitle('华东区').click()
23
+ await page.getByRole('button', { name: '确定' }).click()
24
+
25
+ // 筛选 region=east 的行,应显示 2 条
26
+ await expect(page.locator('.tiny-grid-body__row:visible')).toHaveCount(2)
27
+ })
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <tiny-grid :data="tableData" @filter-change="filterChangeEvent">
3
+ <tiny-grid-column type="index" width="60"></tiny-grid-column>
4
+ <tiny-grid-column
5
+ field="status"
6
+ title="状态"
7
+ :filter="statusFilter"
8
+ :formatter="statusFormatter"
9
+ ></tiny-grid-column>
10
+ <tiny-grid-column
11
+ field="region"
12
+ title="区域"
13
+ :filter="regionFilter"
14
+ :formatter="regionFormatter"
15
+ ></tiny-grid-column>
16
+ </tiny-grid>
17
+ </template>
18
+
19
+ <script>
20
+ import { TinyGrid, TinyGridColumn, TinyModal } from '@opentiny/vue'
21
+
22
+ export default {
23
+ components: {
24
+ TinyGrid,
25
+ TinyGridColumn
26
+ },
27
+ data() {
28
+ return {
29
+ tableData: [
30
+ { id: '1', status: 'active', region: 'east' },
31
+ { id: '2', status: 'inactive', region: 'south' },
32
+ { id: '3', status: 'active', region: 'west' },
33
+ { id: '4', status: 'pending', region: 'east' }
34
+ ],
35
+ // 使用 label/value 字段映射:后端返回 code/name,前端显示 name
36
+ statusFilter: {
37
+ multi: true,
38
+ enumable: true,
39
+ inputFilter: false,
40
+ defaultFilter: false,
41
+ label: 'name',
42
+ value: 'code',
43
+ values: [
44
+ { code: 'active', name: '已启用' },
45
+ { code: 'inactive', name: '已停用' },
46
+ { code: 'pending', name: '待审核' }
47
+ ]
48
+ },
49
+ regionFilter: {
50
+ multi: true,
51
+ enumable: true,
52
+ inputFilter: false,
53
+ defaultFilter: false,
54
+ label: 'regionName',
55
+ value: 'regionCode',
56
+ values: [
57
+ { regionCode: 'east', regionName: '华东区' },
58
+ { regionCode: 'south', regionName: '华南区' },
59
+ { regionCode: 'west', regionName: '华西区' }
60
+ ]
61
+ }
62
+ }
63
+ },
64
+ methods: {
65
+ filterChangeEvent({ filters }) {
66
+ TinyModal.message({ message: `筛选条件:${JSON.stringify(filters)}`, status: 'info' })
67
+ },
68
+ statusFormatter({ cellValue }) {
69
+ const map = { active: '已启用', inactive: '已停用', pending: '待审核' }
70
+ return map[cellValue] || cellValue
71
+ },
72
+ regionFormatter({ cellValue }) {
73
+ const map = { east: '华东区', south: '华南区', west: '华西区' }
74
+ return map[cellValue] || cellValue
75
+ }
76
+ }
77
+ }
78
+ </script>
@@ -0,0 +1,116 @@
1
+ <template>
2
+ <tiny-grid :data="tableData" @filter-change="filterChangeEvent">
3
+ <tiny-grid-column type="index" width="60"></tiny-grid-column>
4
+ <tiny-grid-column field="name" title="公司名称"></tiny-grid-column>
5
+ <tiny-grid-column field="employees" title="员工数" :filter="employeesFilter"></tiny-grid-column>
6
+ <tiny-grid-column field="city" title="城市"></tiny-grid-column>
7
+ </tiny-grid>
8
+ </template>
9
+
10
+ <script setup>
11
+ import { h, ref } from 'vue'
12
+ import { TinyGrid, TinyGridColumn, TinyInput, TinyModal } from '@opentiny/vue'
13
+
14
+ // 范围输入组件:左最小值、右最大值,与 demo 同文件(使用 render 避免运行时编译)
15
+ const RangeInput = {
16
+ name: 'RangeInput',
17
+ components: { TinyInput },
18
+ props: {
19
+ modelValue: {
20
+ type: [Object, String],
21
+ default: () => ({ min: '', max: '' })
22
+ }
23
+ },
24
+ emits: ['update:modelValue'],
25
+ computed: {
26
+ rangeValue() {
27
+ const v = this.modelValue
28
+ if (!v || typeof v !== 'object') {
29
+ return { min: '', max: '' }
30
+ }
31
+ return { min: v.min ?? '', max: v.max ?? '' }
32
+ }
33
+ },
34
+ render() {
35
+ const { rangeValue } = this
36
+ return h('div', { class: 'range-input-wrapper' }, [
37
+ h(TinyInput, {
38
+ modelValue: rangeValue.min,
39
+ placeholder: '最小值',
40
+ size: 'small',
41
+ clearable: true,
42
+ 'onUpdate:modelValue': (val) => {
43
+ this.$emit('update:modelValue', { ...rangeValue, min: val })
44
+ }
45
+ }),
46
+ h('span', { class: 'range-separator' }, '~'),
47
+ h(TinyInput, {
48
+ modelValue: rangeValue.max,
49
+ placeholder: '最大值',
50
+ size: 'small',
51
+ clearable: true,
52
+ 'onUpdate:modelValue': (val) => {
53
+ this.$emit('update:modelValue', { ...rangeValue, max: val })
54
+ }
55
+ })
56
+ ])
57
+ }
58
+ }
59
+
60
+ const tableData = ref([
61
+ { id: '1', name: 'GFD 科技', city: '福州', employees: 200 },
62
+ { id: '2', name: 'WWW 科技', city: '深圳', employees: 500 },
63
+ { id: '3', name: 'RFV 公司', city: '中山', employees: 800 },
64
+ { id: '4', name: 'TGB 科技', city: '福州', employees: 350 },
65
+ { id: '5', name: 'YHN 科技', city: '韶关', employees: 1200 }
66
+ ])
67
+
68
+ const employeesFilter = ref({
69
+ layout: 'input,base',
70
+ inputFilter: {
71
+ component: RangeInput,
72
+ attrs: {},
73
+ relation: 'range',
74
+ relations: [
75
+ {
76
+ label: '范围内',
77
+ value: 'range',
78
+ method: ({ value, input }) => {
79
+ if (!input || typeof input !== 'object') return true
80
+ const num = Number(value)
81
+ const minVal = input.min === '' || input.min === undefined ? null : Number(input.min)
82
+ const maxVal = input.max === '' || input.max === undefined ? null : Number(input.max)
83
+ if (isNaN(num)) return false
84
+ if (minVal !== null && !isNaN(minVal) && num < minVal) return false
85
+ if (maxVal !== null && !isNaN(maxVal) && num > maxVal) return false
86
+ return true
87
+ }
88
+ }
89
+ ]
90
+ }
91
+ })
92
+
93
+ function filterChangeEvent({ filters }) {
94
+ TinyModal.message({ message: `筛选条件:${JSON.stringify(filters)}`, status: 'info' })
95
+ }
96
+ </script>
97
+
98
+ <style>
99
+ /* 范围输入样式:筛选面板使用 popper 渲染,需非 scoped */
100
+ .tiny-grid__filter-wrapper .range-input-wrapper {
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 10px;
104
+ padding: 4px 0 8px;
105
+ }
106
+ .tiny-grid__filter-wrapper .range-input-wrapper .tiny-input {
107
+ flex: 1;
108
+ min-width: 90px;
109
+ }
110
+ .tiny-grid__filter-wrapper .range-input-wrapper .range-separator {
111
+ font-size: 12px;
112
+ color: var(--ti-common-color-text-placeholder, #adb0b8);
113
+ white-space: nowrap;
114
+ flex-shrink: 0;
115
+ }
116
+ </style>
@@ -0,0 +1,20 @@
1
+ import { test, expect } from '@playwright/test'
2
+
3
+ test('自定义输入组件 - 范围筛选', async ({ page }) => {
4
+ page.on('pageerror', (exception) => expect(exception).toBeNull())
5
+ await page.goto('grid-filter#filter-input-custom-component')
6
+
7
+ // 点击员工数列筛选图标
8
+ await page.getByRole('cell', { name: '员工数' }).getByRole('img').first().click()
9
+
10
+ // 等待筛选面板打开并填写范围:最小值 300,最大值 600
11
+ const filterPanel = page.locator('.tiny-grid__filter-wrapper.filter__active')
12
+ await filterPanel.waitFor({ state: 'visible' })
13
+ await filterPanel.getByPlaceholder('最小值').nth(1).fill('300')
14
+ await filterPanel.getByPlaceholder('最大值').nth(1).fill('600')
15
+
16
+ await filterPanel.getByRole('button', { name: '确定' }).click()
17
+
18
+ // 应显示员工数在 300-600 之间的行:500(WWW)、350(TGB),使用 :visible 只统计可见行
19
+ await expect(page.locator('.tiny-grid-body__row:visible')).toHaveCount(2)
20
+ })
@@ -0,0 +1,126 @@
1
+ <template>
2
+ <tiny-grid :data="tableData" @filter-change="filterChangeEvent">
3
+ <tiny-grid-column type="index" width="60"></tiny-grid-column>
4
+ <tiny-grid-column field="name" title="公司名称"></tiny-grid-column>
5
+ <tiny-grid-column field="employees" title="员工数" :filter="employeesFilter"></tiny-grid-column>
6
+ <tiny-grid-column field="city" title="城市"></tiny-grid-column>
7
+ </tiny-grid>
8
+ </template>
9
+
10
+ <script>
11
+ import { h } from 'vue'
12
+ import { TinyGrid, TinyGridColumn, TinyInput, TinyModal } from '@opentiny/vue'
13
+
14
+ // 范围输入组件:左最小值、右最大值,与 demo 同文件(使用 render 避免运行时编译)
15
+ const RangeInput = {
16
+ name: 'RangeInput',
17
+ components: { TinyInput },
18
+ props: {
19
+ modelValue: {
20
+ type: [Object, String],
21
+ default: () => ({ min: '', max: '' })
22
+ }
23
+ },
24
+ emits: ['update:modelValue'],
25
+ computed: {
26
+ rangeValue() {
27
+ const v = this.modelValue
28
+ if (!v || typeof v !== 'object') {
29
+ return { min: '', max: '' }
30
+ }
31
+ return { min: v.min ?? '', max: v.max ?? '' }
32
+ }
33
+ },
34
+ render() {
35
+ const { rangeValue } = this
36
+ return h('div', { class: 'range-input-wrapper' }, [
37
+ h(TinyInput, {
38
+ modelValue: rangeValue.min,
39
+ placeholder: '最小值',
40
+ size: 'small',
41
+ clearable: true,
42
+ 'onUpdate:modelValue': (val) => {
43
+ this.$emit('update:modelValue', { ...rangeValue, min: val })
44
+ }
45
+ }),
46
+ h('span', { class: 'range-separator' }, '~'),
47
+ h(TinyInput, {
48
+ modelValue: rangeValue.max,
49
+ placeholder: '最大值',
50
+ size: 'small',
51
+ clearable: true,
52
+ 'onUpdate:modelValue': (val) => {
53
+ this.$emit('update:modelValue', { ...rangeValue, max: val })
54
+ }
55
+ })
56
+ ])
57
+ }
58
+ }
59
+
60
+ export default {
61
+ components: {
62
+ TinyGrid,
63
+ TinyGridColumn
64
+ },
65
+ data() {
66
+ return {
67
+ tableData: [
68
+ { id: '1', name: 'GFD 科技', city: '福州', employees: 200 },
69
+ { id: '2', name: 'WWW 科技', city: '深圳', employees: 500 },
70
+ { id: '3', name: 'RFV 公司', city: '中山', employees: 800 },
71
+ { id: '4', name: 'TGB 科技', city: '福州', employees: 350 },
72
+ { id: '5', name: 'YHN 科技', city: '韶关', employees: 1200 }
73
+ ],
74
+ employeesFilter: {
75
+ layout: 'input,base',
76
+ inputFilter: {
77
+ component: RangeInput,
78
+ attrs: {},
79
+ relation: 'range',
80
+ relations: [
81
+ {
82
+ label: '范围内',
83
+ value: 'range',
84
+ method: ({ value, input }) => {
85
+ if (!input || typeof input !== 'object') return true
86
+ const num = Number(value)
87
+ const minVal = input.min === '' || input.min === undefined ? null : Number(input.min)
88
+ const maxVal = input.max === '' || input.max === undefined ? null : Number(input.max)
89
+ if (isNaN(num)) return false
90
+ if (minVal !== null && !isNaN(minVal) && num < minVal) return false
91
+ if (maxVal !== null && !isNaN(maxVal) && num > maxVal) return false
92
+ return true
93
+ }
94
+ }
95
+ ]
96
+ }
97
+ }
98
+ }
99
+ },
100
+ methods: {
101
+ filterChangeEvent({ filters }) {
102
+ TinyModal.message({ message: `筛选条件:${JSON.stringify(filters)}`, status: 'info' })
103
+ }
104
+ }
105
+ }
106
+ </script>
107
+
108
+ <style>
109
+ /* 范围输入样式:筛选面板使用 popper 渲染,需非 scoped */
110
+ .tiny-grid__filter-wrapper .range-input-wrapper {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 10px;
114
+ padding: 4px 0 8px;
115
+ }
116
+ .tiny-grid__filter-wrapper .range-input-wrapper .tiny-input {
117
+ flex: 1;
118
+ min-width: 90px;
119
+ }
120
+ .tiny-grid__filter-wrapper .range-input-wrapper .range-separator {
121
+ font-size: 12px;
122
+ color: var(--ti-common-color-text-placeholder, #adb0b8);
123
+ white-space: nowrap;
124
+ flex-shrink: 0;
125
+ }
126
+ </style>
@@ -0,0 +1,114 @@
1
+ <template>
2
+ <tiny-grid :data="tableData" @filter-change="filterChangeEvent">
3
+ <tiny-grid-column type="index" width="60"></tiny-grid-column>
4
+ <tiny-grid-column field="name" title="公司名称" :filter="nameFilter"></tiny-grid-column>
5
+ <tiny-grid-column field="city" title="城市" :filter="cityFilter"></tiny-grid-column>
6
+ <tiny-grid-column field="employees" title="员工数" :filter="employeesFilter"></tiny-grid-column>
7
+ </tiny-grid>
8
+ </template>
9
+
10
+ <script setup>
11
+ import { ref } from 'vue'
12
+ import { TinyGrid, TinyGridColumn, TinyModal, TinyNumeric } from '@opentiny/vue'
13
+
14
+ const cityData = [
15
+ { label: '福州', value: '福州' },
16
+ { label: '深圳', value: '深圳' },
17
+ { label: '中山', value: '中山' }
18
+ ]
19
+
20
+ const tableData = ref([
21
+ {
22
+ id: '1',
23
+ name: 'GFD 科技 YX 公司',
24
+ city: '福州',
25
+ employees: 800,
26
+ createdDate: '2014-04-30 00:56:00'
27
+ },
28
+ {
29
+ id: '2',
30
+ name: 'WWW 科技 YX 公司',
31
+ city: '深圳',
32
+ employees: 300,
33
+ createdDate: '2016-07-08 12:36:22'
34
+ },
35
+ {
36
+ id: '3',
37
+ name: 'RFV 有限责任公司',
38
+ city: '中山',
39
+ employees: 1300,
40
+ createdDate: '2014-02-14 14:14:14'
41
+ },
42
+ {
43
+ id: '4',
44
+ name: 'TGB 科技 YX 公司',
45
+ city: '龙岩',
46
+ employees: 360,
47
+ createdDate: '2013-01-13 13:13:13'
48
+ },
49
+ {
50
+ id: '5',
51
+ name: 'YHN 科技 YX 公司',
52
+ city: '韶关',
53
+ employees: 810,
54
+ createdDate: '2012-12-12 12:12:12'
55
+ },
56
+ {
57
+ id: '6',
58
+ name: 'WSX 科技 YX 公司',
59
+ city: '黄冈',
60
+ employees: 800,
61
+ createdDate: '2011-11-11 11:11:11'
62
+ },
63
+ {
64
+ id: '7',
65
+ name: 'KBG 物业 YX 公司',
66
+ city: '赤壁',
67
+ employees: 400,
68
+ createdDate: '2016-04-30 23:56:00'
69
+ },
70
+ {
71
+ id: '8',
72
+ name: '深圳市福德宝网络技术 YX 公司',
73
+ city: '厦门',
74
+ employees: 540,
75
+ createdDate: '2016-06-03 13:53:25'
76
+ }
77
+ ])
78
+
79
+ const cityFilter = ref({
80
+ multi: true,
81
+ enumable: true,
82
+ inputFilter: false,
83
+ defaultFilter: false,
84
+ values: cityData,
85
+ layout: 'enum,base'
86
+ })
87
+
88
+ const nameFilter = ref({
89
+ enumable: false,
90
+ inputFilter: true,
91
+ defaultFilter: false,
92
+ layout: 'input,base'
93
+ })
94
+
95
+ const employeesFilter = ref({
96
+ layout: 'extends,input,base',
97
+ inputFilter: {
98
+ component: TinyNumeric,
99
+ attrs: {},
100
+ relations: [
101
+ { label: '小于', value: 'lessThan' },
102
+ { label: '大于', value: 'greaterThan' }
103
+ ]
104
+ },
105
+ extends: [
106
+ { label: '大型企业(>500)', method: ({ value }) => value > 500 },
107
+ { label: '小型企业(<500)', method: ({ value }) => value < 500 }
108
+ ]
109
+ })
110
+
111
+ function filterChangeEvent({ filters }) {
112
+ TinyModal.message({ message: `筛选条件:${JSON.stringify(filters)}`, status: 'info' })
113
+ }
114
+ </script>