@ticatec/uniface-element 0.2.0 → 0.2.2
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/README.md +320 -1
- package/README_CN.md +392 -0
- package/dist/accordion/Accordion.svelte +1 -1
- package/dist/action-bar/ActionBar.svelte +7 -1
- package/dist/attachment-files/FileRender.svelte +1 -1
- package/dist/attachment-files/FileUploadBar.svelte +7 -6
- package/dist/attachment-files/FileUploadPanel.svelte +2 -2
- package/dist/base-calendar/DateContext.js +5 -4
- package/dist/base-calendar/YearPickupPanel.svelte +4 -4
- package/dist/card/Card.svelte +1 -1
- package/dist/card/Card.svelte.d.ts +1 -1
- package/dist/cascade-options-select/CascadeOptionSelect.svelte +2 -3
- package/dist/cascade-options-select/CascadeOptionSelect.svelte.d.ts +46 -1
- package/dist/cascade-options-select/CascadePanel.svelte +1 -1
- package/dist/checkbox/CheckBox.svelte +1 -1
- package/dist/color-picker/ColorPicker.svelte +9 -9
- package/dist/color-picker/ColorPicker.svelte.d.ts +0 -2
- package/dist/color-picker/ColorPickerPanel.svelte +11 -15
- package/dist/color-picker/ColorPickerPanel.svelte.d.ts +1 -1
- package/dist/common/CommonPicker.svelte +2 -2
- package/dist/common/CommonPicker.svelte.d.ts +1 -1
- package/dist/common/MouseClickHandler.d.ts +1 -1
- package/dist/common/Popover.svelte +28 -6
- package/dist/common/Popover.svelte.d.ts +9 -3
- package/dist/common/uniface-utils.js +6 -4
- package/dist/common-editor/CommonEditor.svelte +1 -1
- package/dist/common-editor/CommonEditor.svelte.d.ts +1 -1
- package/dist/common-editor/NumberInput.svelte +1 -1
- package/dist/common-editor/NumberInput.svelte.d.ts +1 -1
- package/dist/concise-data-table/ConciseListTable.svelte +7 -2
- package/dist/concise-data-table/ConciseListTable.svelte.d.ts +1 -0
- package/dist/concise-data-table/DataRow.svelte +1 -1
- package/dist/concise-data-table/TableOptions.d.ts +1 -1
- package/dist/data-table/DataTable.svelte +1 -1
- package/dist/data-table/parts/ActionsPanel.svelte +4 -3
- package/dist/data-table/parts/ActionsRow.svelte +3 -3
- package/dist/data-table/parts/ContentPanel.svelte +3 -2
- package/dist/data-table/parts/FixedColumnsPanel.svelte +0 -1
- package/dist/data-table/parts/FixedColumnsPanel.svelte.d.ts +1 -2
- package/dist/data-table/parts/FixedHeaderPanel.svelte +2 -2
- package/dist/data-table/parts/FixedRow.svelte +2 -1
- package/dist/data-table/parts/FixedRow.svelte.d.ts +1 -1
- package/dist/data-table/parts/HrefCell.svelte +23 -2
- package/dist/data-table/parts/PopupMenu.svelte +25 -2
- package/dist/data-table/parts/PopupMenu.svelte.d.ts +8 -1
- package/dist/date-picker/DatePicker.svelte +0 -1
- package/dist/date-picker/DateTimePicker.svelte +3 -3
- package/dist/date-picker/ScrollBar.svelte +4 -7
- package/dist/date-range/DateRangeEditor.svelte +1 -1
- package/dist/dialog/CommonDialog.svelte +8 -5
- package/dist/dialog/CommonDialog.svelte.d.ts +2 -1
- package/dist/dialog/Dialog.svelte +37 -12
- package/dist/dialog/Dialog.svelte.d.ts +4 -3
- package/dist/dialog/DialogBoard.svelte +11 -8
- package/dist/dialog/DialogWrapper.svelte +10 -0
- package/dist/{common-editor/DateInput.svelte.d.ts → dialog/DialogWrapper.svelte.d.ts} +6 -13
- package/dist/dialog/Dialogs.js +1 -1
- package/dist/dialog/ModalResult.d.ts +5 -0
- package/dist/dialog/ModalResult.js +5 -0
- package/dist/drawer/Drawer.svelte +1 -1
- package/dist/form-field/FormField.svelte +2 -1
- package/dist/form-field/FormField.svelte.d.ts +1 -0
- package/dist/form-panel/flex-row-form/FormContainer.svelte +1 -1
- package/dist/form-panel/grid-form/EmptyCell.svelte +8 -7
- package/dist/form-panel/grid-form/EmptyCell.svelte.d.ts +2 -2
- package/dist/form-panel/grid-form/GridField.svelte +1 -1
- package/dist/i18nRes/i18nRes.d.ts +2 -0
- package/dist/i18nRes/i18nRes.js +44 -0
- package/dist/i18nRes/index.d.ts +2 -0
- package/dist/i18nRes/index.js +2 -0
- package/dist/image-files/ImageFilesField.svelte +1 -1
- package/dist/indicator/IndicatorBoard.svelte +1 -1
- package/dist/inline-cell-editor/InvalidEditor.svelte +1 -0
- package/dist/input-options-select/InputOptionsSelect.svelte +6 -6
- package/dist/input-options-select/InputOptionsSelect.svelte.d.ts +1 -1
- package/dist/layout/flex/FlexBlock.svelte +1 -1
- package/dist/lookup-editor/LookupEditor.svelte +1 -1
- package/dist/memo-editor/MemoEditor.svelte +2 -2
- package/dist/message-box/IMessageBox.d.ts +2 -4
- package/dist/message-box/IMessageBox.js +4 -6
- package/dist/message-box/MessageBoxBoard.svelte +1 -3
- package/dist/options-multi-select/OptionsMultiSelect.svelte +1 -1
- package/dist/prompts-text-editor/PromptsTextEditor.svelte +2 -2
- package/dist/prompts-text-editor/PromptsTextEditor.svelte.d.ts +1 -1
- package/dist/property-editor/PropertyEditor.svelte +3 -3
- package/dist/text-editor/PasswordEditor.svelte +1 -1
- package/dist/transfer/Transfer.svelte +17 -12
- package/dist/transfer/Transfer.svelte.d.ts +0 -1
- package/dist/unit-number-editor/UnitNumberEditor.svelte +2 -2
- package/docs/buttons/cn/ActionBar.md +459 -0
- package/docs/buttons/cn/Button.md +228 -0
- package/docs/buttons/cn/IconButton.md +294 -0
- package/docs/buttons/cn/README.md +600 -0
- package/docs/buttons/cn/TextButton.md +163 -0
- package/docs/buttons/en/ActionBar.md +459 -0
- package/docs/buttons/en/Button.md +228 -0
- package/docs/buttons/en/IconButton.md +294 -0
- package/docs/buttons/en/README.md +600 -0
- package/docs/buttons/en/TextButton.md +163 -0
- package/docs/containers/cn/accordion.md +622 -0
- package/docs/containers/cn/box.md +445 -0
- package/docs/containers/cn/card.md +404 -0
- package/docs/containers/cn/page.md +576 -0
- package/docs/containers/cn/tabs.md +686 -0
- package/docs/containers/en/accordion.md +622 -0
- package/docs/containers/en/box.md +445 -0
- package/docs/containers/en/card.md +404 -0
- package/docs/containers/en/page.md +576 -0
- package/docs/containers/en/tabs.md +686 -0
- package/docs/data-display/cn/concise-data-table.md +731 -0
- package/docs/data-display/cn/listbox.md +786 -0
- package/docs/data-display/cn/treeview.md +863 -0
- package/docs/data-display/en/concise-data-table.md +732 -0
- package/docs/data-display/en/listbox.md +786 -0
- package/docs/data-display/en/treeview.md +863 -0
- package/docs/data-table/cn/README.md +596 -0
- package/docs/data-table/en/README.md +596 -0
- package/docs/dialog/cn/README.md +767 -0
- package/docs/dialog/en/README.md +767 -0
- package/docs/form/cn/README.md +847 -0
- package/docs/form/en/README.md +847 -0
- package/docs/form-components/cn/README.md +495 -0
- package/docs/form-components/cn/attachmentfilesfield.md +757 -0
- package/docs/form-components/cn/cascade-options-select.md +374 -0
- package/docs/form-components/cn/checkbox.md +58 -0
- package/docs/form-components/cn/date-picker.md +138 -0
- package/docs/form-components/cn/datetime-picker.md +228 -0
- package/docs/form-components/cn/group-checkbox.md +405 -0
- package/docs/form-components/cn/group-radiobox.md +525 -0
- package/docs/form-components/cn/imagefilesfield.md +651 -0
- package/docs/form-components/cn/input-options-select.md +250 -0
- package/docs/form-components/cn/lookup-editor.md +406 -0
- package/docs/form-components/cn/memoeditor.md +409 -0
- package/docs/form-components/cn/number-editor.md +261 -0
- package/docs/form-components/cn/options-multi-select.md +177 -0
- package/docs/form-components/cn/options-select.md +83 -0
- package/docs/form-components/cn/passwordeditor.md +538 -0
- package/docs/form-components/cn/prompts-text-editor.md +241 -0
- package/docs/form-components/cn/radiobutton.md +84 -0
- package/docs/form-components/cn/switch.md +178 -0
- package/docs/form-components/cn/text-editor.md +142 -0
- package/docs/form-components/cn/time-editor.md +159 -0
- package/docs/form-components/cn/unit-number-editor.md +164 -0
- package/docs/form-components/en/README.md +463 -0
- package/docs/form-components/en/attachmentfilesfield.md +757 -0
- package/docs/form-components/en/cascade-options-select.md +358 -0
- package/docs/form-components/en/checkbox.md +59 -0
- package/docs/form-components/en/date-picker.md +138 -0
- package/docs/form-components/en/datetime-picker.md +228 -0
- package/docs/form-components/en/group-checkbox.md +395 -0
- package/docs/form-components/en/group-radiobox.md +498 -0
- package/docs/form-components/en/imagefilesfield.md +651 -0
- package/docs/form-components/en/input-options-select.md +239 -0
- package/docs/form-components/en/lookup-editor.md +389 -0
- package/docs/form-components/en/memoeditor.md +409 -0
- package/docs/form-components/en/number-editor.md +249 -0
- package/docs/form-components/en/options-multi-select.md +177 -0
- package/docs/form-components/en/options-select.md +83 -0
- package/docs/form-components/en/passwordeditor.md +538 -0
- package/docs/form-components/en/prompts-text-editor.md +241 -0
- package/docs/form-components/en/radiobutton.md +84 -0
- package/docs/form-components/en/switch.md +180 -0
- package/docs/form-components/en/text-editor.md +142 -0
- package/docs/form-components/en/time-editor.md +159 -0
- package/docs/form-components/en/unit-number-editor.md +164 -0
- package/docs/form-layout/cn/criteria-field.md +652 -0
- package/docs/form-layout/cn/flex-form.md +454 -0
- package/docs/form-layout/cn/flex-row-form.md +542 -0
- package/docs/form-layout/cn/form-field.md +923 -0
- package/docs/form-layout/cn/grid-form.md +676 -0
- package/docs/form-layout/en/criteria-field.md +652 -0
- package/docs/form-layout/en/flex-form.md +454 -0
- package/docs/form-layout/en/flex-row-form.md +542 -0
- package/docs/form-layout/en/form-field.md +923 -0
- package/docs/form-layout/en/grid-form.md +676 -0
- package/docs/global/cn/README.md +534 -0
- package/docs/global/en/README.md +534 -0
- package/docs/layout/cn/classiclayout.md +460 -0
- package/docs/layout/cn/headerlayout.md +524 -0
- package/docs/layout/cn/sidebarlayout.md +263 -0
- package/docs/layout/en/classiclayout.md +951 -0
- package/docs/layout/en/headerlayout.md +524 -0
- package/docs/layout/en/sidebarlayout.md +262 -0
- package/docs/misc/cn/README.md +1217 -0
- package/docs/misc/en/README.md +1217 -0
- package/docs/navigation/cn/breadcrumb.md +291 -0
- package/docs/navigation/cn/navigator.md +322 -0
- package/docs/navigation/cn/navigatormenu.md +512 -0
- package/docs/navigation/cn/progressbar.md +199 -0
- package/docs/navigation/cn/progressstepbar.md +299 -0
- package/docs/navigation/en/breadcrumb.md +291 -0
- package/docs/navigation/en/navigator.md +322 -0
- package/docs/navigation/en/navigatormenu.md +512 -0
- package/docs/navigation/en/progressbar.md +199 -0
- package/docs/navigation/en/progressstepbar.md +299 -0
- package/docs/search-filters/cn/daterange.md +1156 -0
- package/docs/search-filters/cn/numberrange.md +688 -0
- package/docs/search-filters/cn/searchbox.md +1582 -0
- package/docs/search-filters/en/daterange.md +1156 -0
- package/docs/search-filters/en/numberrange.md +688 -0
- package/docs/search-filters/en/searchbox.md +1582 -0
- package/package.json +53 -13
- package/dist/common-editor/DateInput.svelte +0 -27
- package/dist/i18n_resources/index.d.ts +0 -3
- package/dist/i18n_resources/index.js +0 -3
- /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.d.ts +0 -0
- /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.js +0 -0
- /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.d.ts +0 -0
- /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.js +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# InputOptionsSelect 组件
|
|
2
|
+
|
|
3
|
+
带输入和异步搜索功能的选择组件。
|
|
4
|
+
|
|
5
|
+
## 类型定义
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
interface LoaderResult<T = any> {
|
|
9
|
+
list: T[];
|
|
10
|
+
hasMore: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type LazyLoader<T = any> = (keyword: string, pageNo: number) => Promise<LoaderResult<T>>;
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 属性
|
|
17
|
+
|
|
18
|
+
| 属性 | 类型 | 默认值 | 描述 |
|
|
19
|
+
|------|------|--------|------|
|
|
20
|
+
| `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | 外观变体 |
|
|
21
|
+
| `disabled` | `boolean` | `false` | 是否禁用 |
|
|
22
|
+
| `readonly` | `boolean` | `false` | 是否只读 |
|
|
23
|
+
| `compact` | `boolean` | `false` | 是否紧凑模式 |
|
|
24
|
+
| `text` | `string` | `''` | 显示文本 |
|
|
25
|
+
| `value` | `T | null` | `null` | 选中的值 |
|
|
26
|
+
| `keyField` | `string` | `'code'` | 值字段名 |
|
|
27
|
+
| `textField` | `string` | `'text'` | 显示字段名 |
|
|
28
|
+
| `menu$height` | `number` | `0` | 下拉菜单高度 |
|
|
29
|
+
| `placeholder` | `string` | `''` | 占位符文本 |
|
|
30
|
+
| `displayMode` | `DisplayMode` | `DisplayMode.Edit` | 显示模式 |
|
|
31
|
+
| `itemRender` | `ComponentType` | `null` | 自定义选项渲染组件 |
|
|
32
|
+
| `lazyLoader` | `LazyLoader<T>` | - | 异步数据加载器 |
|
|
33
|
+
| `style` | `string` | `''` | 自定义样式 |
|
|
34
|
+
| `onChange` | `OnChangeHandler<T | null>` | - | 值变化回调 |
|
|
35
|
+
|
|
36
|
+
## 使用示例
|
|
37
|
+
|
|
38
|
+
```svelte
|
|
39
|
+
<script lang="ts">
|
|
40
|
+
import InputOptionsSelect, { type LazyLoader } from '@ticatec/uniface-element/InputOptionsSelect';
|
|
41
|
+
|
|
42
|
+
interface User {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
email: string;
|
|
46
|
+
department: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface Product {
|
|
50
|
+
code: string;
|
|
51
|
+
text: string;
|
|
52
|
+
category: string;
|
|
53
|
+
price: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let selectedUser: User | null = null;
|
|
57
|
+
let selectedProduct: Product | null = null;
|
|
58
|
+
|
|
59
|
+
// 用户搜索
|
|
60
|
+
const searchUsers: LazyLoader<User> = async (keyword: string, pageNo: number): Promise<LoaderResult<User>> => {
|
|
61
|
+
const response: Response = await fetch(`/api/users/search?q=${encodeURIComponent(keyword)}&page=${pageNo}`);
|
|
62
|
+
const data = await response.json();
|
|
63
|
+
return {
|
|
64
|
+
list: data.users,
|
|
65
|
+
hasMore: data.hasMore
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// 产品搜索
|
|
70
|
+
const searchProducts: LazyLoader<Product> = async (keyword: string, pageNo: number): Promise<LoaderResult<Product>> => {
|
|
71
|
+
// 模拟API延迟
|
|
72
|
+
await new Promise<void>(resolve => setTimeout(resolve, 300));
|
|
73
|
+
|
|
74
|
+
const mockProducts: Product[] = [
|
|
75
|
+
{ code: 'P001', text: 'iPhone 15 Pro', category: '手机', price: 7999 },
|
|
76
|
+
{ code: 'P002', text: 'MacBook Air M2', category: '笔记本', price: 8999 },
|
|
77
|
+
{ code: 'P003', text: 'iPad Pro 12.9', category: '平板', price: 6999 }
|
|
78
|
+
].filter((p: Product): boolean => p.text.toLowerCase().includes(keyword.toLowerCase()));
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
list: mockProducts,
|
|
82
|
+
hasMore: false
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleUserChange = (user: User): void => {
|
|
87
|
+
selectedUser = user;
|
|
88
|
+
console.log('选择的用户:', user);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleProductChange = (product: Product): void => {
|
|
92
|
+
selectedProduct = product;
|
|
93
|
+
console.log('选择的产品:', product);
|
|
94
|
+
};
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<!-- 用户搜索选择 -->
|
|
98
|
+
<InputOptionsSelect
|
|
99
|
+
placeholder="请输入用户名搜索"
|
|
100
|
+
lazyLoader={searchUsers}
|
|
101
|
+
keyField="id"
|
|
102
|
+
textField="name"
|
|
103
|
+
onChange={handleUserChange}
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
<!-- 产品搜索选择 -->
|
|
107
|
+
<InputOptionsSelect
|
|
108
|
+
placeholder="请输入产品名称搜索"
|
|
109
|
+
lazyLoader={searchProducts}
|
|
110
|
+
keyField="code"
|
|
111
|
+
textField="text"
|
|
112
|
+
onChange={handleProductChange}
|
|
113
|
+
/>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 高级用法
|
|
117
|
+
|
|
118
|
+
### 自定义选项渲染
|
|
119
|
+
```svelte
|
|
120
|
+
<script lang="ts">
|
|
121
|
+
import type { ComponentType } from 'svelte';
|
|
122
|
+
import UserItemRenderer from './UserItemRenderer.svelte';
|
|
123
|
+
|
|
124
|
+
const searchUsersWithDetails: LazyLoader<User> = async (keyword: string, pageNo: number): Promise<LoaderResult<User>> => {
|
|
125
|
+
const response: Response = await fetch(`/api/users/search-detailed?q=${keyword}&page=${pageNo}`);
|
|
126
|
+
return await response.json();
|
|
127
|
+
};
|
|
128
|
+
</script>
|
|
129
|
+
|
|
130
|
+
<InputOptionsSelect
|
|
131
|
+
placeholder="搜索用户"
|
|
132
|
+
lazyLoader={searchUsersWithDetails}
|
|
133
|
+
keyField="id"
|
|
134
|
+
textField="name"
|
|
135
|
+
itemRender={UserItemRenderer}
|
|
136
|
+
onChange={handleUserChange}
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
<!-- UserItemRenderer.svelte -->
|
|
140
|
+
<script lang="ts">
|
|
141
|
+
interface User {
|
|
142
|
+
id: string;
|
|
143
|
+
name: string;
|
|
144
|
+
email: string;
|
|
145
|
+
department: string;
|
|
146
|
+
avatar: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export let item: User;
|
|
150
|
+
</script>
|
|
151
|
+
|
|
152
|
+
<div class="user-item">
|
|
153
|
+
<div class="user-avatar">
|
|
154
|
+
<img src={item.avatar} alt={item.name} />
|
|
155
|
+
</div>
|
|
156
|
+
<div class="user-info">
|
|
157
|
+
<div class="user-name">{item.name}</div>
|
|
158
|
+
<div class="user-email">{item.email}</div>
|
|
159
|
+
<div class="user-department">{item.department}</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 带缓存的搜索
|
|
165
|
+
```svelte
|
|
166
|
+
<script lang="ts">
|
|
167
|
+
interface SearchCache {
|
|
168
|
+
[key: string]: {
|
|
169
|
+
data: any[];
|
|
170
|
+
timestamp: number;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let searchCache: SearchCache = {};
|
|
175
|
+
const cacheTimeout: number = 5 * 60 * 1000; // 5分钟缓存
|
|
176
|
+
|
|
177
|
+
const cachedSearchLoader: LazyLoader = async (keyword: string, pageNo: number): Promise<LoaderResult> => {
|
|
178
|
+
const cacheKey: string = `${keyword}-${pageNo}`;
|
|
179
|
+
const cached = searchCache[cacheKey];
|
|
180
|
+
|
|
181
|
+
// 检查缓存
|
|
182
|
+
if (cached && Date.now() - cached.timestamp < cacheTimeout) {
|
|
183
|
+
console.log('使用缓存数据');
|
|
184
|
+
return {
|
|
185
|
+
list: cached.data,
|
|
186
|
+
hasMore: false
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 从API获取数据
|
|
191
|
+
const response: Response = await fetch(`/api/search?q=${keyword}&page=${pageNo}`);
|
|
192
|
+
const result: LoaderResult = await response.json();
|
|
193
|
+
|
|
194
|
+
// 缓存结果
|
|
195
|
+
searchCache[cacheKey] = {
|
|
196
|
+
data: result.list,
|
|
197
|
+
timestamp: Date.now()
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
};
|
|
202
|
+
</script>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## 最佳实践
|
|
206
|
+
|
|
207
|
+
### 1. 防抖搜索
|
|
208
|
+
组件内部已经实现了防抖机制,默认延迟500ms后执行搜索。
|
|
209
|
+
|
|
210
|
+
### 2. 错误处理
|
|
211
|
+
```svelte
|
|
212
|
+
<script lang="ts">
|
|
213
|
+
const robustSearchLoader: LazyLoader = async (keyword: string, pageNo: number): Promise<LoaderResult> => {
|
|
214
|
+
try {
|
|
215
|
+
const response: Response = await fetch(`/api/search?q=${keyword}&page=${pageNo}`);
|
|
216
|
+
|
|
217
|
+
if (!response.ok) {
|
|
218
|
+
throw new Error(`搜索失败: ${response.status}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return await response.json();
|
|
222
|
+
} catch (error: unknown) {
|
|
223
|
+
console.error('搜索出错:', error);
|
|
224
|
+
return {
|
|
225
|
+
list: [],
|
|
226
|
+
hasMore: false
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
</script>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### 3. 分页加载
|
|
234
|
+
```svelte
|
|
235
|
+
<script lang="ts">
|
|
236
|
+
const paginatedLoader: LazyLoader = async (keyword: string, pageNo: number): Promise<LoaderResult> => {
|
|
237
|
+
const pageSize: number = 20;
|
|
238
|
+
const response: Response = await fetch(
|
|
239
|
+
`/api/search?q=${keyword}&page=${pageNo}&size=${pageSize}`
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const data = await response.json();
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
list: data.items,
|
|
246
|
+
hasMore: data.total > pageNo * pageSize
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
</script>
|
|
250
|
+
```
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
# LookupEditor 组件
|
|
2
|
+
|
|
3
|
+
查找编辑器组件,通过模态对话框进行复杂的查找和选择操作。
|
|
4
|
+
|
|
5
|
+
## 类型定义
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
type OnActionHandler = () => void;
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 属性
|
|
12
|
+
|
|
13
|
+
| 属性 | 类型 | 默认值 | 描述 |
|
|
14
|
+
|------|------|--------|------|
|
|
15
|
+
| `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | 外观变体 |
|
|
16
|
+
| `disabled` | `boolean` | `false` | 是否禁用 |
|
|
17
|
+
| `readonly` | `boolean` | `false` | 是否只读 |
|
|
18
|
+
| `compact` | `boolean` | `false` | 是否紧凑模式 |
|
|
19
|
+
| `mandatory` | `boolean` | `false` | 是否必填 |
|
|
20
|
+
| `value` | `T | null` | `null` | 选中的值 |
|
|
21
|
+
| `text` | `string` | `''` | 显示文本 |
|
|
22
|
+
| `placeholder` | `string` | `''` | 占位符文本 |
|
|
23
|
+
| `displayMode` | `DisplayMode` | `DisplayMode.Edit` | 显示模式 |
|
|
24
|
+
| `style` | `string` | `''` | 自定义样式 |
|
|
25
|
+
| `onChange` | `OnChangeHandler<T | null>` | - | 值变化回调 |
|
|
26
|
+
| `onAction` | `OnActionHandler` | - | 查找动作回调 |
|
|
27
|
+
|
|
28
|
+
## 使用示例
|
|
29
|
+
|
|
30
|
+
```svelte
|
|
31
|
+
<script lang="ts">
|
|
32
|
+
import LookupEditor from '@ticatec/uniface-element/LookupEditor';
|
|
33
|
+
import CustomerLookupModal from './CustomerLookupModal.svelte';
|
|
34
|
+
import ProductLookupModal from './ProductLookupModal.svelte';
|
|
35
|
+
|
|
36
|
+
interface Customer {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
code: string;
|
|
40
|
+
email: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Product {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
price: number;
|
|
47
|
+
category: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let selectedCustomer: string | null = null;
|
|
51
|
+
let customerText: string = '';
|
|
52
|
+
let selectedProduct: string | null = null;
|
|
53
|
+
let productText: string = '';
|
|
54
|
+
let showCustomerModal: boolean = false;
|
|
55
|
+
let showProductModal: boolean = false;
|
|
56
|
+
|
|
57
|
+
// 客户查找
|
|
58
|
+
const handleCustomerLookup = (): void => {
|
|
59
|
+
showCustomerModal = true;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const handleCustomerSelect = (customer: Customer): void => {
|
|
63
|
+
selectedCustomer = customer.id;
|
|
64
|
+
customerText = `${customer.name} (${customer.code})`;
|
|
65
|
+
showCustomerModal = false;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handleCustomerChange = (value: string | null): void => {
|
|
69
|
+
selectedCustomer = value;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// 产品查找
|
|
73
|
+
const handleProductLookup = (): void => {
|
|
74
|
+
showProductModal = true;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleProductSelect = (product: Product): void => {
|
|
78
|
+
selectedProduct = product.id;
|
|
79
|
+
productText = `${product.name} - ¥${product.price}`;
|
|
80
|
+
showProductModal = false;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleProductChange = (value: string | null): void => {
|
|
84
|
+
selectedProduct = value;
|
|
85
|
+
};
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<!-- 客户查找 -->
|
|
89
|
+
<LookupEditor
|
|
90
|
+
placeholder="请选择客户"
|
|
91
|
+
bind:value={selectedCustomer}
|
|
92
|
+
bind:text={customerText}
|
|
93
|
+
onAction={handleCustomerLookup}
|
|
94
|
+
onChange={handleCustomerChange}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
<!-- 产品查找 -->
|
|
98
|
+
<LookupEditor
|
|
99
|
+
placeholder="请选择产品"
|
|
100
|
+
mandatory={true}
|
|
101
|
+
bind:value={selectedProduct}
|
|
102
|
+
bind:text={productText}
|
|
103
|
+
onAction={handleProductLookup}
|
|
104
|
+
onChange={handleProductChange}
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
<!-- 客户查找模态框 -->
|
|
108
|
+
{#if showCustomerModal}
|
|
109
|
+
<CustomerLookupModal
|
|
110
|
+
onSelect={handleCustomerSelect}
|
|
111
|
+
onClose={() => showCustomerModal = false}
|
|
112
|
+
/>
|
|
113
|
+
{/if}
|
|
114
|
+
|
|
115
|
+
<!-- 产品查找模态框 -->
|
|
116
|
+
{#if showProductModal}
|
|
117
|
+
<ProductLookupModal
|
|
118
|
+
onSelect={handleProductSelect}
|
|
119
|
+
onClose={() => showProductModal = false}
|
|
120
|
+
/>
|
|
121
|
+
{/if}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 高级用法
|
|
125
|
+
|
|
126
|
+
### 多步骤查找流程
|
|
127
|
+
```svelte
|
|
128
|
+
<script lang="ts">
|
|
129
|
+
interface Supplier {
|
|
130
|
+
id: string;
|
|
131
|
+
name: string;
|
|
132
|
+
country: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface SupplierProduct {
|
|
136
|
+
id: string;
|
|
137
|
+
name: string;
|
|
138
|
+
supplier_id: string;
|
|
139
|
+
price: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let selectedSupplier: string | null = null;
|
|
143
|
+
let supplierText: string = '';
|
|
144
|
+
let selectedSupplierProduct: string | null = null;
|
|
145
|
+
let supplierProductText: string = '';
|
|
146
|
+
let showSupplierModal: boolean = false;
|
|
147
|
+
let showSupplierProductModal: boolean = false;
|
|
148
|
+
|
|
149
|
+
const handleSupplierLookup = (): void => {
|
|
150
|
+
showSupplierModal = true;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const handleSupplierSelect = (supplier: Supplier): void => {
|
|
154
|
+
selectedSupplier = supplier.id;
|
|
155
|
+
supplierText = `${supplier.name} (${supplier.country})`;
|
|
156
|
+
showSupplierModal = false;
|
|
157
|
+
|
|
158
|
+
// 重置供应商产品选择
|
|
159
|
+
selectedSupplierProduct = null;
|
|
160
|
+
supplierProductText = '';
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const handleSupplierProductLookup = (): void => {
|
|
164
|
+
if (!selectedSupplier) {
|
|
165
|
+
alert('请先选择供应商');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
showSupplierProductModal = true;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const handleSupplierProductSelect = (product: SupplierProduct): void => {
|
|
172
|
+
selectedSupplierProduct = product.id;
|
|
173
|
+
supplierProductText = `${product.name} - ¥${product.price}`;
|
|
174
|
+
showSupplierProductModal = false;
|
|
175
|
+
};
|
|
176
|
+
</script>
|
|
177
|
+
|
|
178
|
+
<div class="lookup-flow">
|
|
179
|
+
<div class="form-field">
|
|
180
|
+
<label>供应商:</label>
|
|
181
|
+
<LookupEditor
|
|
182
|
+
placeholder="请选择供应商"
|
|
183
|
+
bind:value={selectedSupplier}
|
|
184
|
+
bind:text={supplierText}
|
|
185
|
+
onAction={handleSupplierLookup}
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="form-field">
|
|
190
|
+
<label>供应商产品:</label>
|
|
191
|
+
<LookupEditor
|
|
192
|
+
placeholder="请选择产品"
|
|
193
|
+
disabled={!selectedSupplier}
|
|
194
|
+
bind:value={selectedSupplierProduct}
|
|
195
|
+
bind:text={supplierProductText}
|
|
196
|
+
onAction={handleSupplierProductLookup}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 带缓存的查找
|
|
203
|
+
```svelte
|
|
204
|
+
<script lang="ts">
|
|
205
|
+
interface CacheEntry<T> {
|
|
206
|
+
data: T;
|
|
207
|
+
timestamp: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
class LookupCache {
|
|
211
|
+
private cache = new Map<string, CacheEntry<any>>();
|
|
212
|
+
private ttl = 5 * 60 * 1000; // 5分钟缓存
|
|
213
|
+
|
|
214
|
+
get<T>(key: string): T | null {
|
|
215
|
+
const entry = this.cache.get(key);
|
|
216
|
+
if (!entry) return null;
|
|
217
|
+
|
|
218
|
+
if (Date.now() - entry.timestamp > this.ttl) {
|
|
219
|
+
this.cache.delete(key);
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return entry.data;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
set<T>(key: string, data: T): void {
|
|
227
|
+
this.cache.set(key, {
|
|
228
|
+
data,
|
|
229
|
+
timestamp: Date.now()
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
clear(): void {
|
|
234
|
+
this.cache.clear();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const cache = new LookupCache();
|
|
239
|
+
|
|
240
|
+
let selectedUser: string | null = null;
|
|
241
|
+
let userText: string = '';
|
|
242
|
+
let showUserModal: boolean = false;
|
|
243
|
+
|
|
244
|
+
const handleUserLookup = (): void => {
|
|
245
|
+
// 检查缓存
|
|
246
|
+
const cachedUsers = cache.get('users');
|
|
247
|
+
if (cachedUsers) {
|
|
248
|
+
console.log('使用缓存的用户数据');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
showUserModal = true;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
interface User {
|
|
255
|
+
id: string;
|
|
256
|
+
name: string;
|
|
257
|
+
email: string;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const handleUserSelect = (user: User): void => {
|
|
261
|
+
selectedUser = user.id;
|
|
262
|
+
userText = `${user.name} (${user.email})`;
|
|
263
|
+
|
|
264
|
+
// 缓存用户数据
|
|
265
|
+
cache.set(`user:${user.id}`, user);
|
|
266
|
+
|
|
267
|
+
showUserModal = false;
|
|
268
|
+
};
|
|
269
|
+
</script>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 外部API集成
|
|
273
|
+
```svelte
|
|
274
|
+
<script lang="ts">
|
|
275
|
+
interface ApiResponse<T> {
|
|
276
|
+
data: T[];
|
|
277
|
+
total: number;
|
|
278
|
+
page: number;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
interface ApiUser {
|
|
282
|
+
id: number;
|
|
283
|
+
name: string;
|
|
284
|
+
email: string;
|
|
285
|
+
department: string;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let selectedApiUser: number | null = null;
|
|
289
|
+
let apiUserText: string = '';
|
|
290
|
+
let showApiUserModal: boolean = false;
|
|
291
|
+
let isLoading: boolean = false;
|
|
292
|
+
|
|
293
|
+
const searchUsers = async (query: string, page: number = 1): Promise<ApiResponse<ApiUser>> => {
|
|
294
|
+
const response: Response = await fetch(`/api/users/search?q=${encodeURIComponent(query)}&page=${page}`);
|
|
295
|
+
if (!response.ok) {
|
|
296
|
+
throw new Error('搜索用户失败');
|
|
297
|
+
}
|
|
298
|
+
return await response.json();
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const handleApiUserLookup = (): void => {
|
|
302
|
+
showApiUserModal = true;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const handleApiUserSelect = async (userId: number): Promise<void> => {
|
|
306
|
+
isLoading = true;
|
|
307
|
+
try {
|
|
308
|
+
const response: Response = await fetch(`/api/users/${userId}`);
|
|
309
|
+
const user: ApiUser = await response.json();
|
|
310
|
+
|
|
311
|
+
selectedApiUser = user.id;
|
|
312
|
+
apiUserText = `${user.name} - ${user.department}`;
|
|
313
|
+
showApiUserModal = false;
|
|
314
|
+
} catch (error: unknown) {
|
|
315
|
+
console.error('获取用户详情失败:', error);
|
|
316
|
+
} finally {
|
|
317
|
+
isLoading = false;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
</script>
|
|
321
|
+
|
|
322
|
+
<LookupEditor
|
|
323
|
+
placeholder="请选择API用户"
|
|
324
|
+
disabled={isLoading}
|
|
325
|
+
bind:value={selectedApiUser}
|
|
326
|
+
bind:text={apiUserText}
|
|
327
|
+
onAction={handleApiUserLookup}
|
|
328
|
+
/>
|
|
329
|
+
|
|
330
|
+
{#if isLoading}
|
|
331
|
+
<div class="loading-indicator">加载中...</div>
|
|
332
|
+
{/if}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## 最佳实践
|
|
336
|
+
|
|
337
|
+
### 1. 提供清晰的显示文本
|
|
338
|
+
```typescript
|
|
339
|
+
interface DisplayItem {
|
|
340
|
+
name: string;
|
|
341
|
+
code: string;
|
|
342
|
+
category: string;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const formatDisplayText = (item: DisplayItem): string => {
|
|
346
|
+
return `${item.name} (${item.code}) - ${item.category}`;
|
|
347
|
+
};
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### 2. 处理必填验证
|
|
351
|
+
```svelte
|
|
352
|
+
<script lang="ts">
|
|
353
|
+
let value: string | null = null;
|
|
354
|
+
let error: string = '';
|
|
355
|
+
|
|
356
|
+
const validate = (): boolean => {
|
|
357
|
+
if (!value) {
|
|
358
|
+
error = '此字段为必填项';
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
error = '';
|
|
362
|
+
return true;
|
|
363
|
+
};
|
|
364
|
+
</script>
|
|
365
|
+
|
|
366
|
+
<LookupEditor
|
|
367
|
+
mandatory={true}
|
|
368
|
+
bind:value={value}
|
|
369
|
+
onAction={handleLookup}
|
|
370
|
+
/>
|
|
371
|
+
{#if error}
|
|
372
|
+
<div class="error-message">{error}</div>
|
|
373
|
+
{/if}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### 3. 响应式模态框
|
|
377
|
+
```svelte
|
|
378
|
+
<script lang="ts">
|
|
379
|
+
import { onMount } from 'svelte';
|
|
380
|
+
|
|
381
|
+
let isMobile: boolean = false;
|
|
382
|
+
let showModal: boolean = false;
|
|
383
|
+
|
|
384
|
+
const checkMobile = (): void => {
|
|
385
|
+
isMobile = window.innerWidth < 768;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const handleSelect = (item: any): void => {
|
|
389
|
+
// Handle selection logic
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
onMount((): (() => void) => {
|
|
393
|
+
checkMobile();
|
|
394
|
+
window.addEventListener('resize', checkMobile);
|
|
395
|
+
return (): void => window.removeEventListener('resize', checkMobile);
|
|
396
|
+
});
|
|
397
|
+
</script>
|
|
398
|
+
|
|
399
|
+
{#if showModal}
|
|
400
|
+
<LookupModal
|
|
401
|
+
fullscreen={isMobile}
|
|
402
|
+
onSelect={handleSelect}
|
|
403
|
+
onClose={() => showModal = false}
|
|
404
|
+
/>
|
|
405
|
+
{/if}
|
|
406
|
+
```
|