foggy-data-viewer 1.0.1-beta.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 (34) hide show
  1. package/README.md +273 -0
  2. package/dist/favicon.svg +4 -0
  3. package/dist/index.js +1531 -0
  4. package/dist/index.umd +1 -0
  5. package/dist/style.css +1 -0
  6. package/package.json +51 -0
  7. package/src/App.vue +469 -0
  8. package/src/api/viewer.ts +163 -0
  9. package/src/components/DataTable.test.ts +533 -0
  10. package/src/components/DataTable.vue +810 -0
  11. package/src/components/DataTableWithSearch.test.ts +628 -0
  12. package/src/components/DataTableWithSearch.vue +277 -0
  13. package/src/components/DataViewer.vue +310 -0
  14. package/src/components/SearchToolbar.test.ts +521 -0
  15. package/src/components/SearchToolbar.vue +406 -0
  16. package/src/components/composables/index.ts +2 -0
  17. package/src/components/composables/useTableSelection.test.ts +248 -0
  18. package/src/components/composables/useTableSelection.ts +44 -0
  19. package/src/components/composables/useTableSummary.test.ts +341 -0
  20. package/src/components/composables/useTableSummary.ts +129 -0
  21. package/src/components/filters/BoolFilter.vue +103 -0
  22. package/src/components/filters/DateRangeFilter.vue +194 -0
  23. package/src/components/filters/NumberRangeFilter.vue +160 -0
  24. package/src/components/filters/SelectFilter.vue +464 -0
  25. package/src/components/filters/TextFilter.vue +230 -0
  26. package/src/components/filters/index.ts +5 -0
  27. package/src/examples/EnhancedTableExample.vue +136 -0
  28. package/src/index.ts +32 -0
  29. package/src/main.ts +14 -0
  30. package/src/types/index.ts +159 -0
  31. package/src/utils/README.md +140 -0
  32. package/src/utils/schemaHelper.test.ts +215 -0
  33. package/src/utils/schemaHelper.ts +44 -0
  34. package/src/vite-env.d.ts +7 -0
