plain-design 1.0.0-beta.78 → 1.0.0-beta.79
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/plain-design.commonjs.min.js +2 -2
- package/dist/plain-design.min.css +1 -0
- package/dist/plain-design.min.js +2 -2
- package/dist/report.html +2 -2
- package/package.json +1 -1
- package/src/packages/components/$search/SearchFooter.tsx +32 -0
- package/src/packages/components/$search/SearchList.tsx +206 -0
- package/src/packages/components/$search/SearchServicePanel.tsx +233 -0
- package/src/packages/components/$search/createSearchService.tsx +43 -0
- package/src/packages/components/$search/index.tsx +6 -0
- package/src/packages/components/$search/search-service.scss +220 -0
- package/src/packages/components/$search/search.utils.tsx +112 -0
- package/src/packages/components/Input/index.tsx +9 -1
- package/src/packages/components/Input/input.utils.ts +2 -1
- package/src/packages/components/Input/uses/useInputSuffixIcon.tsx +1 -1
- package/src/packages/components/Scroll/index.tsx +34 -0
- package/src/packages/components/Table/standard/PlcTree/PlcTree.renderNode.tsx +3 -0
- package/src/packages/entry.tsx +8 -0
- package/src/packages/i18n/lang/en-us.ts +9 -1
- package/src/packages/i18n/lang/zh-cn.ts +11 -3
- package/src/packages/utils/createListUtils.ts +38 -0
package/package.json
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
import {designPage} from "plain-design-composition";
|
2
|
+
import {i18n} from "../i18n";
|
3
|
+
|
4
|
+
export const SearchFooter = designPage(() => {
|
5
|
+
return () => (
|
6
|
+
<>
|
7
|
+
<svg width="15" height="15" aria-label="Enter key" role="img">
|
8
|
+
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.2">
|
9
|
+
<path d="M12 3.53088v3c0 1-1 2-2 2H4M7 11.53088l-3-3 3-3"></path>
|
10
|
+
</g>
|
11
|
+
</svg>
|
12
|
+
<span>{i18n.$it('search.select').d('选择')}</span>
|
13
|
+
<svg width="15" height="15" aria-label="Arrow down" role="img">
|
14
|
+
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.2">
|
15
|
+
<path d="M7.5 3.5v8M10.5 8.5l-3 3-3-3"></path>
|
16
|
+
</g>
|
17
|
+
</svg>
|
18
|
+
<svg width="15" height="15" aria-label="Arrow up" role="img">
|
19
|
+
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.2">
|
20
|
+
<path d="M7.5 11.5v-8M10.5 6.5l-3-3-3 3"></path>
|
21
|
+
</g>
|
22
|
+
</svg>
|
23
|
+
<span>{i18n.$it('search.switch').d('切换')}</span>
|
24
|
+
<svg width="15" height="15" aria-label="Escape key" role="img">
|
25
|
+
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.2">
|
26
|
+
<path d="M13.6167 8.936c-.1065.3583-.6883.962-1.4875.962-.7993 0-1.653-.9165-1.653-2.1258v-.5678c0-1.2548.7896-2.1016 1.653-2.1016.8634 0 1.3601.4778 1.4875 1.0724M9 6c-.1352-.4735-.7506-.9219-1.46-.8972-.7092.0246-1.344.57-1.344 1.2166s.4198.8812 1.3445.9805C8.465 7.3992 8.968 7.9337 9 8.5c.032.5663-.454 1.398-1.4595 1.398C6.6593 9.898 6 9 5.963 8.4851m-1.4748.5368c-.2635.5941-.8099.876-1.5443.876s-1.7073-.6248-1.7073-2.204v-.4603c0-1.0416.721-2.131 1.7073-2.131.9864 0 1.6425 1.031 1.5443 2.2492h-2.956"></path>
|
27
|
+
</g>
|
28
|
+
</svg>
|
29
|
+
<span>{i18n.$it('base.close').d('关闭')}</span>
|
30
|
+
</>
|
31
|
+
);
|
32
|
+
});
|
@@ -0,0 +1,206 @@
|
|
1
|
+
import {computed, designComponent, iMouseEvent, PropType, reactive, useRefs} from "plain-design-composition";
|
2
|
+
import {iSearchDataMeta, iSearchServiceConfig, SearchOptionButton, SearchTreeIcon, SearchType2Icon} from "./search.utils";
|
3
|
+
import {createListUtils} from "../../utils/createListUtils";
|
4
|
+
import {delay} from "plain-utils/utils/delay";
|
5
|
+
import VirtualList from "../VirtualList";
|
6
|
+
|
7
|
+
export const SearchList = designComponent({
|
8
|
+
props: {
|
9
|
+
data: { type: Array as PropType<iSearchDataMeta[]>, required: true },
|
10
|
+
config: { type: Object as PropType<iSearchServiceConfig>, required: true }
|
11
|
+
},
|
12
|
+
slots: ['default'],
|
13
|
+
emits: {
|
14
|
+
/*选中选项事件*/
|
15
|
+
onSelect: (val: iSearchDataMeta) => true,
|
16
|
+
/*删除选项事件,选项可能是历史选项,也可能是收藏选项*/
|
17
|
+
onRemoveItem: (val: iSearchDataMeta) => true,
|
18
|
+
/*添加选项为收藏选项*/
|
19
|
+
onAddFavorite: (val: iSearchDataMeta) => true,
|
20
|
+
},
|
21
|
+
setup({ props, slots, event: { emit } }) {
|
22
|
+
|
23
|
+
const { refs, onRef } = useRefs({ virtual: VirtualList });
|
24
|
+
|
25
|
+
const state = reactive({
|
26
|
+
/*当前选中的选项位置索引*/
|
27
|
+
selectIndex: null as null | number
|
28
|
+
});
|
29
|
+
|
30
|
+
/*有效可选中的选项数组*/
|
31
|
+
const availableList = computed(() => props.data.filter(i => i.type !== 'group'));
|
32
|
+
/*选中选项在有效选项数组中的位置索引*/
|
33
|
+
const availableIndex = computed(() => state.selectIndex == null ? null : availableList.value.indexOf(props.data[state.selectIndex]));
|
34
|
+
|
35
|
+
const listUtils = createListUtils({ getList: () => availableList.value, current: () => availableIndex.value, });
|
36
|
+
|
37
|
+
const methods = {
|
38
|
+
/**
|
39
|
+
* 选中上一个节点
|
40
|
+
* @author 韦胜健
|
41
|
+
* @date 2024.6.23 21:27
|
42
|
+
*/
|
43
|
+
selectPrev: async () => {
|
44
|
+
const newAvailableIndex = listUtils.prevIndex();
|
45
|
+
if (newAvailableIndex == -1) {return;}
|
46
|
+
const newSelectIndex = props.data.indexOf(availableList.value[newAvailableIndex]);
|
47
|
+
if (!!refs.virtual && availableIndex.value == 0 && newAvailableIndex == availableList.value.length - 1) {
|
48
|
+
/*滚动到底部再选中选项*/
|
49
|
+
refs.virtual.refs.scroll?.methods.scrollEnd();
|
50
|
+
await delay(78);
|
51
|
+
} else {
|
52
|
+
refs.virtual?.refs.scroll?.methods.showElement(`[data-index="_${newSelectIndex}"]`);
|
53
|
+
}
|
54
|
+
state.selectIndex = newSelectIndex;
|
55
|
+
},
|
56
|
+
/**
|
57
|
+
* 选中下一个节点
|
58
|
+
* @author 韦胜健
|
59
|
+
* @date 2024.6.23 21:28
|
60
|
+
*/
|
61
|
+
selectNext: async () => {
|
62
|
+
const newAvailableIndex = listUtils.nextIndex();
|
63
|
+
if (newAvailableIndex == -1) {return;}
|
64
|
+
const newSelectIndex = props.data.indexOf(availableList.value[newAvailableIndex]);
|
65
|
+
if (!!refs.virtual && availableIndex.value == availableList.value.length - 1 && newAvailableIndex == 0) {
|
66
|
+
/*滚动到顶部再选中选项*/
|
67
|
+
refs.virtual.refs.scroll?.methods.scrollTop(0);
|
68
|
+
await delay(78);
|
69
|
+
} else {
|
70
|
+
refs.virtual?.refs.scroll?.methods.showElement(`[data-index="_${newSelectIndex}"]`);
|
71
|
+
}
|
72
|
+
state.selectIndex = newSelectIndex;
|
73
|
+
},
|
74
|
+
/**
|
75
|
+
* 获取当前选中的节点
|
76
|
+
* @author 韦胜健
|
77
|
+
* @date 2024.6.23 21:28
|
78
|
+
*/
|
79
|
+
getSelected: (): iSearchDataMeta | null => {
|
80
|
+
return state.selectIndex == null ? null : props.data[state.selectIndex];
|
81
|
+
},
|
82
|
+
/**
|
83
|
+
* 选中节点
|
84
|
+
* @author 韦胜健
|
85
|
+
* @date 2024.6.23 21:28
|
86
|
+
*/
|
87
|
+
selectItem: (dataMeta: iSearchDataMeta) => {
|
88
|
+
emit.onSelect(dataMeta);
|
89
|
+
},
|
90
|
+
/**
|
91
|
+
* 删除收藏或者历史
|
92
|
+
* @author 韦胜健
|
93
|
+
* @date 2024.6.23 21:58
|
94
|
+
*/
|
95
|
+
removeItem: (e: iMouseEvent, dataMeta: iSearchDataMeta) => {
|
96
|
+
e.stopPropagation();
|
97
|
+
emit.onRemoveItem(dataMeta);
|
98
|
+
},
|
99
|
+
/**
|
100
|
+
* 添加收藏
|
101
|
+
* @author 韦胜健
|
102
|
+
* @date 2024.6.23 21:58
|
103
|
+
*/
|
104
|
+
addFavorite: (e: iMouseEvent, dataMeta: iSearchDataMeta) => {
|
105
|
+
e.stopPropagation();
|
106
|
+
emit.onAddFavorite(dataMeta);
|
107
|
+
},
|
108
|
+
/**
|
109
|
+
* 重置选中节点
|
110
|
+
* @author 韦胜健
|
111
|
+
* @date 2024.6.23 23:42
|
112
|
+
*/
|
113
|
+
resetSelectIndex: async () => {
|
114
|
+
await delay();
|
115
|
+
state.selectIndex = !availableList.value.length ? null : props.data.indexOf(availableList.value[0]);
|
116
|
+
},
|
117
|
+
};
|
118
|
+
|
119
|
+
const renderItem = (
|
120
|
+
{ item, vid, index }: {
|
121
|
+
item: iSearchDataMeta, vid: string, index: number
|
122
|
+
}
|
123
|
+
) => {
|
124
|
+
|
125
|
+
/**
|
126
|
+
* 是否为最后一个subHeader,如果返回值为null,表明不是sub_header,否则true为最后一个sub_header,false为普通sub_header
|
127
|
+
* @author 韦胜健
|
128
|
+
* @date 2024.6.23 23:49
|
129
|
+
*/
|
130
|
+
const isLastSubHeader = (() => {
|
131
|
+
if (item.type !== 'sub_header') {return null;}
|
132
|
+
const nextItem = index + 1 > props.data.length - 1 ? null : props.data[index + 1];
|
133
|
+
return !(!!nextItem && nextItem.type === 'sub_header');
|
134
|
+
})();
|
135
|
+
|
136
|
+
return (
|
137
|
+
<div
|
138
|
+
className={`search-service-option-item`}
|
139
|
+
data-active={String(state.selectIndex === index)}
|
140
|
+
key={vid}
|
141
|
+
data-vid={vid}
|
142
|
+
data-index={`_${index}`}
|
143
|
+
onMouseEnter={() => item.type != 'group' && (state.selectIndex = index)}
|
144
|
+
>
|
145
|
+
{(props.config.render || ((item: iSearchDataMeta) => {
|
146
|
+
return (
|
147
|
+
<div className="search-service-option-item-default" data-service-item-type={item.type}>
|
148
|
+
{item.type == 'group' ? (
|
149
|
+
<div className="search-service-option-item-default-title">{item.title}</div>
|
150
|
+
) : (
|
151
|
+
<div className="search-service-option-item-default-box" onClick={() => methods.selectItem(item)}>
|
152
|
+
{isLastSubHeader != null && SearchTreeIcon[isLastSubHeader ? 'last' : 'normal']()}
|
153
|
+
{SearchType2Icon[item.type]()}
|
154
|
+
<div className="search-service-option-item-default-label">
|
155
|
+
{item.title && <span>{item.title}</span>}
|
156
|
+
{item.desc && <span>{item.desc}</span>}
|
157
|
+
</div>
|
158
|
+
{item.type === 'favorite' ?
|
159
|
+
SearchOptionButton.remove((e) => methods.removeItem(e, item)) :
|
160
|
+
item.type === 'history' ? <>
|
161
|
+
{SearchOptionButton.favorite((e) => methods.addFavorite(e, item))}
|
162
|
+
{SearchOptionButton.remove((e) => methods.removeItem(e, item))}
|
163
|
+
</> : SearchOptionButton.normal()}
|
164
|
+
</div>
|
165
|
+
)}
|
166
|
+
</div>
|
167
|
+
);
|
168
|
+
}))(item)}
|
169
|
+
</div>
|
170
|
+
);
|
171
|
+
};
|
172
|
+
|
173
|
+
return {
|
174
|
+
refer: {
|
175
|
+
refs,
|
176
|
+
methods,
|
177
|
+
},
|
178
|
+
render: () => (
|
179
|
+
!props.data.length ? (
|
180
|
+
<div className="search-service-empty" key="empty">
|
181
|
+
{slots.default()}
|
182
|
+
</div>
|
183
|
+
) : (
|
184
|
+
<div className="search-service-list" key="list">
|
185
|
+
{availableList.value.length <= 6 ? (
|
186
|
+
/*不超过6个元素,就直接渲染选项数组*/
|
187
|
+
props.data.map((item, index) => (renderItem({ item, vid: `_${(item.title || String(index)) + (item.desc || String(index))}`, index })))
|
188
|
+
) : (
|
189
|
+
/*超过6个就渲染虚拟列表*/
|
190
|
+
<div className="search-service-option-virtual-list">
|
191
|
+
<VirtualList
|
192
|
+
ref={onRef.virtual}
|
193
|
+
size={56}
|
194
|
+
dynamicSize
|
195
|
+
data={props.data}
|
196
|
+
alwaysShowScrollbar
|
197
|
+
v-slots={{ default: ({ item, vid, index }) => renderItem({ item, vid, index }) }}
|
198
|
+
/>
|
199
|
+
</div>
|
200
|
+
)}
|
201
|
+
</div>
|
202
|
+
)
|
203
|
+
)
|
204
|
+
};
|
205
|
+
},
|
206
|
+
});
|
@@ -0,0 +1,233 @@
|
|
1
|
+
import {computed, createStore, designComponent, getComponentCls, onMounted, PropType, reactive, useClassCache, useRefs} from "plain-design-composition";
|
2
|
+
import {iSearchDataMeta, iSearchServiceConfig} from "./search.utils";
|
3
|
+
import {debounce} from "plain-utils/utils/debounce";
|
4
|
+
import {handleKeyboard} from "../KeyboardService";
|
5
|
+
import {Box} from "../Box";
|
6
|
+
import Input from "../Input";
|
7
|
+
import {SearchList} from "./SearchList";
|
8
|
+
import {SearchFooter} from "./SearchFooter";
|
9
|
+
import ApplicationConfigurationProvider from "../ApplicationConfigurationProvider";
|
10
|
+
import {i18n} from "../../i18n";
|
11
|
+
|
12
|
+
export const SearchServicePanel = designComponent({
|
13
|
+
props: {
|
14
|
+
config: { type: Object as PropType<iSearchServiceConfig>, required: true }
|
15
|
+
},
|
16
|
+
emits: {
|
17
|
+
onClose: () => true,
|
18
|
+
},
|
19
|
+
setup({ props, event: { emit } }) {
|
20
|
+
|
21
|
+
const configuration = ApplicationConfigurationProvider.inject();
|
22
|
+
|
23
|
+
/*微调输入框以及footer在黑白主题下的颜色*/
|
24
|
+
const inputBackgroundColor = computed(() => ({ backgroundColor: configuration.value.theme.vars[configuration.value.theme.dark ? "bg-1" : 'bg-2'] }));
|
25
|
+
|
26
|
+
/**
|
27
|
+
* 搜索缓存
|
28
|
+
* @author 韦胜健
|
29
|
+
* @date 2024.6.23 21:21
|
30
|
+
*/
|
31
|
+
const searchCache = createStore({
|
32
|
+
initialState: {
|
33
|
+
history: [] as iSearchDataMeta[],
|
34
|
+
favorite: [] as iSearchDataMeta[],
|
35
|
+
},
|
36
|
+
getCacheConfig: () => ({ envName: getComponentCls(''), cacheName: props.config.cacheName || `@@search_service}` })
|
37
|
+
});
|
38
|
+
|
39
|
+
const { refs, onRef } = useRefs({ list: SearchList });
|
40
|
+
|
41
|
+
const state = reactive({
|
42
|
+
/*当前输入的搜索关键词*/
|
43
|
+
searchText: '',
|
44
|
+
/*展示的选项数据*/
|
45
|
+
data: [] as iSearchDataMeta[],
|
46
|
+
/*当前是否处于加载状态*/
|
47
|
+
loading: false,
|
48
|
+
/*当前是否处于输入状态*/
|
49
|
+
editing: false,
|
50
|
+
});
|
51
|
+
|
52
|
+
const historyData = computed(() => {
|
53
|
+
const data: iSearchDataMeta[] = [];
|
54
|
+
if (!!searchCache.value.history.length) {
|
55
|
+
data.push({ title: i18n.$it('search.searchHistory').d('搜索历史'), type: 'group' });
|
56
|
+
data.push(...searchCache.value.history);
|
57
|
+
}
|
58
|
+
if (!!searchCache.value.favorite.length) {
|
59
|
+
data.push({ title: i18n.$it('search.favorite').d('收藏'), type: 'group' });
|
60
|
+
data.push(...searchCache.value.favorite);
|
61
|
+
}
|
62
|
+
return data;
|
63
|
+
});
|
64
|
+
|
65
|
+
const classes = useClassCache(() => [
|
66
|
+
getComponentCls('search-service-panel')
|
67
|
+
]);
|
68
|
+
|
69
|
+
const handler = {
|
70
|
+
/**
|
71
|
+
* 每次输入搜索关键词的时候都重置数据,防抖执行
|
72
|
+
* @author 韦胜健
|
73
|
+
* @date 2024.6.23 21:23
|
74
|
+
*/
|
75
|
+
resetData: debounce(async () => {
|
76
|
+
if (!state.searchText.trim().length) {
|
77
|
+
state.data = [];
|
78
|
+
state.editing = false;
|
79
|
+
refs.list?.methods.resetSelectIndex();
|
80
|
+
return;
|
81
|
+
}
|
82
|
+
state.loading = true;
|
83
|
+
try {
|
84
|
+
state.data = await props.config.getData(state.searchText);
|
85
|
+
} catch (e) {
|
86
|
+
state.data = [];
|
87
|
+
throw e;
|
88
|
+
} finally {
|
89
|
+
state.loading = false;
|
90
|
+
state.editing = false;
|
91
|
+
refs.list?.methods.resetSelectIndex();
|
92
|
+
}
|
93
|
+
}, 300),
|
94
|
+
/**
|
95
|
+
* 搜索关键词变化的话立即标识处于输入状态
|
96
|
+
* @author 韦胜健
|
97
|
+
* @date 2024.6.23 21:24
|
98
|
+
*/
|
99
|
+
onInputChange: () => {
|
100
|
+
state.editing = true;
|
101
|
+
handler.resetData();
|
102
|
+
},
|
103
|
+
/**
|
104
|
+
* 处理上下以及enter,esc快捷键
|
105
|
+
* @author 韦胜健
|
106
|
+
* @date 2024.6.23 21:24
|
107
|
+
*/
|
108
|
+
onKeydown: handleKeyboard({
|
109
|
+
up: async (e) => {
|
110
|
+
e.preventDefault();
|
111
|
+
e.stopPropagation();
|
112
|
+
refs.list?.methods.selectPrev();
|
113
|
+
},
|
114
|
+
down: async (e) => {
|
115
|
+
e.preventDefault();
|
116
|
+
e.stopPropagation();
|
117
|
+
refs.list?.methods.selectNext();
|
118
|
+
},
|
119
|
+
enter: (e) => {
|
120
|
+
e.preventDefault();
|
121
|
+
e.stopPropagation();
|
122
|
+
const target = refs.list?.methods.getSelected();
|
123
|
+
if (!target) {return;}
|
124
|
+
handler.onSelectItem(target);
|
125
|
+
},
|
126
|
+
esc: (e) => {
|
127
|
+
e.preventDefault();
|
128
|
+
e.stopPropagation();
|
129
|
+
emit.onClose();
|
130
|
+
},
|
131
|
+
}),
|
132
|
+
/**
|
133
|
+
* 处理选中某个选项的动作
|
134
|
+
* @author 韦胜健
|
135
|
+
* @date 2024.6.23 21:24
|
136
|
+
*/
|
137
|
+
onSelectItem: (dataMeta: iSearchDataMeta) => {
|
138
|
+
if (!historyData.value.find(i => i.title === dataMeta.title && i.desc === dataMeta.desc)) {
|
139
|
+
dataMeta = { ...dataMeta, type: 'history' };
|
140
|
+
searchCache.value.history.unshift(dataMeta);
|
141
|
+
if (searchCache.value.history.length > 5) {searchCache.value.history.pop();}
|
142
|
+
searchCache.value = { ...searchCache.value };
|
143
|
+
}
|
144
|
+
props.config.onSelect(dataMeta);
|
145
|
+
emit.onClose();
|
146
|
+
},
|
147
|
+
onRemoveItem: (dataMeta: iSearchDataMeta) => {
|
148
|
+
const historyIndex = searchCache.value.history.findIndex(i => i.title === dataMeta.title && i.desc === dataMeta.desc && i.type === 'history');
|
149
|
+
if (historyIndex > -1) {
|
150
|
+
searchCache.value.history.splice(historyIndex, 1);
|
151
|
+
searchCache.value = { ...searchCache.value };
|
152
|
+
return;
|
153
|
+
}
|
154
|
+
const favoriteIndex = searchCache.value.favorite.findIndex(i => i.title === dataMeta.title && i.desc === dataMeta.desc && i.type === 'favorite');
|
155
|
+
if (favoriteIndex > -1) {
|
156
|
+
searchCache.value.favorite.splice(favoriteIndex, 1);
|
157
|
+
searchCache.value = { ...searchCache.value };
|
158
|
+
return;
|
159
|
+
}
|
160
|
+
},
|
161
|
+
onAddFavorite: (dataMeta: iSearchDataMeta) => {
|
162
|
+
handler.onRemoveItem(dataMeta);
|
163
|
+
dataMeta = { ...dataMeta, type: 'favorite' };
|
164
|
+
searchCache.value.favorite.unshift(dataMeta);
|
165
|
+
searchCache.value = { ...searchCache.value };
|
166
|
+
},
|
167
|
+
};
|
168
|
+
|
169
|
+
const renderList = (data: iSearchDataMeta[], empty: () => any) => {
|
170
|
+
return (
|
171
|
+
<SearchList
|
172
|
+
ref={onRef.list}
|
173
|
+
data={data}
|
174
|
+
config={props.config}
|
175
|
+
onSelect={handler.onSelectItem}
|
176
|
+
onRemoveItem={handler.onRemoveItem}
|
177
|
+
onAddFavorite={handler.onAddFavorite}
|
178
|
+
>
|
179
|
+
{empty()}
|
180
|
+
</SearchList>
|
181
|
+
);
|
182
|
+
};
|
183
|
+
|
184
|
+
onMounted(() => {
|
185
|
+
refs.list?.methods.resetSelectIndex();
|
186
|
+
});
|
187
|
+
|
188
|
+
return () => (
|
189
|
+
<Box className={classes.value}>
|
190
|
+
<div className="search-service-input-box">
|
191
|
+
<Input
|
192
|
+
style={inputBackgroundColor.value}
|
193
|
+
autoFocus
|
194
|
+
prefixIcon="pi-search"
|
195
|
+
v-model={state.searchText}
|
196
|
+
placeholder={props.config.placeholder}
|
197
|
+
inputMode="stroke"
|
198
|
+
onChange={handler.onInputChange}
|
199
|
+
loading={state.loading}
|
200
|
+
loadingType="kappa"
|
201
|
+
onKeyDown={handler.onKeydown}
|
202
|
+
/>
|
203
|
+
</div>
|
204
|
+
<div className="search-service-body">
|
205
|
+
{!state.searchText.trim().length || (state.editing && !state.data.length) ? (
|
206
|
+
/*如果没有搜索关键词,或者处于输入关键词状态并且没有数据的话,显示搜索历史*/
|
207
|
+
renderList(historyData.value, () => (
|
208
|
+
<div className="search-service-panel-history" key="history">
|
209
|
+
{i18n.$it('search.noHistory').d('没有搜索历史')}
|
210
|
+
</div>))
|
211
|
+
) : (
|
212
|
+
/*否则显示具体的数据*/
|
213
|
+
renderList(state.data, () => {
|
214
|
+
return (
|
215
|
+
<div key="no_match">
|
216
|
+
<svg width="40" height="40" viewBox="0 0 20 20" fill="none" fillRule="evenodd" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
217
|
+
<path d="M15.5 4.8c2 3 1.7 7-1 9.7h0l4.3 4.3-4.3-4.3a7.8 7.8 0 01-9.8 1m-2.2-2.2A7.8 7.8 0 0113.2 2.4M2 18L18 2"></path>
|
218
|
+
</svg>
|
219
|
+
<div>
|
220
|
+
{i18n.$it('search.noMatch', { val: state.searchText }).d(`无法找到搜索结果 "${state.searchText}"`)}
|
221
|
+
</div>
|
222
|
+
</div>
|
223
|
+
);
|
224
|
+
})
|
225
|
+
)}
|
226
|
+
</div>
|
227
|
+
<div className="search-service-foot" style={inputBackgroundColor.value}>
|
228
|
+
{props.config.footer ? props.config.footer() : (<SearchFooter/>)}
|
229
|
+
</div>
|
230
|
+
</Box>
|
231
|
+
);
|
232
|
+
},
|
233
|
+
});
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import {iSearchServiceConfig, iSearchServiceCustomConfig, iSearchServiceDefaultConfig} from "./search.utils";
|
2
|
+
import $dialog from "../$dialog";
|
3
|
+
import {getComponentCls} from "plain-design-composition";
|
4
|
+
import './search-service.scss';
|
5
|
+
import {i18n} from "../i18n";
|
6
|
+
|
7
|
+
import {SearchServicePanel} from "./SearchServicePanel";
|
8
|
+
|
9
|
+
export function createSearchService(defaultConfig?: Partial<iSearchServiceDefaultConfig>) {
|
10
|
+
|
11
|
+
const _defaultConfig: iSearchServiceDefaultConfig = {
|
12
|
+
width: defaultConfig?.width || 560,
|
13
|
+
footer: defaultConfig?.footer,
|
14
|
+
render: defaultConfig?.render,
|
15
|
+
placeholder: defaultConfig?.placeholder || i18n.$it('table.pleaseEnterSearchKey').d('请输入搜索关键词')
|
16
|
+
};
|
17
|
+
|
18
|
+
return (customConfig: iSearchServiceCustomConfig & Partial<iSearchServiceDefaultConfig>) => {
|
19
|
+
|
20
|
+
const config: iSearchServiceConfig = {
|
21
|
+
..._defaultConfig,
|
22
|
+
...customConfig,
|
23
|
+
};
|
24
|
+
|
25
|
+
const closeDialog = $dialog({
|
26
|
+
width: config.width,
|
27
|
+
noHead: true,
|
28
|
+
noFoot: true,
|
29
|
+
externalClass: getComponentCls('search-service'),
|
30
|
+
noContentPadding: true,
|
31
|
+
render: () => {
|
32
|
+
return (
|
33
|
+
<SearchServicePanel config={config} onClose={closeDialog}/>
|
34
|
+
);
|
35
|
+
},
|
36
|
+
});
|
37
|
+
|
38
|
+
return {
|
39
|
+
close: closeDialog,
|
40
|
+
};
|
41
|
+
};
|
42
|
+
}
|
43
|
+
|