@ticatec/uniface-element 0.2.3 → 0.3.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.
- package/dist/accordion/README.md +169 -0
- package/dist/accordion/README_CN.md +176 -0
- package/dist/action-bar/README.md +275 -0
- package/dist/action-bar/README_CN.md +285 -0
- package/dist/app-layout/README.md +374 -0
- package/dist/app-layout/README_CN.md +381 -0
- package/dist/app-top-bar/README.md +357 -0
- package/dist/app-top-bar/README_CN.md +365 -0
- package/dist/attachment-files/AttachmentFilesField.svelte +67 -1
- package/dist/attachment-files/AttachmentFilesField.svelte.d.ts +8 -1
- package/dist/base-calendar/CalendarBase.svelte +0 -3
- package/dist/box/README.md +385 -0
- package/dist/box/README_CN.md +393 -0
- package/dist/breadcrumbs/README.md +442 -0
- package/dist/breadcrumbs/README_CN.md +442 -0
- package/dist/button/Button.svelte +27 -7
- package/dist/button/Button.svelte.d.ts +3 -1
- package/dist/button/IconButton.svelte +26 -6
- package/dist/button/IconButton.svelte.d.ts +3 -1
- package/dist/button/IconButton_README.md +513 -0
- package/dist/button/IconButton_README_CN.md +513 -0
- package/dist/button/README.md +428 -0
- package/dist/button/README_CN.md +428 -0
- package/dist/button/TextButton.svelte +26 -6
- package/dist/button/TextButton.svelte.d.ts +3 -1
- package/dist/button/TextButton_README.md +571 -0
- package/dist/button/TextButton_README_CN.md +571 -0
- package/dist/card/README.md +666 -0
- package/dist/card/README_CN.md +666 -0
- package/dist/cascade-options-select/CascadeOptionSelect.svelte +43 -7
- package/dist/cascade-options-select/CascadeOptionSelect.svelte.d.ts +3 -4
- package/dist/cascade-options-select/README.md +663 -2
- package/dist/cascade-options-select/README_CN.md +664 -0
- package/dist/checkbox/CheckBox.svelte +20 -6
- package/dist/checkbox/CheckBox.svelte.d.ts +4 -2
- package/dist/checkbox/README.md +562 -0
- package/dist/checkbox/README_CN.md +727 -0
- package/dist/common-editor/NumberInput.svelte +39 -26
- package/dist/common-editor/NumberInput.svelte.d.ts +5 -1
- package/dist/concise-data-table/ConciseListTable.svelte +133 -36
- package/dist/concise-data-table/ConciseListTable.svelte.d.ts +8 -1
- package/dist/concise-data-table/DataCell.svelte +5 -6
- package/dist/concise-data-table/DataRow.svelte +22 -2
- package/dist/concise-data-table/DataRow.svelte.d.ts +1 -0
- package/dist/concise-data-table/README.md +606 -0
- package/dist/concise-data-table/README_CN.md +611 -0
- package/dist/context-menu/ContextMenu.svelte +138 -21
- package/dist/context-menu/ContextMenu.svelte.d.ts +5 -0
- package/dist/context-menu/ContextMenuItem.d.ts +20 -4
- package/dist/context-menu/README.md +551 -0
- package/dist/context-menu/README_CN.md +551 -0
- package/dist/criteria-field/CriteriaField.svelte +3 -5
- package/dist/criteria-field/README.md +532 -0
- package/dist/criteria-field/README_CN.md +532 -0
- package/dist/data-table/README.md +712 -0
- package/dist/data-table/README_CN.md +712 -0
- package/dist/date-picker/DatePicker.svelte +44 -7
- package/dist/date-picker/DatePicker.svelte.d.ts +3 -4
- package/dist/date-picker/DateTimePicker.svelte +32 -8
- package/dist/date-picker/DateTimePicker.svelte.d.ts +3 -4
- package/dist/date-picker/README.md +326 -0
- package/dist/date-picker/README_CN.md +316 -0
- package/dist/date-picker/ScrollBar.svelte +18 -8
- package/dist/date-picker/ScrollBar.svelte.d.ts +1 -1
- package/dist/date-picker/TimePanel.svelte +5 -5
- package/dist/date-picker/TimePanel.svelte.d.ts +1 -1
- package/dist/date-range/README.md +374 -0
- package/dist/date-range/README_CN.md +374 -0
- package/dist/dialog/DialogBoard.svelte +1 -1
- package/dist/dialog/README.md +596 -0
- package/dist/dialog/README_CN.md +596 -0
- package/dist/drawer/README.md +679 -1
- package/dist/drawer/README_CN.md +679 -0
- package/dist/group-box/group-check-box/GroupCheckBox.svelte +62 -5
- package/dist/group-box/group-check-box/GroupCheckBox.svelte.d.ts +7 -2
- package/dist/group-box/group-check-box/README.md +749 -0
- package/dist/group-box/group-check-box/README_CN.md +750 -0
- package/dist/group-box/group-radio-box/GroupRadioBox.svelte +69 -4
- package/dist/group-box/group-radio-box/GroupRadioBox.svelte.d.ts +8 -2
- package/dist/group-box/group-radio-box/README.md +920 -0
- package/dist/group-box/group-radio-box/README_CN.md +920 -0
- package/dist/image-files/README.md +1346 -0
- package/dist/image-files/README_CN.md +1346 -0
- package/dist/input-options-select/InputOptionsSelect.svelte +161 -65
- package/dist/input-options-select/InputOptionsSelect.svelte.d.ts +4 -1
- package/dist/lib/forwardAllEvents.d.ts +3 -0
- package/dist/lib/forwardAllEvents.js +24 -0
- package/dist/list-box/README.md +579 -0
- package/dist/list-box/README_CN.md +682 -0
- package/dist/lookup-editor/LookupEditor.svelte +15 -3
- package/dist/lookup-editor/LookupEditor.svelte.d.ts +3 -4
- package/dist/lookup-editor/README.md +684 -0
- package/dist/lookup-editor/README_CN.md +684 -0
- package/dist/memo-editor/MemoEditor.svelte +77 -27
- package/dist/memo-editor/MemoEditor.svelte.d.ts +6 -3
- package/dist/memo-editor/README.md +819 -0
- package/dist/memo-editor/README_CN.md +819 -0
- package/dist/message-box/README.md +398 -0
- package/dist/message-box/README_CN.md +398 -0
- package/dist/number-editor/NumberEditor.svelte +2 -2
- package/dist/number-editor/NumberEditor.svelte.d.ts +1 -1
- package/dist/number-editor/README.md +353 -22
- package/dist/number-editor/README_CN.md +357 -0
- package/dist/number-range/README.md +443 -0
- package/dist/number-range/README_CN.md +443 -0
- package/dist/options-multi-select/OptionsMultiSelect.svelte +87 -12
- package/dist/options-multi-select/OptionsMultiSelect.svelte.d.ts +3 -1
- package/dist/options-multi-select/README.md +480 -0
- package/dist/options-multi-select/README_CN.md +480 -0
- package/dist/options-select/OptionsSelect.svelte +36 -5
- package/dist/options-select/OptionsSelect.svelte.d.ts +3 -4
- package/dist/options-select/README.md +632 -0
- package/dist/options-select/README_CN.md +632 -0
- package/dist/page/README.md +605 -0
- package/dist/page/README_CN.md +605 -0
- package/dist/pagination/README.md +946 -0
- package/dist/pagination/README_CN.md +946 -0
- package/dist/pagination-panel/README.md +1075 -0
- package/dist/pagination-panel/README_CN.md +1075 -0
- package/dist/popup-hint/README.md +841 -0
- package/dist/popup-hint/README_CN.md +840 -0
- package/dist/progress-bar/README.md +242 -0
- package/dist/progress-bar/README_CN.md +242 -0
- package/dist/progress-step-bar/README.md +316 -0
- package/dist/progress-step-bar/README_CN.md +316 -0
- package/dist/prompts-text-editor/PromptsTextEditor.svelte +6 -3
- package/dist/prompts-text-editor/PromptsTextEditor.svelte.d.ts +3 -1
- package/dist/prompts-text-editor/README.md +329 -0
- package/dist/prompts-text-editor/README_CN.md +329 -0
- package/dist/property-editor/README.md +969 -0
- package/dist/property-editor/README_CN.md +969 -0
- package/dist/radio-button/README.md +372 -0
- package/dist/radio-button/README_CN.md +372 -0
- package/dist/radio-button/RadioButton.svelte +32 -3
- package/dist/radio-button/RadioButton.svelte.d.ts +12 -1
- package/dist/search-box/README.md +425 -0
- package/dist/search-box/README_CN.md +424 -0
- package/dist/search-box/SearchBox.svelte +22 -6
- package/dist/search-box/SearchBox.svelte.d.ts +4 -1
- package/dist/tabs/README.md +919 -0
- package/dist/tabs/README_CN.md +920 -0
- package/dist/tag/README.md +922 -0
- package/dist/tag/README_CN.md +922 -0
- package/dist/text-editor/PasswordEditor.svelte +17 -2
- package/dist/text-editor/PasswordEditor.svelte.d.ts +3 -3
- package/dist/text-editor/PasswordEditor_README.md +551 -0
- package/dist/text-editor/PasswordEditor_README_CN.md +551 -0
- package/dist/text-editor/README.md +466 -0
- package/dist/text-editor/README_CN.md +466 -0
- package/dist/text-editor/TextEditor.svelte +51 -19
- package/dist/text-editor/TextEditor.svelte.d.ts +0 -1
- package/dist/ticatec-uniface-web.css +1 -0
- package/dist/time-editor/README.md +498 -24
- package/dist/time-editor/README_CN.md +498 -0
- package/dist/time-editor/TimeEditor.svelte +7 -3
- package/dist/time-editor/TimeEditor.svelte.d.ts +3 -1
- package/dist/toast/README.md +667 -0
- package/dist/toast/README_CN.md +667 -0
- package/dist/transfer/README.md +795 -0
- package/dist/transfer/Transfer.svelte +6 -4
- package/dist/transfer/Transfer.svelte.d.ts +3 -1
- package/dist/tree-view/README.md +761 -0
- package/dist/tree-view/README_CN.md +761 -0
- package/dist/tree-view/TreeView.svelte +5 -3
- package/dist/tree-view/TreeView.svelte.d.ts +3 -1
- package/dist/unit-number-editor/README.md +546 -20
- package/dist/unit-number-editor/README_CN.md +525 -0
- package/dist/unit-number-editor/UnitNumberEditor.svelte +62 -8
- package/dist/unit-number-editor/UnitNumberEditor.svelte.d.ts +5 -1
- package/docs/form-field/cn/README_CN.md +364 -0
- package/docs/form-field/en/README.md +364 -0
- package/docs/form-panel/flex-form/cn/README_CN.md +586 -0
- package/docs/form-panel/flex-form/en/README.md +586 -0
- package/docs/form-panel/flex-row-form/cn/README_CN.md +787 -0
- package/docs/form-panel/flex-row-form/en/README.md +787 -0
- package/docs/form-panel/grid-form/en/README.md +744 -0
- package/package.json +1 -1
- package/dist/drawer/README-CN.md +0 -1
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
# ListBox 列表框组件
|
|
2
|
+
|
|
3
|
+
一个多功能的列表组件,支持过滤、懒加载、单选/多选模式和自定义项目渲染。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **多种选择模式**: 支持无选择、单选和多选模式
|
|
8
|
+
- **搜索和过滤**: 内置搜索功能,支持自定义过滤函数
|
|
9
|
+
- **懒加载**: 支持分页数据加载和无限滚动
|
|
10
|
+
- **自定义渲染**: 灵活的项目渲染,支持自定义组件
|
|
11
|
+
- **交互状态**: 悬停、选中和点击处理
|
|
12
|
+
- **头部/底部插槽**: 可自定义的头部和底部区域
|
|
13
|
+
- **加载状态**: 内置异步操作的加载指示器
|
|
14
|
+
- **无障碍访问**: 键盘导航和屏幕阅读器支持
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @ticatec/uniface-element
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 使用方法
|
|
23
|
+
|
|
24
|
+
### 基本用法
|
|
25
|
+
|
|
26
|
+
```svelte
|
|
27
|
+
<script>
|
|
28
|
+
import ListBox from '@ticatec/uniface-element/list-box';
|
|
29
|
+
import MyItemComponent from './MyItemComponent.svelte';
|
|
30
|
+
|
|
31
|
+
let items = [
|
|
32
|
+
{ id: 1, name: '项目 1', description: '第一个项目' },
|
|
33
|
+
{ id: 2, name: '项目 2', description: '第二个项目' },
|
|
34
|
+
{ id: 3, name: '项目 3', description: '第三个项目' }
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
let selectedItem = null;
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<ListBox
|
|
41
|
+
list={items}
|
|
42
|
+
itemRender={MyItemComponent}
|
|
43
|
+
selectMode="single"
|
|
44
|
+
bind:selectedItem
|
|
45
|
+
style="width: 300px; height: 400px;"
|
|
46
|
+
/>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 只读列表显示
|
|
50
|
+
|
|
51
|
+
```svelte
|
|
52
|
+
<ListBox
|
|
53
|
+
list={items}
|
|
54
|
+
itemRender={ItemRenderer}
|
|
55
|
+
selectMode="none"
|
|
56
|
+
readonly={true}
|
|
57
|
+
style="width: 100%; height: 300px;"
|
|
58
|
+
/>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 多选模式
|
|
62
|
+
|
|
63
|
+
```svelte
|
|
64
|
+
<script>
|
|
65
|
+
let selectedList = [];
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<ListBox
|
|
69
|
+
list={items}
|
|
70
|
+
itemRender={ItemRenderer}
|
|
71
|
+
selectMode="multiple"
|
|
72
|
+
bind:selectedList
|
|
73
|
+
title="选择多个项目"
|
|
74
|
+
style="width: 100%; height: 400px;"
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
<p>已选择: {selectedList.length} 个项目</p>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 带搜索/过滤功能
|
|
81
|
+
|
|
82
|
+
```svelte
|
|
83
|
+
<script>
|
|
84
|
+
const filterFunction = (item, searchText) => {
|
|
85
|
+
return item.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
|
86
|
+
item.description.toLowerCase().includes(searchText.toLowerCase());
|
|
87
|
+
};
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<ListBox
|
|
91
|
+
list={items}
|
|
92
|
+
itemRender={ItemRenderer}
|
|
93
|
+
selectMode="single"
|
|
94
|
+
filter={filterFunction}
|
|
95
|
+
title="可搜索列表"
|
|
96
|
+
bind:selectedItem
|
|
97
|
+
/>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 懒加载
|
|
101
|
+
|
|
102
|
+
```svelte
|
|
103
|
+
<script>
|
|
104
|
+
const lazyLoader = async (searchText, pageNo) => {
|
|
105
|
+
const response = await fetch(`/api/items?search=${searchText}&page=${pageNo}`);
|
|
106
|
+
const data = await response.json();
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
list: data.items,
|
|
110
|
+
hasMore: data.hasMore
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<ListBox
|
|
116
|
+
lazyLoader={lazyLoader}
|
|
117
|
+
itemRender={ItemRenderer}
|
|
118
|
+
selectMode="single"
|
|
119
|
+
bind:selectedItem
|
|
120
|
+
style="width: 100%; height: 500px;"
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 自定义头部和底部
|
|
125
|
+
|
|
126
|
+
```svelte
|
|
127
|
+
<ListBox
|
|
128
|
+
list={items}
|
|
129
|
+
itemRender={ItemRenderer}
|
|
130
|
+
selectMode="single"
|
|
131
|
+
bind:selectedItem
|
|
132
|
+
>
|
|
133
|
+
<div slot="header" style="padding: 12px; background: #f8f9fa; font-weight: 600;">
|
|
134
|
+
自定义头部 - 总计: {items.length}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div slot="footer" style="padding: 8px; text-align: right; background: #f8f9fa;">
|
|
138
|
+
已选择: {selectedItem?.name || '无'}
|
|
139
|
+
</div>
|
|
140
|
+
</ListBox>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## API 参考
|
|
144
|
+
|
|
145
|
+
### 属性
|
|
146
|
+
|
|
147
|
+
| 属性 | 类型 | 默认值 | 描述 |
|
|
148
|
+
|------|------|---------|-------------|
|
|
149
|
+
| `list` | `Array<any>` | `[]` | 要显示的项目数组 |
|
|
150
|
+
| `itemRender` | `SvelteComponent` | - | 渲染每个项目的组件 |
|
|
151
|
+
| `selectMode` | `'none' \| 'single' \| 'multiple'` | `'none'` | 选择模式 |
|
|
152
|
+
| `selectedItem` | `any` | `null` | 当前选中的项目(单选模式) |
|
|
153
|
+
| `selectedList` | `Array<any>` | `[]` | 选中项目的数组(多选模式) |
|
|
154
|
+
| `filter` | `FunFilter \| null` | `null` | 搜索的过滤函数 |
|
|
155
|
+
| `lazyLoader` | `LazyLoader \| null` | `null` | 懒加载函数 |
|
|
156
|
+
| `readonly` | `boolean` | `false` | 列表是否为只读 |
|
|
157
|
+
| `title` | `string` | - | 头部的标题文本 |
|
|
158
|
+
| `style` | `string` | `''` | 自定义CSS样式 |
|
|
159
|
+
| `header$style` | `string` | `''` | 头部的自定义样式 |
|
|
160
|
+
| `footer$style` | `string` | `''` | 底部的自定义样式 |
|
|
161
|
+
| `round` | `boolean` | `false` | 是否使用圆角 |
|
|
162
|
+
| `item$props` | `any` | `null` | 传递给项目组件的额外属性 |
|
|
163
|
+
| `class` | `string` | `''` | 额外的CSS类 |
|
|
164
|
+
|
|
165
|
+
### 事件
|
|
166
|
+
|
|
167
|
+
| 事件 | 类型 | 描述 |
|
|
168
|
+
|-------|------|-------------|
|
|
169
|
+
| `onSelectChange` | `(item: any) => void` | 选择改变时触发(单选模式) |
|
|
170
|
+
| `onItemClick` | `(item: any) => void` | 项目被点击时触发 |
|
|
171
|
+
| `onItemDblClick` | `(item: any) => void` | 项目被双击时触发 |
|
|
172
|
+
|
|
173
|
+
### 插槽
|
|
174
|
+
|
|
175
|
+
| 插槽 | 描述 |
|
|
176
|
+
|------|-------------|
|
|
177
|
+
| `header` | 自定义头部内容 |
|
|
178
|
+
| `footer` | 自定义底部内容 |
|
|
179
|
+
| `loadMoreIndicator` | 懒加载的自定义加载指示器 |
|
|
180
|
+
|
|
181
|
+
### 类型定义
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// 过滤函数类型
|
|
185
|
+
type FunFilter = (item: any, searchText: string) => boolean;
|
|
186
|
+
|
|
187
|
+
// 懒加载函数类型
|
|
188
|
+
type LazyLoader = (searchText: string, pageNo: number) => Promise<LoadResult>;
|
|
189
|
+
|
|
190
|
+
// 加载结果接口
|
|
191
|
+
interface LoadResult {
|
|
192
|
+
list: Array<any>;
|
|
193
|
+
hasMore: boolean;
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## 自定义项目渲染器
|
|
198
|
+
|
|
199
|
+
创建自定义组件来渲染列表项:
|
|
200
|
+
|
|
201
|
+
```svelte
|
|
202
|
+
<!-- EmployeeRenderer.svelte -->
|
|
203
|
+
<script lang="ts">
|
|
204
|
+
export let item;
|
|
205
|
+
export let selected = false;
|
|
206
|
+
export let readonly = false;
|
|
207
|
+
</script>
|
|
208
|
+
|
|
209
|
+
<div class="employee-item" class:selected>
|
|
210
|
+
<div class="employee-name">{item.name}</div>
|
|
211
|
+
<div class="employee-department">{item.department}</div>
|
|
212
|
+
<div class="employee-phone">{item.phone}</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<style>
|
|
216
|
+
.employee-item {
|
|
217
|
+
padding: 12px;
|
|
218
|
+
border-radius: 4px;
|
|
219
|
+
transition: background-color 0.2s;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.employee-item.selected {
|
|
223
|
+
background-color: #e3f2fd;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.employee-name {
|
|
227
|
+
font-weight: 600;
|
|
228
|
+
font-size: 1.1em;
|
|
229
|
+
margin-bottom: 4px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.employee-department {
|
|
233
|
+
color: #666;
|
|
234
|
+
font-size: 0.9em;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.employee-phone {
|
|
238
|
+
color: #888;
|
|
239
|
+
font-size: 0.8em;
|
|
240
|
+
margin-top: 4px;
|
|
241
|
+
}
|
|
242
|
+
</style>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## 示例
|
|
246
|
+
|
|
247
|
+
### 员工目录
|
|
248
|
+
|
|
249
|
+
```svelte
|
|
250
|
+
<script>
|
|
251
|
+
import ListBox from '@ticatec/uniface-element/list-box';
|
|
252
|
+
import EmployeeRenderer from './EmployeeRenderer.svelte';
|
|
253
|
+
|
|
254
|
+
let employees = [
|
|
255
|
+
{ id: 1, name: '张三', department: '工程部', phone: '+86-138-0013-8000' },
|
|
256
|
+
{ id: 2, name: '李四', department: '市场部', phone: '+86-138-0013-8001' },
|
|
257
|
+
{ id: 3, name: '王五', department: '销售部', phone: '+86-138-0013-8002' }
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
let selectedEmployee = null;
|
|
261
|
+
|
|
262
|
+
const filterEmployees = (employee, searchText) => {
|
|
263
|
+
const text = searchText.toLowerCase();
|
|
264
|
+
return employee.name.toLowerCase().includes(text) ||
|
|
265
|
+
employee.department.toLowerCase().includes(text);
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
function handleEmployeeSelect(employee) {
|
|
269
|
+
console.log('选中员工:', employee);
|
|
270
|
+
}
|
|
271
|
+
</script>
|
|
272
|
+
|
|
273
|
+
<ListBox
|
|
274
|
+
list={employees}
|
|
275
|
+
itemRender={EmployeeRenderer}
|
|
276
|
+
selectMode="single"
|
|
277
|
+
filter={filterEmployees}
|
|
278
|
+
bind:selectedItem={selectedEmployee}
|
|
279
|
+
onSelectChange={handleEmployeeSelect}
|
|
280
|
+
title="员工目录"
|
|
281
|
+
style="width: 400px; height: 500px;"
|
|
282
|
+
>
|
|
283
|
+
<div slot="footer" style="padding: 8px; text-align: center; background: #f5f5f5;">
|
|
284
|
+
总员工数: {employees.length}
|
|
285
|
+
{#if selectedEmployee}
|
|
286
|
+
| 已选择: {selectedEmployee.name}
|
|
287
|
+
{/if}
|
|
288
|
+
</div>
|
|
289
|
+
</ListBox>
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 带懒加载的产品目录
|
|
293
|
+
|
|
294
|
+
```svelte
|
|
295
|
+
<script>
|
|
296
|
+
import ListBox from '@ticatec/uniface-element/list-box';
|
|
297
|
+
import ProductRenderer from './ProductRenderer.svelte';
|
|
298
|
+
|
|
299
|
+
let selectedProducts = [];
|
|
300
|
+
|
|
301
|
+
const loadProducts = async (searchText, pageNo) => {
|
|
302
|
+
const params = new URLSearchParams({
|
|
303
|
+
search: searchText || '',
|
|
304
|
+
page: pageNo.toString(),
|
|
305
|
+
limit: '20'
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const response = await fetch(`/api/products?${params}`);
|
|
310
|
+
const data = await response.json();
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
list: data.products,
|
|
314
|
+
hasMore: data.totalPages > pageNo
|
|
315
|
+
};
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error('加载产品失败:', error);
|
|
318
|
+
return { list: [], hasMore: false };
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
</script>
|
|
322
|
+
|
|
323
|
+
<ListBox
|
|
324
|
+
lazyLoader={loadProducts}
|
|
325
|
+
itemRender={ProductRenderer}
|
|
326
|
+
selectMode="multiple"
|
|
327
|
+
bind:selectedList={selectedProducts}
|
|
328
|
+
style="width: 100%; height: 600px;"
|
|
329
|
+
>
|
|
330
|
+
<div slot="header" style="padding: 12px; background: #2196f3; color: white;">
|
|
331
|
+
<h3 style="margin: 0;">产品目录</h3>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<div slot="footer" style="padding: 8px; background: #f0f0f0; text-align: right;">
|
|
335
|
+
已选择: {selectedProducts.length} 个产品
|
|
336
|
+
<button
|
|
337
|
+
on:click={() => selectedProducts = []}
|
|
338
|
+
disabled={selectedProducts.length === 0}
|
|
339
|
+
>
|
|
340
|
+
清除选择
|
|
341
|
+
</button>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<div slot="loadMoreIndicator" style="padding: 16px; text-align: center;">
|
|
345
|
+
<div class="loading-spinner"></div>
|
|
346
|
+
正在加载更多产品...
|
|
347
|
+
</div>
|
|
348
|
+
</ListBox>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### 带操作的联系人列表
|
|
352
|
+
|
|
353
|
+
```svelte
|
|
354
|
+
<script>
|
|
355
|
+
import ListBox from '@ticatec/uniface-element/list-box';
|
|
356
|
+
import ContactRenderer from './ContactRenderer.svelte';
|
|
357
|
+
|
|
358
|
+
let contacts = [
|
|
359
|
+
{ id: 1, name: '张小明', email: 'zhangxm@example.com', phone: '+86-138-0001-0001' },
|
|
360
|
+
{ id: 2, name: '李小红', email: 'lixh@example.com', phone: '+86-138-0001-0002' }
|
|
361
|
+
];
|
|
362
|
+
|
|
363
|
+
let selectedContact = null;
|
|
364
|
+
|
|
365
|
+
function handleContactDblClick(contact) {
|
|
366
|
+
// 打开联系人详情或编辑模态框
|
|
367
|
+
console.log('打开联系人:', contact);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function handleContactCall(contact) {
|
|
371
|
+
console.log('拨打电话:', contact.phone);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function handleContactEmail(contact) {
|
|
375
|
+
window.location.href = `mailto:${contact.email}`;
|
|
376
|
+
}
|
|
377
|
+
</script>
|
|
378
|
+
|
|
379
|
+
<ListBox
|
|
380
|
+
list={contacts}
|
|
381
|
+
itemRender={ContactRenderer}
|
|
382
|
+
selectMode="single"
|
|
383
|
+
bind:selectedItem={selectedContact}
|
|
384
|
+
onItemDblClick={handleContactDblClick}
|
|
385
|
+
item$props={{ onCall: handleContactCall, onEmail: handleContactEmail }}
|
|
386
|
+
title="联系人"
|
|
387
|
+
style="width: 350px; height: 450px;"
|
|
388
|
+
>
|
|
389
|
+
<div slot="footer" style="padding: 12px; display: flex; gap: 8px;">
|
|
390
|
+
<button
|
|
391
|
+
disabled={!selectedContact}
|
|
392
|
+
on:click={() => handleContactCall(selectedContact)}
|
|
393
|
+
>
|
|
394
|
+
拨打电话
|
|
395
|
+
</button>
|
|
396
|
+
<button
|
|
397
|
+
disabled={!selectedContact}
|
|
398
|
+
on:click={() => handleContactEmail(selectedContact)}
|
|
399
|
+
>
|
|
400
|
+
发送邮件
|
|
401
|
+
</button>
|
|
402
|
+
</div>
|
|
403
|
+
</ListBox>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## 样式
|
|
407
|
+
|
|
408
|
+
### CSS 变量
|
|
409
|
+
|
|
410
|
+
ListBox组件使用CSS变量进行一致的主题化:
|
|
411
|
+
|
|
412
|
+
```css
|
|
413
|
+
:root {
|
|
414
|
+
--uniface-listbox-separator-color: #f0f0f0;
|
|
415
|
+
--uniface-list-box-hover-bg: #ffffe1;
|
|
416
|
+
--uniface-list-box-selected-bg: #b1c8f6;
|
|
417
|
+
--uniface-list-box-header-bottom-border: 1px solid #f0f0f0;
|
|
418
|
+
--uniface-list-box-border-color: #f0f0f0;
|
|
419
|
+
--uniface-list-box-header-bg: #f8fafc;
|
|
420
|
+
--uniface-list-box-footer-bg: #f8fafc;
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### 自定义样式
|
|
425
|
+
|
|
426
|
+
```css
|
|
427
|
+
.custom-listbox {
|
|
428
|
+
--uniface-list-box-hover-bg: #e3f2fd;
|
|
429
|
+
--uniface-list-box-selected-bg: #1976d2;
|
|
430
|
+
--uniface-list-box-border-color: #1976d2;
|
|
431
|
+
border-radius: 8px;
|
|
432
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.custom-listbox .listview-item {
|
|
436
|
+
transition: all 0.2s ease;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.custom-listbox .box-header {
|
|
440
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
441
|
+
color: white;
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## 无障碍功能
|
|
446
|
+
|
|
447
|
+
- **键盘导航**: 方向键导航,回车键选择
|
|
448
|
+
- **屏幕阅读器支持**: 适当的ARIA标签和角色
|
|
449
|
+
- **焦点管理**: 可见的焦点指示器和逻辑tab顺序
|
|
450
|
+
- **选择通知**: 选择改变时的屏幕阅读器反馈
|
|
451
|
+
- **搜索无障碍**: 搜索功能的适当标签
|
|
452
|
+
|
|
453
|
+
## 性能考虑
|
|
454
|
+
|
|
455
|
+
### 大型列表
|
|
456
|
+
|
|
457
|
+
对于大型数据集,使用懒加载:
|
|
458
|
+
|
|
459
|
+
```svelte
|
|
460
|
+
<script>
|
|
461
|
+
const lazyLoader = async (searchText, pageNo) => {
|
|
462
|
+
// 实现高效的服务器端分页
|
|
463
|
+
const response = await fetch(`/api/items?search=${searchText}&page=${pageNo}&limit=50`);
|
|
464
|
+
return await response.json();
|
|
465
|
+
};
|
|
466
|
+
</script>
|
|
467
|
+
|
|
468
|
+
<ListBox
|
|
469
|
+
lazyLoader={lazyLoader}
|
|
470
|
+
itemRender={OptimizedItemRenderer}
|
|
471
|
+
style="height: 400px;"
|
|
472
|
+
/>
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### 虚拟滚动
|
|
476
|
+
|
|
477
|
+
对于极大的列表,可以在项目渲染器中实现虚拟滚动:
|
|
478
|
+
|
|
479
|
+
```svelte
|
|
480
|
+
<!-- VirtualizedItemRenderer.svelte -->
|
|
481
|
+
<script>
|
|
482
|
+
export let item;
|
|
483
|
+
export let index;
|
|
484
|
+
|
|
485
|
+
// 在这里实现虚拟滚动逻辑
|
|
486
|
+
</script>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## 最佳实践
|
|
490
|
+
|
|
491
|
+
### 1. 优化项目渲染器
|
|
492
|
+
|
|
493
|
+
保持项目渲染器简单高效:
|
|
494
|
+
|
|
495
|
+
```svelte
|
|
496
|
+
<!-- 好的做法:简单高效 -->
|
|
497
|
+
<script>
|
|
498
|
+
export let item;
|
|
499
|
+
</script>
|
|
500
|
+
|
|
501
|
+
<div class="item">
|
|
502
|
+
<h4>{item.title}</h4>
|
|
503
|
+
<p>{item.description}</p>
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
<!-- 避免:在渲染器中进行重计算 -->
|
|
507
|
+
<script>
|
|
508
|
+
export let item;
|
|
509
|
+
|
|
510
|
+
// 不要在这里进行重计算
|
|
511
|
+
$: processedData = heavyComputation(item);
|
|
512
|
+
</script>
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### 2. 实现适当的加载状态
|
|
516
|
+
|
|
517
|
+
```svelte
|
|
518
|
+
<ListBox
|
|
519
|
+
lazyLoader={dataLoader}
|
|
520
|
+
itemRender={ItemRenderer}
|
|
521
|
+
>
|
|
522
|
+
<div slot="loadMoreIndicator" class="loading-indicator">
|
|
523
|
+
<div class="spinner"></div>
|
|
524
|
+
正在加载更多项目...
|
|
525
|
+
</div>
|
|
526
|
+
</ListBox>
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### 3. 处理空状态
|
|
530
|
+
|
|
531
|
+
```svelte
|
|
532
|
+
<script>
|
|
533
|
+
let items = [];
|
|
534
|
+
let isLoading = false;
|
|
535
|
+
</script>
|
|
536
|
+
|
|
537
|
+
{#if isLoading}
|
|
538
|
+
<div class="loading-state">正在加载...</div>
|
|
539
|
+
{:else if items.length === 0}
|
|
540
|
+
<div class="empty-state">未找到项目</div>
|
|
541
|
+
{:else}
|
|
542
|
+
<ListBox list={items} itemRender={ItemRenderer} />
|
|
543
|
+
{/if}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### 4. 提供清晰的选择反馈
|
|
547
|
+
|
|
548
|
+
```svelte
|
|
549
|
+
<ListBox
|
|
550
|
+
bind:selectedItem
|
|
551
|
+
selectMode="single"
|
|
552
|
+
style="--uniface-list-box-selected-bg: #2196f3; --uniface-list-box-hover-bg: #e3f2fd;"
|
|
553
|
+
/>
|
|
554
|
+
|
|
555
|
+
<div class="selection-info">
|
|
556
|
+
{#if selectedItem}
|
|
557
|
+
已选择: {selectedItem.name}
|
|
558
|
+
{:else}
|
|
559
|
+
未选择项目
|
|
560
|
+
{/if}
|
|
561
|
+
</div>
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
## 组件优化建议
|
|
565
|
+
|
|
566
|
+
分析代码后,发现以下可以优化的地方:
|
|
567
|
+
|
|
568
|
+
### 1. 性能优化
|
|
569
|
+
|
|
570
|
+
```svelte
|
|
571
|
+
<!-- 当前实现中的问题 -->
|
|
572
|
+
<script>
|
|
573
|
+
// 在每次过滤时都重新创建数组
|
|
574
|
+
const doFilter = () => {
|
|
575
|
+
const newList: Array<any> = [];
|
|
576
|
+
list.forEach(item => {
|
|
577
|
+
if (filter?.(item, filterText)) {
|
|
578
|
+
newList.push(item)
|
|
579
|
+
}
|
|
580
|
+
filteredList = newList; // 这行代码位置有问题
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
</script>
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
建议优化为:
|
|
587
|
+
|
|
588
|
+
```svelte
|
|
589
|
+
<script>
|
|
590
|
+
const doFilter = () => {
|
|
591
|
+
if (!list || !filter) {
|
|
592
|
+
filteredList = list || [];
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
filteredList = list.filter(item => filter(item, filterText));
|
|
597
|
+
|
|
598
|
+
// 清除单选状态
|
|
599
|
+
if (selectMode === 'single') {
|
|
600
|
+
selectedItem = null;
|
|
601
|
+
onSelectChange?.(null);
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
</script>
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### 2. 键盘导航优化
|
|
608
|
+
|
|
609
|
+
当前缺少键盘导航支持,建议添加:
|
|
610
|
+
|
|
611
|
+
```svelte
|
|
612
|
+
<script>
|
|
613
|
+
let focusedIndex = -1;
|
|
614
|
+
|
|
615
|
+
function handleKeyDown(event) {
|
|
616
|
+
if (!filteredList || filteredList.length === 0) return;
|
|
617
|
+
|
|
618
|
+
switch (event.key) {
|
|
619
|
+
case 'ArrowDown':
|
|
620
|
+
event.preventDefault();
|
|
621
|
+
focusedIndex = Math.min(focusedIndex + 1, filteredList.length - 1);
|
|
622
|
+
break;
|
|
623
|
+
case 'ArrowUp':
|
|
624
|
+
event.preventDefault();
|
|
625
|
+
focusedIndex = Math.max(focusedIndex - 1, 0);
|
|
626
|
+
break;
|
|
627
|
+
case 'Enter':
|
|
628
|
+
if (focusedIndex >= 0 && selectMode === 'single') {
|
|
629
|
+
selectedItem = filteredList[focusedIndex];
|
|
630
|
+
onSelectChange?.(selectedItem);
|
|
631
|
+
}
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
</script>
|
|
636
|
+
|
|
637
|
+
<div on:keydown={handleKeyDown} tabindex="0">
|
|
638
|
+
<!-- 列表内容 -->
|
|
639
|
+
</div>
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### 3. 无障碍访问优化
|
|
643
|
+
|
|
644
|
+
添加适当的ARIA属性:
|
|
645
|
+
|
|
646
|
+
```svelte
|
|
647
|
+
<div
|
|
648
|
+
class="listbox-content"
|
|
649
|
+
role="listbox"
|
|
650
|
+
aria-multiselectable={selectMode === 'multiple'}
|
|
651
|
+
bind:this={scrollElement}
|
|
652
|
+
on:scroll={handleScroll}
|
|
653
|
+
>
|
|
654
|
+
{#each filteredList as item, index}
|
|
655
|
+
<div
|
|
656
|
+
class="listview-item"
|
|
657
|
+
role="option"
|
|
658
|
+
aria-selected={selectMode === 'single' ? item === selectedItem : selectedList.includes(item)}
|
|
659
|
+
tabindex="-1"
|
|
660
|
+
>
|
|
661
|
+
<!-- 项目内容 -->
|
|
662
|
+
</div>
|
|
663
|
+
{/each}
|
|
664
|
+
</div>
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
## 浏览器支持
|
|
668
|
+
|
|
669
|
+
- **现代浏览器**: Chrome、Firefox、Safari、Edge(最新版本)
|
|
670
|
+
- **移动浏览器**: iOS Safari、Chrome Mobile、Firefox Mobile
|
|
671
|
+
- **Flexbox支持**: 布局功能必需
|
|
672
|
+
- **Intersection Observer**: 懒加载需要(可提供polyfill)
|
|
673
|
+
|
|
674
|
+
## 相关组件
|
|
675
|
+
|
|
676
|
+
- **SearchBox**: 内部用于过滤功能
|
|
677
|
+
- **CheckBox**: 用于多选模式
|
|
678
|
+
- **Box**: 基础布局组件
|
|
679
|
+
|
|
680
|
+
## 许可证
|
|
681
|
+
|
|
682
|
+
MIT许可证 - 详情请参阅LICENSE文件。
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import CommonPicker from "../common/CommonPicker.svelte";
|
|
7
7
|
|
|
8
8
|
type OnActionHandler = () => void;
|
|
9
|
+
type OnFocusHandler = (event: FocusEvent) => void;
|
|
10
|
+
type OnBlurHandler = (event: FocusEvent) => void;
|
|
9
11
|
|
|
10
12
|
export let variant: '' | 'plain' | 'outlined' | 'filled' = '';
|
|
11
13
|
export let disabled: boolean = false;
|
|
@@ -17,8 +19,10 @@
|
|
|
17
19
|
export let placeholder: string = "";
|
|
18
20
|
export let displayMode: DisplayMode = DisplayMode.Edit;
|
|
19
21
|
export let text: string;
|
|
20
|
-
export let
|
|
22
|
+
export let onchange: OnChangeHandler<any> = null as unknown as OnChangeHandler<any>;
|
|
21
23
|
export let onAction: OnActionHandler = null as unknown as OnActionHandler;
|
|
24
|
+
export let onfocus: OnFocusHandler = null as unknown as OnFocusHandler;
|
|
25
|
+
export let onblur: OnBlurHandler = null as unknown as OnBlurHandler;
|
|
22
26
|
|
|
23
27
|
let oldValue = value;
|
|
24
28
|
|
|
@@ -29,17 +33,25 @@
|
|
|
29
33
|
const clean = () => {
|
|
30
34
|
value = null;
|
|
31
35
|
text = '';
|
|
32
|
-
|
|
36
|
+
onchange?.(value)
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
const handleActionIconClick = async (event: MouseEvent) => {
|
|
36
40
|
onAction?.();
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
const handleFocus = (event: FocusEvent) => {
|
|
44
|
+
onfocus?.(event);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const handleBlur = (event: FocusEvent) => {
|
|
48
|
+
onblur?.(event);
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
</script>
|
|
40
52
|
|
|
41
53
|
<CommonPicker {displayMode} {variant} {style} {compact} className="multiple" dropDownIcon="icon_google_keyboard_control"
|
|
42
54
|
canClean={!mandatory && value!=null} autoFit {clean} textValue={text} {readonly} {disabled} iconClickHandler={handleActionIconClick}>
|
|
43
55
|
<input style="width: 100%" {placeholder} class="text-editor" readonly value={text??''} {disabled}
|
|
44
|
-
on:
|
|
56
|
+
on:focus={handleFocus} on:blur={handleBlur} on:click={handleActionIconClick}/>
|
|
45
57
|
</CommonPicker>
|