@@ -0,0 +1,136 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted, h } from 'vue'
3
+ import DataTable from '@/components/DataTable.vue'
4
+ import { fetchQueryMeta, fetchQueryData } from '@/api/viewer'
5
+ import { buildTableColumns } from '@/utils/schemaHelper'
6
+ import type { EnhancedColumnSchema, TableConfig } from '@/types'
7
+
8
+ // 示例1: 显式指定列及顺序
9
+ const tableConfig: TableConfig = {
10
+ visibleColumns: ['orderId', 'orderDate', 'customerName', 'amount', 'status', 'isPaid', 'metadata'],
11
+ customizations: [
12
+ {
13
+ name: 'orderId',
14
+ width: 150,
15
+ fixed: 'left'
16
+ },
17
+ {
18
+ name: 'orderDate',
19
+ width: 120
20
+ },
21
+ {
22
+ name: 'customerName',
23
+ width: 150
24
+ },
25
+ {
26
+ // formatter: 用于格式化数据(导出时使用)
27
+ name: 'amount',
28
+ width: 120,
29
+ formatter: (value) => `¥${Number(value).toFixed(2)}`
30
+ },
31
+ {
32
+ // render: 用于自定义显示(仅前端显示,不影响导出)
33
+ name: 'status',
34
+ width: 100,
35
+ render: ({ value }) => {
36
+ const statusMap: Record<string, { text: string; color: string }> = {
37
+ paid: { text: '已支付', color: '#67c23a' },
38
+ pending: { text: '待支付', color: '#e6a23c' },
39
+ cancelled: { text: '已取消', color: '#909399' }
40
+ }
41
+ const status = statusMap[value as string] || { text: value as string, color: '#909399' }
42
+ return h('span', { style: { color: status.color, fontWeight: 'bold' } }, status.text)
43
+ }
44
+ },
45
+ {
46
+ // render: 将 true 渲染成勾符号
47
+ name: 'isPaid',
48
+ width: 80,
49
+ render: ({ value }) => {
50
+ return h('span', {
51
+ style: { fontSize: '18px', color: value ? '#67c23a' : '#f56c6c' }
52
+ }, value ? '✓' : '✗')
53
+ }
54
+ },
55
+ {
56
+ // formatter: 将 JSON 对象转成文字(用于导出)
57
+ name: 'metadata',
58
+ width: 200,
59
+ formatter: (value) => {
60
+ if (!value) return ''
61
+ if (typeof value === 'object') {
62
+ return JSON.stringify(value)
63
+ }
64
+ return String(value)
65
+ }
66
+ }
67
+ ]
68
+ }
69
+
70
+ // 示例2: 显示所有列(按 QM schema 顺序)
71
+ const tableConfigShowAll: TableConfig = {
72
+ showAll: true,
73
+ customizations: [
74
+ {
75
+ name: 'orderId',
76
+ width: 150,
77
+ fixed: 'left'
78
+ }
79
+ ]
80
+ }
81
+
82
+ const columns = ref<EnhancedColumnSchema[]>([])
83
+ const data = ref<Record<string, unknown>[]>([])
84
+ const total = ref(0)
85
+ const loading = ref(false)
86
+
87
+ async function loadData() {
88
+ try {
89
+ loading.value = true
90
+
91
+ // 1. 获取 QM schema
92
+ const meta = await fetchQueryMeta('your-query-id')
93
+
94
+ // 2. 合并 schema 和定制参数
95
+ columns.value = buildTableColumns(meta.schema, tableConfig)
96
+
97
+ // 3. 加载数据
98
+ const response = await fetchQueryData('your-query-id', {
99
+ start: 0,
100
+ limit: 50
101
+ })
102
+
103
+ data.value = response.items
104
+ total.value = response.total
105
+ } catch (error) {
106
+ console.error('Failed to load data:', error)
107
+ } finally {
108
+ loading.value = false
109
+ }
110
+ }
111
+
112
+ onMounted(() => {
113
+ loadData()
114
+ })
115
+ </script>
116
+
117
+ <template>
118
+ <div class="example-container">
119
+ <h2>增强表格示例</h2>
120
+ <DataTable
121
+ :columns="columns"
122
+ :data="data"
123
+ :total="total"
124
+ :loading="loading"
125
+ @page-change="(page, size) => console.log('Page changed:', page, size)"
126
+ @sort-change="(field, order) => console.log('Sort changed:', field, order)"
127
+ @filter-change="(slices) => console.log('Filter changed:', slices)"
128
+ />
129
+ </div>
130
+ </template>
131
+
132
+ <style scoped>
133
+ .example-container {
134
+ padding: 20px;
135
+ }
136
+ </style>
package/src/index.ts ADDED
@@ -0,0 +1,32 @@
1
+ // 统一引入所有依赖样式(用户只需引入一次)
2
+ import 'vxe-table/lib/style.css'
3
+ import 'element-plus/dist/index.css'
4
+
5
+ // 导出组件
6
+ export { default as DataTable } from './components/DataTable.vue'
7
+ export { default as DataViewer } from './components/DataViewer.vue'
8
+ export { default as SearchToolbar } from './components/SearchToolbar.vue'
9
+ export { default as DataTableWithSearch } from './components/DataTableWithSearch.vue'
10
+
11
+ // 导出过滤器组件
12
+ export * from './components/filters'
13
+
14
+ // 导出 Composables
15
+ export * from './components/composables'
16
+
17
+ // 导出工具函数
18
+ export { buildTableColumns } from './utils/schemaHelper'
19
+
20
+ // 导出类型定义
21
+ export type {
22
+ ColumnSchema,
23
+ EnhancedColumnSchema,
24
+ TableConfig,
25
+ ColumnCustomization,
26
+ QueryMetaResponse,
27
+ ViewerQueryRequest,
28
+ ViewerDataResponse,
29
+ SliceRequestDef,
30
+ OrderRequestDef,
31
+ FilterOption
32
+ } from './types'
package/src/main.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { createApp } from 'vue'
2
+ import VXETable from 'vxe-table'
3
+ import 'vxe-table/lib/style.css'
4
+ import ElementPlus from 'element-plus'
5
+ import 'element-plus/dist/index.css'
6
+ import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
7
+ import App from './App.vue'
8
+
9
+ const app = createApp(App)
10
+
11
+ app.use(VXETable)
12
+ app.use(ElementPlus, { locale: zhCn })
13
+
14
+ app.mount('#app')
@@ -0,0 +1,159 @@
1
+ /**
2
+ * 字典项
3
+ */
4
+ export interface DictItem {
5
+ value: string | number
6
+ label: string
7
+ }
8
+
9
+ /**
10
+ * 列定义类型
11
+ */
12
+ export interface ColumnSchema {
13
+ name: string
14
+ type: string
15
+ title?: string
16
+ filterable?: boolean
17
+ aggregatable?: boolean
18
+
19
+ // 过滤器元数据
20
+ filterType?: 'text' | 'number' | 'date' | 'datetime' | 'dict' | 'dimension' | 'bool' | 'custom'
21
+ dictId?: string
22
+ dictItems?: DictItem[]
23
+ dimensionRef?: string
24
+ format?: string
25
+ measure?: boolean
26
+ uiConfig?: Record<string, unknown>
27
+ }
28
+
29
+ /**
30
+ * DSL 过滤条件 (SliceRequestDef)
31
+ * 直接对应后端 DSL 格式
32
+ */
33
+ export interface SliceRequestDef {
34
+ field: string
35
+ op: string // =, !=, >, >=, <, <=, in, like, right_like, [], [), is null, is not null 等
36
+ value?: unknown
37
+ link?: 1 | 2 // 1=AND, 2=OR
38
+ children?: SliceRequestDef[]
39
+ }
40
+
41
+ /**
42
+ * DSL 排序条件 (OrderRequestDef)
43
+ */
44
+ export interface OrderRequestDef {
45
+ field: string
46
+ order: 'asc' | 'desc'
47
+ }
48
+
49
+ /**
50
+ * 查询元数据响应(重构后的版本)
51
+ */
52
+ export interface QueryMetaResponse {
53
+ title: string
54
+ tableConfig: TableConfig // 改为 tableConfig
55
+ estimatedRowCount: number | null
56
+ expiresAt: string
57
+ /** 初始过滤条件(来自缓存) */
58
+ initialSlice?: SliceRequestDef[]
59
+ }
60
+
61
+ /**
62
+ * 数据查询请求 (使用 DSL 格式)
63
+ */
64
+ export interface ViewerQueryRequest {
65
+ start?: number
66
+ limit?: number
67
+ /** 过滤条件 (DSL slice 格式) */
68
+ slice?: SliceRequestDef[]
69
+ /** 排序条件 (DSL orderBy 格式) */
70
+ orderBy?: OrderRequestDef[]
71
+ }
72
+
73
+ /**
74
+ * 过滤选项(用于下拉)
75
+ */
76
+ export interface FilterOption {
77
+ value: string | number
78
+ label: string
79
+ }
80
+
81
+ /**
82
+ * 过滤选项响应
83
+ */
84
+ export interface FilterOptionsResponse {
85
+ options: FilterOption[]
86
+ total: number
87
+ error?: string
88
+ }
89
+
90
+ /**
91
+ * 数据响应
92
+ */
93
+ export interface ViewerDataResponse {
94
+ success: boolean
95
+ items: Record<string, unknown>[]
96
+ total: number
97
+ start: number
98
+ limit: number
99
+ errorMessage?: string
100
+ expired?: boolean
101
+ /** 全量数据汇总(包含总记录数和度量合计) */
102
+ totalData?: Record<string, unknown>
103
+ }
104
+
105
+ /**
106
+ * 分页状态
107
+ */
108
+ export interface PaginationState {
109
+ currentPage: number
110
+ pageSize: number
111
+ total: number
112
+ }
113
+
114
+ /**
115
+ * 排序状态
116
+ */
117
+ export interface SortState {
118
+ field: string | null
119
+ order: 'asc' | 'desc' | null
120
+ }
121
+
122
+ /**
123
+ * 列定制配置
124
+ */
125
+ export interface ColumnCustomization {
126
+ name: string
127
+ width?: number
128
+ minWidth?: number
129
+ fixed?: 'left' | 'right'
130
+ render?: (params: { row: Record<string, unknown>; value: unknown }) => unknown
131
+ filterComponent?: unknown
132
+ formatter?: (value: unknown) => string
133
+ }
134
+
135
+ /**
136
+ * 增强的列配置(合并 QM schema 和前端定制)
137
+ */
138
+ export interface EnhancedColumnSchema extends ColumnSchema {
139
+ width?: number
140
+ minWidth?: number
141
+ fixed?: 'left' | 'right'
142
+ customRender?: (params: { row: Record<string, unknown>; value: unknown }) => unknown
143
+ customFilterComponent?: unknown
144
+ customFormatter?: (value: unknown) => string
145
+ }
146
+
147
+ /**
148
+ * 表格配置
149
+ */
150
+ export interface TableConfig {
151
+ /** QM 模型名称 */
152
+ qmModel: string
153
+ /** 显式指定显示的列及顺序(必填,除非 showAll=true) */
154
+ visibleColumns?: string[]
155
+ /** 显示所有列 */
156
+ showAll?: boolean
157
+ /** 列定制配置 */
158
+ customizations?: ColumnCustomization[]
159
+ }
@@ -0,0 +1,140 @@
1
+ # Schema Helper 使用说明
2
+
3
+ ## 核心概念
4
+
5
+ ### formatter vs render
6
+
7
+ - **formatter**: 用于数据格式化,影响**导出**和**显示**
8
+ - 返回值:`string`
9
+ - 使用场景:格式化金额、日期、将 JSON 转文字等
10
+ - 示例:`formatter: (v) => '¥' + v`
11
+ - **不会修改原始数据**
12
+
13
+ - **render**: 用于自定义渲染,仅影响**前端显示**
14
+ - 返回值:`VNode | string`
15
+ - 使用场景:添加图标、颜色、特殊样式等
16
+ - 示例:`render: ({ value }) => h('span', { style: { color: 'red' } }, value)`
17
+ - **不会修改原始数据**
18
+
19
+ > **重要**:
20
+ > - formatter 和 render 都只影响显示/导出,**不会修改 data 中的原始数据**
21
+ > - 有 formatter 一般不需要 render,但不绝对
22
+ > - 如果同时存在,render 用于显示,formatter 用于导出
23
+
24
+ ## 基本用法
25
+
26
+ ```typescript
27
+ import { buildTableColumns } from '@/utils/schemaHelper'
28
+ import type { TableConfig } from '@/types'
29
+
30
+ // 1. 定义表格配置
31
+ const config: TableConfig = {
32
+ // 必须指定显示的列及顺序
33
+ visibleColumns: ['id', 'name', 'amount', 'status'],
34
+
35
+ // 或者显示所有列
36
+ // showAll: true,
37
+
38
+ // 列定制
39
+ customizations: [
40
+ {
41
+ name: 'id',
42
+ width: 150,
43
+ fixed: 'left'
44
+ },
45
+ {
46
+ name: 'amount',
47
+ formatter: (v) => `¥${Number(v).toFixed(2)}`
48
+ },
49
+ {
50
+ name: 'status',
51
+ render: ({ value }) => h('span', {
52
+ style: { color: value === 'active' ? 'green' : 'red' }
53
+ }, value)
54
+ }
55
+ ]
56
+ }
57
+
58
+ // 2. 合并 QM schema 和定制配置
59
+ const columns = buildTableColumns(qmSchema, config)
60
+
61
+ // 3. 传给 DataTable 组件
62
+ <DataTable :columns="columns" :data="data" />
63
+ ```
64
+
65
+ ## 常见场景
66
+
67
+ ### 场景1: 布尔值渲染成符号
68
+
69
+ ```typescript
70
+ {
71
+ name: 'isPaid',
72
+ render: ({ value }) => h('span', {
73
+ style: { fontSize: '18px', color: value ? '#67c23a' : '#f56c6c' }
74
+ }, value ? '✓' : '✗')
75
+ }
76
+ ```
77
+
78
+ ### 场景2: JSON 导出为文字
79
+
80
+ ```typescript
81
+ {
82
+ name: 'metadata',
83
+ formatter: (value) => {
84
+ if (typeof value === 'object') {
85
+ return JSON.stringify(value)
86
+ }
87
+ return String(value)
88
+ }
89
+ }
90
+ ```
91
+
92
+ ### 场景3: 状态带颜色显示
93
+
94
+ ```typescript
95
+ {
96
+ name: 'status',
97
+ render: ({ value }) => {
98
+ const colors = { success: '#67c23a', warning: '#e6a23c', error: '#f56c6c' }
99
+ return h('span', { style: { color: colors[value] } }, value)
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### 场景4: 同时使用 formatter 和 render
105
+
106
+ ```typescript
107
+ {
108
+ name: 'price',
109
+ // 导出时格式化为文字
110
+ formatter: (v) => `¥${Number(v).toFixed(2)}`,
111
+ // 显示时添加样式
112
+ render: ({ value }) => h('span', {
113
+ style: { fontWeight: 'bold', color: '#e6a23c' }
114
+ }, `¥${Number(value).toFixed(2)}`)
115
+ }
116
+ ```
117
+
118
+ ## 配置选项
119
+
120
+ ### TableConfig
121
+
122
+ | 属性 | 类型 | 必填 | 说明 |
123
+ |------|------|------|------|
124
+ | visibleColumns | string[] | 是* | 显示的列及顺序 |
125
+ | showAll | boolean | 是* | 显示所有列 |
126
+ | customizations | ColumnCustomization[] | 否 | 列定制配置 |
127
+
128
+ *注:`visibleColumns` 和 `showAll` 必须二选一
129
+
130
+ ### ColumnCustomization
131
+
132
+ | 属性 | 类型 | 说明 |
133
+ |------|------|------|
134
+ | name | string | 列名(必填) |
135
+ | width | number | 列宽 |
136
+ | minWidth | number | 最小列宽 |
137
+ | fixed | 'left' \| 'right' | 固定列 |
138
+ | formatter | (value) => string | 格式化函数(用于导出) |
139
+ | render | ({ row, value }) => VNode \| string | 渲染函数(用于显示) |
140
+ | filterComponent | Component | 自定义过滤器组件 |
@@ -0,0 +1,215 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { buildTableColumns } from './schemaHelper'
3
+ import type { ColumnSchema, TableConfig } from '@/types'
4
+
5
+ describe('schemaHelper', () => {
6
+ const mockQMSchema: ColumnSchema[] = [
7
+ { name: 'id', type: 'INTEGER', title: 'ID' },
8
+ { name: 'name', type: 'TEXT', title: '名称' },
9
+ { name: 'amount', type: 'MONEY', title: '金额' },
10
+ { name: 'status', type: 'TEXT', title: '状态' }
11
+ ]
12
+
13
+ describe('buildTableColumns', () => {
14
+ it('should return all columns when config is empty (defaults to showAll)', () => {
15
+ const config: TableConfig = {}
16
+ const result = buildTableColumns(mockQMSchema, config)
17
+
18
+ // 空配置时默认显示所有列
19
+ expect(result).toHaveLength(4)
20
+ expect(result[0].name).toBe('id')
21
+ expect(result[1].name).toBe('name')
22
+ expect(result[2].name).toBe('amount')
23
+ expect(result[3].name).toBe('status')
24
+ })
25
+
26
+ it('should return all columns when showAll is true', () => {
27
+ const config: TableConfig = { showAll: true }
28
+ const result = buildTableColumns(mockQMSchema, config)
29
+
30
+ expect(result).toHaveLength(4)
31
+ expect(result[0].name).toBe('id')
32
+ expect(result[1].name).toBe('name')
33
+ expect(result[2].name).toBe('amount')
34
+ expect(result[3].name).toBe('status')
35
+ })
36
+
37
+ it('should return columns in specified order', () => {
38
+ const config: TableConfig = {
39
+ visibleColumns: ['amount', 'name', 'id']
40
+ }
41
+ const result = buildTableColumns(mockQMSchema, config)
42
+
43
+ expect(result).toHaveLength(3)
44
+ expect(result[0].name).toBe('amount')
45
+ expect(result[1].name).toBe('name')
46
+ expect(result[2].name).toBe('id')
47
+ })
48
+
49
+ it('should apply width customization', () => {
50
+ const config: TableConfig = {
51
+ visibleColumns: ['id', 'name'],
52
+ customizations: [
53
+ { name: 'id', width: 150 },
54
+ { name: 'name', width: 200 }
55
+ ]
56
+ }
57
+ const result = buildTableColumns(mockQMSchema, config)
58
+
59
+ expect(result[0].width).toBe(150)
60
+ expect(result[1].width).toBe(200)
61
+ })
62
+
63
+ it('should apply minWidth customization with default fallback', () => {
64
+ const config: TableConfig = {
65
+ visibleColumns: ['id', 'name'],
66
+ customizations: [
67
+ { name: 'id', minWidth: 100 }
68
+ ]
69
+ }
70
+ const result = buildTableColumns(mockQMSchema, config)
71
+
72
+ expect(result[0].minWidth).toBe(100)
73
+ expect(result[1].minWidth).toBe(120) // default
74
+ })
75
+
76
+ it('should apply fixed column customization', () => {
77
+ const config: TableConfig = {
78
+ visibleColumns: ['id', 'name', 'amount'],
79
+ customizations: [
80
+ { name: 'id', fixed: 'left' },
81
+ { name: 'amount', fixed: 'right' }
82
+ ]
83
+ }
84
+ const result = buildTableColumns(mockQMSchema, config)
85
+
86
+ expect(result[0].fixed).toBe('left')
87
+ expect(result[1].fixed).toBeUndefined()
88
+ expect(result[2].fixed).toBe('right')
89
+ })
90
+
91
+ it('should apply formatter customization', () => {
92
+ const formatter = (v: unknown) => `¥${v}`
93
+ const config: TableConfig = {
94
+ visibleColumns: ['amount'],
95
+ customizations: [
96
+ { name: 'amount', formatter }
97
+ ]
98
+ }
99
+ const result = buildTableColumns(mockQMSchema, config)
100
+
101
+ expect(result[0].customFormatter).toBe(formatter)
102
+ })
103
+
104
+ it('should apply render customization', () => {
105
+ const render = ({ value }: { value: unknown }) => String(value)
106
+ const config: TableConfig = {
107
+ visibleColumns: ['status'],
108
+ customizations: [
109
+ { name: 'status', render }
110
+ ]
111
+ }
112
+ const result = buildTableColumns(mockQMSchema, config)
113
+
114
+ expect(result[0].customRender).toBe(render)
115
+ })
116
+
117
+ it('should apply filterComponent customization', () => {
118
+ const filterComponent = {}
119
+ const config: TableConfig = {
120
+ visibleColumns: ['status'],
121
+ customizations: [
122
+ { name: 'status', filterComponent }
123
+ ]
124
+ }
125
+ const result = buildTableColumns(mockQMSchema, config)
126
+
127
+ expect(result[0].customFilterComponent).toBe(filterComponent)
128
+ })
129
+
130
+ it('should warn when column not found in schema', () => {
131
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
132
+
133
+ const config: TableConfig = {
134
+ visibleColumns: ['id', 'nonexistent', 'name']
135
+ }
136
+ const result = buildTableColumns(mockQMSchema, config)
137
+
138
+ expect(result).toHaveLength(2)
139
+ expect(result[0].name).toBe('id')
140
+ expect(result[1].name).toBe('name')
141
+ expect(consoleSpy).toHaveBeenCalledWith(
142
+ 'Column "nonexistent" not found in QM schema'
143
+ )
144
+
145
+ consoleSpy.mockRestore()
146
+ })
147
+
148
+ it('should handle empty visibleColumns (defaults to showAll)', () => {
149
+ const config: TableConfig = {
150
+ visibleColumns: []
151
+ }
152
+ const result = buildTableColumns(mockQMSchema, config)
153
+
154
+ // 空的 visibleColumns 时默认显示所有列
155
+ expect(result).toHaveLength(4)
156
+ })
157
+
158
+ it('should merge multiple customizations correctly', () => {
159
+ const formatter = (v: unknown) => `¥${v}`
160
+ const render = ({ value }: { value: unknown }) => String(value)
161
+
162
+ const config: TableConfig = {
163
+ visibleColumns: ['id', 'amount'],
164
+ customizations: [
165
+ {
166
+ name: 'id',
167
+ width: 150,
168
+ minWidth: 100,
169
+ fixed: 'left'
170
+ },
171
+ {
172
+ name: 'amount',
173
+ width: 120,
174
+ formatter,
175
+ render
176
+ }
177
+ ]
178
+ }
179
+ const result = buildTableColumns(mockQMSchema, config)
180
+
181
+ expect(result[0]).toMatchObject({
182
+ name: 'id',
183
+ width: 150,
184
+ minWidth: 100,
185
+ fixed: 'left'
186
+ })
187
+
188
+ expect(result[1]).toMatchObject({
189
+ name: 'amount',
190
+ width: 120
191
+ })
192
+ expect(result[1].customFormatter).toBe(formatter)
193
+ expect(result[1].customRender).toBe(render)
194
+ })
195
+
196
+ it('should preserve original schema properties', () => {
197
+ const config: TableConfig = {
198
+ visibleColumns: ['id', 'amount']
199
+ }
200
+ const result = buildTableColumns(mockQMSchema, config)
201
+
202
+ expect(result[0]).toMatchObject({
203
+ name: 'id',
204
+ type: 'INTEGER',
205
+ title: 'ID'
206
+ })
207
+
208
+ expect(result[1]).toMatchObject({
209
+ name: 'amount',
210
+ type: 'MONEY',
211
+ title: '金额'
212
+ })
213
+ })
214
+ })
215
+ })