Showing
12 changed files
with
1437 additions
and
53 deletions
@@ -54,6 +54,7 @@ | @@ -54,6 +54,7 @@ | ||
54 | "devDependencies": { | 54 | "devDependencies": { |
55 | "@commitlint/cli": "^17.1.2", | 55 | "@commitlint/cli": "^17.1.2", |
56 | "@commitlint/config-conventional": "^17.1.0", | 56 | "@commitlint/config-conventional": "^17.1.0", |
57 | + "@qx/utils": "0.0.58", | ||
57 | "@types/lodash-es": "^4.17.8", | 58 | "@types/lodash-es": "^4.17.8", |
58 | "@types/react": "^18.0.0", | 59 | "@types/react": "^18.0.0", |
59 | "@types/react-dom": "^18.0.0", | 60 | "@types/react-dom": "^18.0.0", |
@@ -69,6 +70,7 @@ | @@ -69,6 +70,7 @@ | ||
69 | "prettier-plugin-organize-imports": "^3.0.0", | 70 | "prettier-plugin-organize-imports": "^3.0.0", |
70 | "prettier-plugin-packagejson": "^2.2.18", | 71 | "prettier-plugin-packagejson": "^2.2.18", |
71 | "react": "^18.0.0", | 72 | "react": "^18.0.0", |
73 | + "react-cookies": ">=0.1.1", | ||
72 | "react-dom": "^18.0.0", | 74 | "react-dom": "^18.0.0", |
73 | "stylelint": "^14.9.1" | 75 | "stylelint": "^14.9.1" |
74 | }, | 76 | }, |
src/qx-app-selector/index.md
0 → 100644
1 | +### 选择应用 | ||
2 | + | ||
3 | +```tsx | ||
4 | +import { createRequest } from '@qx/utils'; | ||
5 | +import React, { useState } from 'react'; | ||
6 | +import { QxAppSelector } from './index'; | ||
7 | + | ||
8 | +export default () => { | ||
9 | + const [visible, setVisible] = useState(false); | ||
10 | + const props = { | ||
11 | + flag: 'join', | ||
12 | + item: { | ||
13 | + flag: 'SINGLE', | ||
14 | + currentId: '', | ||
15 | + appId: 'n7S2Gz9GJ4knP9w2AOb', | ||
16 | + }, | ||
17 | + onchange: () => {}, | ||
18 | + modalProps: { | ||
19 | + width: 550, | ||
20 | + destroyOnClose: true, | ||
21 | + visible: visible, | ||
22 | + onOk: () => setVisible(false), | ||
23 | + onCancel: () => setVisible(false), | ||
24 | + }, | ||
25 | + }; | ||
26 | + | ||
27 | + return ( | ||
28 | + <> | ||
29 | + <a onClick={() => setVisible(true)}>点我一下</a> | ||
30 | + <QxAppSelector {...props} request={createRequest()} /> | ||
31 | + </> | ||
32 | + ); | ||
33 | +}; | ||
34 | +``` | ||
35 | + | ||
36 | +<API></API> |
src/qx-app-selector/index.tsx
0 → 100644
1 | +import { DownOutlined, SearchOutlined } from '@ant-design/icons'; | ||
2 | +import { | ||
3 | + Input, | ||
4 | + Menu, | ||
5 | + // message, | ||
6 | + Modal, | ||
7 | + Popover, | ||
8 | + Radio, | ||
9 | + Select, | ||
10 | + // TreeSelect, | ||
11 | + Typography, | ||
12 | +} from 'antd'; | ||
13 | +import _ from 'lodash'; | ||
14 | +import React, { useEffect, useMemo, useState } from 'react'; | ||
15 | +import { | ||
16 | + getAggreList, | ||
17 | + getAppList, | ||
18 | + getDatasetListByAppid, | ||
19 | + getFunList, | ||
20 | +} from './service'; | ||
21 | +// import { FRWidgetProps } from '@/packages/qx-form-generator/src'; | ||
22 | +import type { ModalProps } from 'antd'; | ||
23 | + | ||
24 | +import './styles.less'; | ||
25 | + | ||
26 | +const { Title, Text } = Typography; | ||
27 | +/** | ||
28 | + * 应用选择器 | ||
29 | + * @constructor | ||
30 | + */ | ||
31 | +type QxAppSelectProps = { | ||
32 | + item?: Record<string, unknown>; | ||
33 | + modalProps: ModalProps; | ||
34 | + title?: string; | ||
35 | + code?: string; | ||
36 | + name?: string; | ||
37 | + onChange?: any; | ||
38 | + getConfig?: (appCode: string, funCode: string) => any; | ||
39 | + flow_add?: boolean; | ||
40 | + from?: string; | ||
41 | + flag?: string; | ||
42 | + clearData?: boolean; | ||
43 | + haveChildren?: boolean; | ||
44 | + clearDataSourceNode?: boolean; | ||
45 | + /** | ||
46 | + * 控制表单类型是否显示. | ||
47 | + * | ||
48 | + * 默认值 true | ||
49 | + */ | ||
50 | + showTableTypeTab?: boolean; | ||
51 | + /** | ||
52 | + * 默认表单类型 | ||
53 | + * | ||
54 | + * 默认值 single | ||
55 | + */ | ||
56 | + defaultTableType?: 'single' | 'join'; | ||
57 | + chartCustom?: boolean; | ||
58 | + nodeFrom?: 'flow_query_single'; | ||
59 | + request: any; | ||
60 | +}; | ||
61 | + | ||
62 | +export const QxAppSelector: React.FC<QxAppSelectProps> = ({ | ||
63 | + showTableTypeTab = true, | ||
64 | + defaultTableType = 'single', | ||
65 | + chartCustom = false, | ||
66 | + request, | ||
67 | + ...props | ||
68 | +}) => { | ||
69 | + const [appList, setAppList] = useState<any[]>([]); //应用列表 | ||
70 | + const [formList, setFormList] = useState<any[]>([]); //表单列表 | ||
71 | + const [appItem, setAppItem] = useState<any>({}); //选择的应用 | ||
72 | + const [formItem, setFormItem] = useState<any>({}); //选择的表单 | ||
73 | + const [tableType, setTableType] = useState<string>(defaultTableType); //单个表单or聚合表 | ||
74 | + const [aggreList, setAggreList] = useState<any[]>([]); //聚合表数据源 | ||
75 | + const [aggreValue, setAggreValue] = useState<string>(); //聚合表选择的值 | ||
76 | + const isDeveloperMode = | ||
77 | + window.sessionStorage.getItem('DEVELOPER_MODE') === '1'; | ||
78 | + | ||
79 | + useEffect(() => { | ||
80 | + const _item = props.item || {}; | ||
81 | + let targetItem: any = {}; | ||
82 | + getAppList(request, { from: props.from }).then((res: any) => { | ||
83 | + // eslint-disable-next-line array-callback-return | ||
84 | + res.map((item: any) => { | ||
85 | + if (item.code === _item['currentId']) { | ||
86 | + item.name = item.name + '(本应用)'; | ||
87 | + } | ||
88 | + if (item.code === _item['appId']) { | ||
89 | + targetItem = { ...item }; | ||
90 | + } | ||
91 | + }); | ||
92 | + setAppList(res); | ||
93 | + setAppItem(targetItem); | ||
94 | + if (!targetItem || !targetItem.code) { | ||
95 | + return; | ||
96 | + } | ||
97 | + if (props?.haveChildren) { | ||
98 | + getFunList(request, targetItem.code, { hasChild: true }).then((re) => { | ||
99 | + setFormList(re); | ||
100 | + }); | ||
101 | + } else { | ||
102 | + getFunList(request, targetItem.code).then((re) => { | ||
103 | + setFormList(re); | ||
104 | + }); | ||
105 | + } | ||
106 | + if (_item.flag === 'JOIN') { | ||
107 | + setFormItem({}); | ||
108 | + //聚合表 | ||
109 | + setTableType('join'); // 单选按钮切到'聚合表' | ||
110 | + if (!aggreList.length) { | ||
111 | + // 如果聚合表没有加载数据源 | ||
112 | + const datasetApi = chartCustom | ||
113 | + ? getDatasetListByAppid(request, targetItem.code) | ||
114 | + : getAggreList(request); | ||
115 | + datasetApi.then((datasetRes: any) => { | ||
116 | + // eslint-disable-next-line array-callback-return | ||
117 | + datasetRes.map((item: any) => { | ||
118 | + item.label = item.name; | ||
119 | + item.value = item.code; | ||
120 | + }); | ||
121 | + setAggreList(datasetRes); | ||
122 | + // console.log('聚合表数据源',res); | ||
123 | + const index = datasetRes.findIndex( | ||
124 | + (o: any) => o.code === String(_item['code']), | ||
125 | + ); //判断聚合表是否已被删除 | ||
126 | + setAggreValue(index === -1 ? undefined : String(_item['code'])); | ||
127 | + }); | ||
128 | + } else { | ||
129 | + const index = aggreList.findIndex( | ||
130 | + (o: any) => o.code === String(_item['code']), | ||
131 | + ); | ||
132 | + setAggreValue(index === -1 ? undefined : String(_item['code'])); | ||
133 | + } | ||
134 | + } else { | ||
135 | + setTableType('single'); | ||
136 | + setFormItem(_item); | ||
137 | + setAggreValue(undefined); | ||
138 | + } | ||
139 | + }); | ||
140 | + }, [props.item]); | ||
141 | + | ||
142 | + const formChange = (item: any) => { | ||
143 | + setFormItem(item); | ||
144 | + }; | ||
145 | + | ||
146 | + const appChange = (item: any) => { | ||
147 | + setAppItem(item); | ||
148 | + setFormItem({}); | ||
149 | + if (props?.haveChildren) { | ||
150 | + getFunList(request, item.code, { hasChild: true }).then((re: any) => { | ||
151 | + setFormList(re); | ||
152 | + }); | ||
153 | + } else { | ||
154 | + getFunList(request, item.code).then((res: any) => { | ||
155 | + setFormList(res); | ||
156 | + }); | ||
157 | + const datasetApi = chartCustom | ||
158 | + ? getDatasetListByAppid(request, item.code) | ||
159 | + : getAggreList(request); | ||
160 | + datasetApi.then((datasetRes: any) => { | ||
161 | + // eslint-disable-next-line array-callback-return | ||
162 | + datasetRes.map((_item: any) => { | ||
163 | + _item.label = _item.name; | ||
164 | + _item.value = _item.code; | ||
165 | + }); | ||
166 | + setAggreList(datasetRes); | ||
167 | + setAggreValue(undefined); | ||
168 | + }); | ||
169 | + } | ||
170 | + }; | ||
171 | + | ||
172 | + const radioChange = (e: any, data: any) => { | ||
173 | + setTableType(e.target.value); | ||
174 | + if (chartCustom) { | ||
175 | + if (e.target.value === 'join' && !aggreList.length) { | ||
176 | + const datasetApi = chartCustom | ||
177 | + ? getDatasetListByAppid(request, data?.code || '') | ||
178 | + : getAggreList(request); | ||
179 | + datasetApi.then((res: any) => { | ||
180 | + if (res && !!res.length) { | ||
181 | + // eslint-disable-next-line array-callback-return | ||
182 | + res.map((item: any) => { | ||
183 | + item.label = item.name; | ||
184 | + item.value = item.code; | ||
185 | + }); | ||
186 | + setAggreList(res); | ||
187 | + } else { | ||
188 | + setAggreList([]); | ||
189 | + } | ||
190 | + }); | ||
191 | + } | ||
192 | + } else { | ||
193 | + if (e.target.value === 'join' && !aggreList.length) { | ||
194 | + getAggreList(request).then((res: any) => { | ||
195 | + // eslint-disable-next-line array-callback-return | ||
196 | + res.map((item: any) => { | ||
197 | + item.label = item.name; | ||
198 | + item.value = item.code; | ||
199 | + }); | ||
200 | + setAggreList(res); | ||
201 | + }); | ||
202 | + } | ||
203 | + } | ||
204 | + }; | ||
205 | + const aggreValueChange = (e: any) => { | ||
206 | + setAggreValue(e); | ||
207 | + }; | ||
208 | + const handleGetConfig = () => { | ||
209 | + const appCode = appItem.extract; | ||
210 | + const funCode = formItem.extract?.code; | ||
211 | + if (!appCode || !funCode) return; | ||
212 | + //props.getConfig 当前为undefined--布尔判断为false | ||
213 | + if (props.getConfig) props.getConfig(appCode, funCode); | ||
214 | + }; | ||
215 | + | ||
216 | + // 只要选的是用户中心 都过滤掉 用户与部门管理 | ||
217 | + const workFormList = useMemo(() => { | ||
218 | + if (appItem?.extract === 'uc') { | ||
219 | + return formList?.filter( | ||
220 | + (o: any) => !['org', 'user'].includes(o.extract?.code), | ||
221 | + ); | ||
222 | + } | ||
223 | + return formList; | ||
224 | + }, [appItem?.extract, formList, props.nodeFrom]); | ||
225 | + | ||
226 | + /* 表单组件 */ | ||
227 | + const RelItemOption: React.FC<any> = (riProps) => { | ||
228 | + const [visible, setVisible] = useState<boolean>(false); | ||
229 | + const [list, setList] = useState<any>(riProps.list); | ||
230 | + const [item, setItem] = useState<any>(); | ||
231 | + useEffect(() => { | ||
232 | + if (riProps.item) { | ||
233 | + setItem(riProps.item); | ||
234 | + } | ||
235 | + | ||
236 | + if (riProps.list) { | ||
237 | + setList(riProps.list); | ||
238 | + } | ||
239 | + }, [riProps]); | ||
240 | + | ||
241 | + const filter = (word: string) => { | ||
242 | + if (list) { | ||
243 | + const _data = _.cloneDeep(list); | ||
244 | + _data.forEach((it: Item) => { | ||
245 | + it.deleted = !(it.name.indexOf(word) > -1); | ||
246 | + }); | ||
247 | + | ||
248 | + setList(_data); | ||
249 | + } | ||
250 | + }; | ||
251 | + | ||
252 | + const handleChange = (_keyword: string) => { | ||
253 | + filter(_keyword.trim()); | ||
254 | + }; | ||
255 | + | ||
256 | + const handleVisibleChange = (_visible: boolean) => { | ||
257 | + setVisible(_visible); | ||
258 | + }; | ||
259 | + | ||
260 | + const menus = () => { | ||
261 | + return ( | ||
262 | + <div className={'qx-search-menus__wrap'}> | ||
263 | + <Input | ||
264 | + className={'qx-selector-sub-search'} | ||
265 | + placeholder={'输入名称,回车搜索'} | ||
266 | + allowClear | ||
267 | + prefix={<SearchOutlined />} | ||
268 | + onChange={(e) => { | ||
269 | + handleChange(e.target.value); | ||
270 | + }} | ||
271 | + /> | ||
272 | + <div> | ||
273 | + <Menu mode={'inline'} selectedKeys={riProps?.item?.code}> | ||
274 | + {list && list.length > 0 ? ( | ||
275 | + <> | ||
276 | + {list.map((it: any) => { | ||
277 | + return !it.deleted ? ( | ||
278 | + <Menu.Item | ||
279 | + style={{ width: '100%' }} | ||
280 | + onClick={() => { | ||
281 | + riProps.onChange(it); | ||
282 | + setVisible(false); | ||
283 | + }} | ||
284 | + key={it.code} | ||
285 | + > | ||
286 | + {it.name} | ||
287 | + </Menu.Item> | ||
288 | + ) : null; | ||
289 | + })} | ||
290 | + </> | ||
291 | + ) : null} | ||
292 | + </Menu> | ||
293 | + </div> | ||
294 | + </div> | ||
295 | + ); | ||
296 | + }; | ||
297 | + | ||
298 | + return ( | ||
299 | + <> | ||
300 | + <Title level={5} style={{ margin: '20px 0 6px' }}> | ||
301 | + {riProps.title} | ||
302 | + </Title> | ||
303 | + <div style={{ width: '100%', marginBottom: '20px' }}> | ||
304 | + <Popover | ||
305 | + content={menus} | ||
306 | + open={visible} | ||
307 | + trigger={'click'} | ||
308 | + onOpenChange={handleVisibleChange} | ||
309 | + placement="bottomLeft" | ||
310 | + overlayClassName={'qx-fg-select-overlay'} | ||
311 | + getPopupContainer={(triggerNode) => triggerNode} | ||
312 | + > | ||
313 | + <div | ||
314 | + className={`ant-input qx-fr-input--fake select-source`} | ||
315 | + style={{ | ||
316 | + height: '36px', | ||
317 | + }} | ||
318 | + > | ||
319 | + <div> | ||
320 | + {item?.name ? ( | ||
321 | + item.name | ||
322 | + ) : ( | ||
323 | + <Text type="secondary">{riProps.placeholder}</Text> | ||
324 | + )} | ||
325 | + </div> | ||
326 | + <DownOutlined /> | ||
327 | + </div> | ||
328 | + </Popover> | ||
329 | + </div> | ||
330 | + </> | ||
331 | + ); | ||
332 | + }; | ||
333 | + /* 聚合表组件 */ | ||
334 | + const AggreOption: React.FC<any> = (_props: any) => { | ||
335 | + return ( | ||
336 | + <> | ||
337 | + <Title level={5} style={{ margin: '20px 0 6px' }}> | ||
338 | + {_props.title} | ||
339 | + </Title> | ||
340 | + <Select | ||
341 | + allowClear | ||
342 | + style={{ width: '100%', height: '36px' }} | ||
343 | + placeholder={_props.placeholder || '请选择数据源'} | ||
344 | + options={_props.aggreList} | ||
345 | + value={_props.aggreValue} | ||
346 | + onChange={_props.onChange} | ||
347 | + dropdownStyle={{ maxHeight: 400, overflow: 'auto' }} | ||
348 | + getPopupContainer={(triggerNode) => triggerNode} | ||
349 | + /> | ||
350 | + </> | ||
351 | + ); | ||
352 | + }; | ||
353 | + | ||
354 | + /** | ||
355 | + * 表单类型选择 | ||
356 | + */ | ||
357 | + const TableTypeNode: React.FC<any> = (_typeProps: any) => { | ||
358 | + return ( | ||
359 | + <> | ||
360 | + {showTableTypeTab && ( | ||
361 | + <Radio.Group | ||
362 | + onChange={(e: any) => _typeProps.radioChange(e, _typeProps.appData)} | ||
363 | + value={_typeProps.radioType} | ||
364 | + defaultValue={_typeProps.defaultRadioType} | ||
365 | + optionType="button" | ||
366 | + style={{ | ||
367 | + width: '100%', | ||
368 | + textAlign: 'center', | ||
369 | + display: props.flag === 'join' ? '' : 'none', | ||
370 | + height: '36px', | ||
371 | + }} | ||
372 | + > | ||
373 | + <Radio.Button | ||
374 | + value="single" | ||
375 | + style={{ | ||
376 | + width: '50%', | ||
377 | + height: '36px', | ||
378 | + }} | ||
379 | + > | ||
380 | + 表单 | ||
381 | + </Radio.Button> | ||
382 | + <Radio.Button | ||
383 | + value="join" | ||
384 | + style={{ | ||
385 | + width: '50%', | ||
386 | + height: '36px', | ||
387 | + }} | ||
388 | + > | ||
389 | + 聚合表 | ||
390 | + </Radio.Button> | ||
391 | + </Radio.Group> | ||
392 | + )} | ||
393 | + </> | ||
394 | + ); | ||
395 | + }; | ||
396 | + | ||
397 | + return ( | ||
398 | + <> | ||
399 | + <Modal | ||
400 | + bodyStyle={{ padding: 0 }} | ||
401 | + className={'qx-query-designer'} | ||
402 | + keyboard={false} | ||
403 | + maskClosable={false} | ||
404 | + {...props.modalProps} | ||
405 | + onOk={ | ||
406 | + tableType === 'single' | ||
407 | + ? (e) => { | ||
408 | + // eslint-disable-next-line @typescript-eslint/no-unused-expressions | ||
409 | + props?.modalProps.onOk && props.modalProps.onOk(e); | ||
410 | + // 把应用id传出来 | ||
411 | + props.onChange({ | ||
412 | + ...formItem, | ||
413 | + appId: appItem.code, | ||
414 | + flag: 'SINGLE', | ||
415 | + appCode: appItem.extract, | ||
416 | + }); | ||
417 | + handleGetConfig(); | ||
418 | + } | ||
419 | + : (e) => { | ||
420 | + // eslint-disable-next-line @typescript-eslint/no-unused-expressions | ||
421 | + props?.modalProps.onOk && props.modalProps.onOk(e); | ||
422 | + // 把选择的聚合表的id传出来 | ||
423 | + const aggreItem = aggreList.filter( | ||
424 | + (ex: any) => ex.value === aggreValue, | ||
425 | + )[0]; | ||
426 | + props.onChange({ | ||
427 | + ...aggreItem, | ||
428 | + appId: appItem.code, | ||
429 | + flag: 'JOIN', | ||
430 | + appCode: appItem.extract, | ||
431 | + }); | ||
432 | + // handleGetConfig() | ||
433 | + } | ||
434 | + } | ||
435 | + title={props.title || '选择关联表单'} | ||
436 | + > | ||
437 | + {chartCustom ? ( | ||
438 | + <div style={{ width: '90%', margin: '10px auto' }}> | ||
439 | + <div style={{ margin: '20px auto' }}> | ||
440 | + <RelItemOption | ||
441 | + title={'应用'} | ||
442 | + list={appList} | ||
443 | + onChange={appChange} | ||
444 | + item={appItem} | ||
445 | + placeholder={'请选择应用'} | ||
446 | + /> | ||
447 | + <> | ||
448 | + <Title level={5} style={{ margin: '20px 0 6px' }}> | ||
449 | + 数据类型 | ||
450 | + </Title> | ||
451 | + <TableTypeNode | ||
452 | + radioType={tableType} | ||
453 | + defaultRadioType={defaultTableType} | ||
454 | + appData={appItem} | ||
455 | + radioChange={radioChange} | ||
456 | + /> | ||
457 | + </> | ||
458 | + {tableType === 'single' ? ( | ||
459 | + <div style={{ margin: '20px auto' }}> | ||
460 | + <RelItemOption | ||
461 | + title={'表单'} | ||
462 | + list={formList} | ||
463 | + onChange={formChange} | ||
464 | + item={formItem} | ||
465 | + placeholder={'请选择表单'} | ||
466 | + /> | ||
467 | + </div> | ||
468 | + ) : ( | ||
469 | + <div style={{ margin: '20px auto' }}> | ||
470 | + <AggreOption | ||
471 | + aggreList={aggreList} | ||
472 | + aggreValue={aggreValue} | ||
473 | + onChange={aggreValueChange} | ||
474 | + title={'聚合表'} | ||
475 | + placeholder={'请选择聚合表'} | ||
476 | + /> | ||
477 | + </div> | ||
478 | + )} | ||
479 | + </div> | ||
480 | + </div> | ||
481 | + ) : ( | ||
482 | + <div style={{ width: '90%', margin: '10px auto' }}> | ||
483 | + {props.title === '选择数据源' ? null : ( | ||
484 | + <Text type="secondary"> | ||
485 | + 在表单中显示关联的记录。如:订单关联客户 | ||
486 | + </Text> | ||
487 | + )} | ||
488 | + <TableTypeNode | ||
489 | + radioType={tableType} | ||
490 | + defaultRadioType={defaultTableType} | ||
491 | + appData={appItem} | ||
492 | + radioChange={radioChange} | ||
493 | + /> | ||
494 | + {tableType === 'single' ? ( | ||
495 | + <div style={{ margin: '20px auto' }}> | ||
496 | + <RelItemOption | ||
497 | + title={'应用'} | ||
498 | + // 非开发者模式下 过滤掉用户中心选项--钉钉2022.10.11 | ||
499 | + list={ | ||
500 | + isDeveloperMode | ||
501 | + ? appList | ||
502 | + : appList?.filter((o) => o?.extract !== 'uc') | ||
503 | + } | ||
504 | + onChange={appChange} | ||
505 | + item={appItem} | ||
506 | + placeholder={'请选择应用'} | ||
507 | + /> | ||
508 | + <RelItemOption | ||
509 | + title={'工作表'} | ||
510 | + list={workFormList} | ||
511 | + onChange={formChange} | ||
512 | + item={formItem} | ||
513 | + placeholder={'请选择工作表'} | ||
514 | + /> | ||
515 | + </div> | ||
516 | + ) : ( | ||
517 | + <div style={{ margin: '20px auto' }}> | ||
518 | + <AggreOption | ||
519 | + aggreList={aggreList} | ||
520 | + aggreValue={aggreValue} | ||
521 | + onChange={aggreValueChange} | ||
522 | + title={'数据表'} | ||
523 | + /> | ||
524 | + </div> | ||
525 | + )} | ||
526 | + </div> | ||
527 | + )} | ||
528 | + </Modal> | ||
529 | + </> | ||
530 | + ); | ||
531 | +}; | ||
532 | + | ||
533 | +type Item = { | ||
534 | + name: string; | ||
535 | + code?: string; | ||
536 | + deleted?: boolean; | ||
537 | +}; |
src/qx-app-selector/service.ts
0 → 100644
1 | +/** | ||
2 | + * 应用选项 | ||
3 | + */ | ||
4 | +export function getAppList( | ||
5 | + request: any, | ||
6 | + params?: { keyword?: string; code?: string; from?: string }, | ||
7 | +) { | ||
8 | + return request.get(`/qx-apaas-lowcode/app/option`, { params }); | ||
9 | +} | ||
10 | + | ||
11 | +/** | ||
12 | + * 功能信息 | ||
13 | + */ | ||
14 | +export function getFunInfo(request: any, funId: string) { | ||
15 | + return request.get(`/qx-apaas-lowcode/app/form/${funId}`); | ||
16 | +} | ||
17 | + | ||
18 | +/** | ||
19 | + * 表单选项 | ||
20 | + */ | ||
21 | +export function getFunList( | ||
22 | + request: any, | ||
23 | + appId: string, | ||
24 | + params?: { keyword?: string; code?: string; id?: string; hasChild?: boolean }, | ||
25 | +) { | ||
26 | + return request.get(`/qx-apaas-lowcode/app/${appId}/option`, { params }); | ||
27 | +} | ||
28 | + | ||
29 | +//获取聚合表的所有选项 | ||
30 | +export function getAggreList( | ||
31 | + request: any, | ||
32 | + params?: { keyword?: string; code?: string }, | ||
33 | +) { | ||
34 | + return request.get(`/qx-apaas-lowcode/dataset/join/option`, { params }); | ||
35 | +} | ||
36 | + | ||
37 | +// 获取应用下 聚合表的所有选项 | ||
38 | +export function getDatasetListByAppid(request: any, appId: string) { | ||
39 | + return request.get(`/qx-apaas-lowcode/dataset/${appId}/option`); | ||
40 | +} |
src/qx-app-selector/styles.less
0 → 100644
1 | +.ant-input.select-source { | ||
2 | + display: flex; | ||
3 | + flex-direction: row; | ||
4 | + align-items: center; | ||
5 | + justify-content: space-between; | ||
6 | + min-height: 32px; | ||
7 | + padding-top: 2px; | ||
8 | + padding-bottom: 2px; | ||
9 | + | ||
10 | + .ant-tag { | ||
11 | + margin: 1px; | ||
12 | + } | ||
13 | + | ||
14 | + > .anticon { | ||
15 | + padding: 0 6px; | ||
16 | + color: rgba(0, 0, 0, 25%); | ||
17 | + font-size: 12px; | ||
18 | + } | ||
19 | +} |
src/qx-form-select/index.md
0 → 100644
1 | +### 选择表单 | ||
2 | + | ||
3 | +```tsx | ||
4 | +import { createRequest } from '@qx/utils'; | ||
5 | +import React, { useState } from 'react'; | ||
6 | +import { QxFormSelect } from './index'; | ||
7 | + | ||
8 | +export default () => { | ||
9 | + const [value, setValue] = useState({}); | ||
10 | + const options = [ | ||
11 | + { | ||
12 | + name: '地址', | ||
13 | + code: 'UdXaoSj9EHYr3wZL253', | ||
14 | + extract: { | ||
15 | + code: 'o72gf', | ||
16 | + isTree: false, | ||
17 | + appName: 'LT-表单', | ||
18 | + type: 'form', | ||
19 | + }, | ||
20 | + }, | ||
21 | + { | ||
22 | + name: '测试关联记录按钮', | ||
23 | + code: 'ZRLa6NjJ98u3lFVLSur', | ||
24 | + extract: { | ||
25 | + code: 'vwxxw', | ||
26 | + isTree: false, | ||
27 | + appName: 'LT-表单', | ||
28 | + type: 'form', | ||
29 | + }, | ||
30 | + }, | ||
31 | + { | ||
32 | + name: '测试关联记录权限', | ||
33 | + code: 'SRqC0JJmcjKquqnn228', | ||
34 | + extract: { | ||
35 | + code: '60jte', | ||
36 | + isTree: false, | ||
37 | + appName: 'LT-表单', | ||
38 | + type: 'form', | ||
39 | + }, | ||
40 | + }, | ||
41 | + { | ||
42 | + name: '子表', | ||
43 | + code: 'GYsqE8yphn1amjVM2OY', | ||
44 | + extract: { | ||
45 | + code: 'oi0d2', | ||
46 | + isTree: false, | ||
47 | + appName: 'LT-表单', | ||
48 | + type: 'form', | ||
49 | + }, | ||
50 | + }, | ||
51 | + { | ||
52 | + name: '关联基础', | ||
53 | + code: '2wN04K7nF0fHjRbOAvu', | ||
54 | + extract: { | ||
55 | + code: '0jpbl', | ||
56 | + isTree: false, | ||
57 | + appName: 'LT-表单', | ||
58 | + type: 'form', | ||
59 | + }, | ||
60 | + }, | ||
61 | + { | ||
62 | + name: '关联记录', | ||
63 | + code: 's6k5W5aovjtiU7kvqVl', | ||
64 | + extract: { | ||
65 | + code: 'zbynu', | ||
66 | + isTree: false, | ||
67 | + appName: 'LT-表单', | ||
68 | + type: 'form', | ||
69 | + }, | ||
70 | + }, | ||
71 | + { | ||
72 | + name: '关联记录-关联记录', | ||
73 | + code: 'hfbs4k6Lbs4WV7tOept', | ||
74 | + extract: { | ||
75 | + code: 'fz7nl', | ||
76 | + isTree: false, | ||
77 | + appName: 'LT-表单', | ||
78 | + type: 'form', | ||
79 | + }, | ||
80 | + }, | ||
81 | + { | ||
82 | + name: '关联属性-关联记录', | ||
83 | + code: 'WPjXCOemcBxoXqeEX6x', | ||
84 | + extract: { | ||
85 | + code: 's585f', | ||
86 | + isTree: false, | ||
87 | + appName: 'LT-表单', | ||
88 | + type: 'form', | ||
89 | + }, | ||
90 | + }, | ||
91 | + { | ||
92 | + name: '子表-关联记录', | ||
93 | + code: 'H3moABKMnhVqjqcVbsa', | ||
94 | + extract: { | ||
95 | + code: 'snfhq', | ||
96 | + isTree: false, | ||
97 | + appName: 'LT-表单', | ||
98 | + type: 'form', | ||
99 | + }, | ||
100 | + }, | ||
101 | + { | ||
102 | + name: '关联记录1', | ||
103 | + code: 'PQspcwgyiewytF9iizd', | ||
104 | + extract: { | ||
105 | + code: 'f53ql', | ||
106 | + isTree: false, | ||
107 | + appName: 'LT-表单', | ||
108 | + type: 'form', | ||
109 | + }, | ||
110 | + }, | ||
111 | + { | ||
112 | + name: '富文本', | ||
113 | + code: 'On7QbrZv9u1qtVgMAGm', | ||
114 | + extract: { | ||
115 | + code: 'tmh23', | ||
116 | + isTree: false, | ||
117 | + appName: 'LT-表单', | ||
118 | + type: 'form', | ||
119 | + }, | ||
120 | + }, | ||
121 | + { | ||
122 | + name: '关联记录删除', | ||
123 | + code: 'MdUFSBxWOYKXSYGSqSk', | ||
124 | + extract: { | ||
125 | + code: 'uxenf', | ||
126 | + isTree: false, | ||
127 | + appName: 'LT-表单', | ||
128 | + type: 'form', | ||
129 | + }, | ||
130 | + }, | ||
131 | + { | ||
132 | + name: '1', | ||
133 | + code: 'HAJ8dEcoPLnjx81Uxjc', | ||
134 | + extract: { | ||
135 | + code: '3xtcr', | ||
136 | + isTree: false, | ||
137 | + appName: 'LT-表单', | ||
138 | + type: 'form', | ||
139 | + }, | ||
140 | + }, | ||
141 | + { | ||
142 | + name: '基础表单', | ||
143 | + code: 'oOY4njEfrHx7PlSlFRf', | ||
144 | + extract: { | ||
145 | + code: 'fd3eb', | ||
146 | + isTree: false, | ||
147 | + appName: 'LT-表单', | ||
148 | + type: 'form', | ||
149 | + }, | ||
150 | + }, | ||
151 | + { | ||
152 | + name: '关联记录', | ||
153 | + code: 'HaIdxngReF8WtKclakp', | ||
154 | + extract: { | ||
155 | + code: 'qbroi', | ||
156 | + isTree: false, | ||
157 | + appName: 'LT-表单', | ||
158 | + type: 'form', | ||
159 | + }, | ||
160 | + }, | ||
161 | + { | ||
162 | + name: '筛选', | ||
163 | + code: 'amWz1TlerTCrysLhKdD', | ||
164 | + extract: { | ||
165 | + code: 'ionr7', | ||
166 | + isTree: false, | ||
167 | + appName: 'LT-表单', | ||
168 | + type: 'form', | ||
169 | + }, | ||
170 | + }, | ||
171 | + { | ||
172 | + name: '子表', | ||
173 | + code: 'JV5IeD1Xc4MtwWdbPhB', | ||
174 | + extract: { | ||
175 | + code: 'x4lh4', | ||
176 | + isTree: false, | ||
177 | + appName: 'LT-表单', | ||
178 | + type: 'form', | ||
179 | + }, | ||
180 | + }, | ||
181 | + { | ||
182 | + name: '附件图片', | ||
183 | + code: 'jCfTNFw7tjERB5jnnWv', | ||
184 | + extract: { | ||
185 | + code: 'pqqko', | ||
186 | + isTree: false, | ||
187 | + appName: 'LT-表单', | ||
188 | + type: 'form', | ||
189 | + }, | ||
190 | + }, | ||
191 | + { | ||
192 | + name: '日期时间', | ||
193 | + code: 'WruqGjaxMj0jLsZ6Ufr', | ||
194 | + extract: { | ||
195 | + code: 'z15we', | ||
196 | + isTree: false, | ||
197 | + appName: 'LT-表单', | ||
198 | + type: 'form', | ||
199 | + }, | ||
200 | + }, | ||
201 | + { | ||
202 | + name: '选人员部门', | ||
203 | + code: 'wqsubyh5kJFsxXuHYyQ', | ||
204 | + extract: { | ||
205 | + code: '0h42r', | ||
206 | + isTree: false, | ||
207 | + appName: 'LT-表单', | ||
208 | + type: 'form', | ||
209 | + }, | ||
210 | + }, | ||
211 | + { | ||
212 | + name: '数值', | ||
213 | + code: 'HEsh8KhnUToggsWuboT', | ||
214 | + extract: { | ||
215 | + code: 'bb8ev', | ||
216 | + isTree: false, | ||
217 | + appName: 'LT-表单', | ||
218 | + type: 'form', | ||
219 | + }, | ||
220 | + }, | ||
221 | + { | ||
222 | + name: '多字段', | ||
223 | + code: 'deTV0Jjc1prqYZTERfN', | ||
224 | + extract: { | ||
225 | + code: 'hxaxj', | ||
226 | + isTree: false, | ||
227 | + appName: 'LT-表单', | ||
228 | + type: 'form', | ||
229 | + }, | ||
230 | + }, | ||
231 | + ]; | ||
232 | + | ||
233 | + return ( | ||
234 | + <QxFormSelect | ||
235 | + options={options} | ||
236 | + value={value} | ||
237 | + onChange={(datasource) => { | ||
238 | + setValue(datasource); | ||
239 | + }} | ||
240 | + request={createRequest()} | ||
241 | + /> | ||
242 | + ); | ||
243 | +}; | ||
244 | +``` | ||
245 | + | ||
246 | +<API></API> |
src/qx-form-select/index.tsx
0 → 100644
1 | +import { BlockOutlined } from '@ant-design/icons'; | ||
2 | +import { useSetState } from 'ahooks'; | ||
3 | +import { Button } from 'antd'; | ||
4 | +import cls from 'classnames'; | ||
5 | +import React, { useCallback, useRef } from 'react'; | ||
6 | +import { QxAppSelector } from '../qx-app-selector'; | ||
7 | +import type { InputSelectProps } from '../qx-input-select'; | ||
8 | +import { QxInputSelect } from '../qx-input-select'; | ||
9 | + | ||
10 | +import './styles.less'; | ||
11 | + | ||
12 | +const prefix = 'qx-datasource-select'; | ||
13 | + | ||
14 | +/** | ||
15 | + * 选择数据源 | ||
16 | + */ | ||
17 | +export const QxFormSelect: React.FC<FormSelectProps> = (props) => { | ||
18 | + const { | ||
19 | + className, | ||
20 | + options = [], | ||
21 | + onChange, | ||
22 | + value, | ||
23 | + loading, | ||
24 | + appId, | ||
25 | + request, | ||
26 | + disabled, | ||
27 | + } = props; | ||
28 | + | ||
29 | + const [state, setState] = useSetState<FormSelectState>({ | ||
30 | + visible: false, | ||
31 | + modalVisible: false, | ||
32 | + }); | ||
33 | + | ||
34 | + const inputSelectRef = useRef<any>(); | ||
35 | + | ||
36 | + const handleChange = (val: any) => { | ||
37 | + if (!val?.code) return; | ||
38 | + function onOk() { | ||
39 | + if (val?.code) { | ||
40 | + onChange?.(val); | ||
41 | + setState({ | ||
42 | + visible: false, | ||
43 | + }); | ||
44 | + } | ||
45 | + } | ||
46 | + onOk(); | ||
47 | + }; | ||
48 | + | ||
49 | + const onOpenOther = useCallback(() => { | ||
50 | + inputSelectRef.current?.closeDropdown(); | ||
51 | + setState({ | ||
52 | + modalVisible: true, | ||
53 | + }); | ||
54 | + }, []); | ||
55 | + | ||
56 | + return ( | ||
57 | + <div className={cls(prefix, className)}> | ||
58 | + <QxInputSelect | ||
59 | + ref={inputSelectRef} | ||
60 | + value={value?.name} | ||
61 | + defaultValue={value?.name} | ||
62 | + placeholder="请选择数据源" | ||
63 | + prefix={<BlockOutlined style={{ color: '#52c41a' }} />} | ||
64 | + options={options} | ||
65 | + onChange={handleChange} | ||
66 | + dropdownProps={{ | ||
67 | + showSearch: true, | ||
68 | + loading, | ||
69 | + renderBottom: ( | ||
70 | + <Button | ||
71 | + className={`${prefix}__dropdown-bottom`} | ||
72 | + type="link" | ||
73 | + onClick={onOpenOther} | ||
74 | + > | ||
75 | + 其他应用 | ||
76 | + </Button> | ||
77 | + ), | ||
78 | + }} | ||
79 | + disabled={disabled} | ||
80 | + /> | ||
81 | + | ||
82 | + {state.modalVisible ? ( | ||
83 | + <QxAppSelector | ||
84 | + title="选择数据源" | ||
85 | + item={{ | ||
86 | + flag: 'SINGLE', | ||
87 | + currentId: '', | ||
88 | + appId: appId, | ||
89 | + }} | ||
90 | + onChange={handleChange} | ||
91 | + flag="join" | ||
92 | + showTableTypeTab={false} | ||
93 | + modalProps={{ | ||
94 | + width: 550, | ||
95 | + destroyOnClose: true, | ||
96 | + visible: state.modalVisible, | ||
97 | + onOk: () => setState({ modalVisible: false }), | ||
98 | + onCancel: () => setState({ modalVisible: false }), | ||
99 | + }} | ||
100 | + request={request} | ||
101 | + /> | ||
102 | + ) : null} | ||
103 | + </div> | ||
104 | + ); | ||
105 | +}; | ||
106 | + | ||
107 | +interface FormSelectProps extends InputSelectProps { | ||
108 | + value?: any; | ||
109 | + loading?: boolean; | ||
110 | + appId?: string; | ||
111 | + request?: any; | ||
112 | + disabled?: boolean; | ||
113 | +} | ||
114 | +interface FormSelectState { | ||
115 | + visible: boolean; | ||
116 | + modalVisible: boolean; | ||
117 | +} |
src/qx-form-select/styles.less
0 → 100644
src/qx-input-select/dropdown-content.tsx
0 → 100644
1 | +import { SearchOutlined } from '@ant-design/icons'; | ||
2 | +import { useScroll } from 'ahooks'; | ||
3 | +import { Empty, Input, Menu, Spin } from 'antd'; | ||
4 | +import cls from 'classnames'; | ||
5 | +import { debounce } from 'lodash'; | ||
6 | +import React, { | ||
7 | + memo, | ||
8 | + useCallback, | ||
9 | + useEffect, | ||
10 | + useMemo, | ||
11 | + useRef, | ||
12 | + useState, | ||
13 | +} from 'react'; | ||
14 | +import './styles.less'; | ||
15 | +const prefix = 'qx-input-select-dropdown'; | ||
16 | + | ||
17 | +const listItemHeight = 40; | ||
18 | + | ||
19 | +const defaultProps = { | ||
20 | + fieldNames: { | ||
21 | + label: 'name', | ||
22 | + value: 'code', | ||
23 | + }, | ||
24 | +}; | ||
25 | + | ||
26 | +const DropdownContent = React.forwardRef<any, DropdownContentProps>( | ||
27 | + (props, forwardRef) => { | ||
28 | + const { | ||
29 | + options = [], | ||
30 | + fieldNames = defaultProps.fieldNames, | ||
31 | + onPopupScroll, | ||
32 | + onChange, | ||
33 | + className, | ||
34 | + style, | ||
35 | + renderBottom, | ||
36 | + showSearch = false, | ||
37 | + onSearch: parentSearch, | ||
38 | + placeholder, | ||
39 | + listHeight = 260, | ||
40 | + loading, | ||
41 | + } = props; | ||
42 | + | ||
43 | + const [filteredOpts, setFilteredOpts] = useState<any[]>(options || []); | ||
44 | + const ref = useRef(null); | ||
45 | + const position = useScroll(ref); | ||
46 | + const isEmpty = !filteredOpts.length; | ||
47 | + const fieldLabel = fieldNames.label || 'name'; | ||
48 | + const fieldValue = fieldNames.value || 'code'; | ||
49 | + | ||
50 | + const computedOptions = useMemo( | ||
51 | + () => | ||
52 | + options?.map((opt) => ({ | ||
53 | + label: opt[fieldLabel], | ||
54 | + key: opt[fieldValue], | ||
55 | + })), | ||
56 | + [options], | ||
57 | + ); | ||
58 | + | ||
59 | + const handleSearch = useCallback( | ||
60 | + debounce((e) => { | ||
61 | + const val = e.target?.value; | ||
62 | + const newOpts = val | ||
63 | + ? computedOptions.filter((opt) => { | ||
64 | + return opt.label?.includes(val); | ||
65 | + }) | ||
66 | + : computedOptions; | ||
67 | + setFilteredOpts(newOpts); | ||
68 | + }, 800), | ||
69 | + [computedOptions], | ||
70 | + ); | ||
71 | + | ||
72 | + const onSearch = (e: any) => { | ||
73 | + e.persist(); | ||
74 | + if (parentSearch) { | ||
75 | + parentSearch?.(e.target?.value); | ||
76 | + } else { | ||
77 | + handleSearch(e); | ||
78 | + } | ||
79 | + }; | ||
80 | + | ||
81 | + const findSelected = (key: string) => | ||
82 | + filteredOpts?.find((opt) => opt.key === key); | ||
83 | + | ||
84 | + const handleChange = ({ key }: any) => { | ||
85 | + const result = findSelected(key); | ||
86 | + onChange?.({ | ||
87 | + ...result, | ||
88 | + [fieldLabel]: result?.label, | ||
89 | + [fieldValue]: result?.key, | ||
90 | + }); | ||
91 | + }; | ||
92 | + | ||
93 | + useEffect(() => { | ||
94 | + const length = options?.length; | ||
95 | + | ||
96 | + if (length < 6) return; | ||
97 | + | ||
98 | + if (position) { | ||
99 | + const _listHeight = length * listItemHeight; | ||
100 | + | ||
101 | + const isBottom = _listHeight - _listHeight <= position.top; | ||
102 | + | ||
103 | + onPopupScroll?.(position, isBottom); | ||
104 | + } | ||
105 | + }, [position, options]); | ||
106 | + | ||
107 | + useEffect(() => { | ||
108 | + if (Array.isArray(computedOptions)) { | ||
109 | + setFilteredOpts(computedOptions); | ||
110 | + } | ||
111 | + }, [computedOptions]); | ||
112 | + | ||
113 | + return ( | ||
114 | + <div ref={forwardRef} className={cls(prefix, className)} style={style}> | ||
115 | + {showSearch ? ( | ||
116 | + <Input | ||
117 | + placeholder={placeholder || '输入名称搜索'} | ||
118 | + prefix={<SearchOutlined />} | ||
119 | + className={`${prefix}__input`} | ||
120 | + onChange={onSearch} | ||
121 | + /> | ||
122 | + ) : null} | ||
123 | + | ||
124 | + {loading ? ( | ||
125 | + <Spin spinning={loading} /> | ||
126 | + ) : ( | ||
127 | + <div | ||
128 | + ref={ref} | ||
129 | + className={`${prefix}__list`} | ||
130 | + style={{ height: listHeight }} | ||
131 | + > | ||
132 | + {isEmpty ? ( | ||
133 | + <Empty | ||
134 | + className={`${prefix}__list-empty`} | ||
135 | + image={Empty.PRESENTED_IMAGE_SIMPLE} | ||
136 | + /> | ||
137 | + ) : ( | ||
138 | + <Menu | ||
139 | + className={cls(`${prefix}__list-content`, { | ||
140 | + [`${prefix}__list-content--has-bottom`]: !!renderBottom, | ||
141 | + })} | ||
142 | + items={filteredOpts} | ||
143 | + onClick={handleChange} | ||
144 | + /> | ||
145 | + )} | ||
146 | + </div> | ||
147 | + )} | ||
148 | + <div className={`${prefix}__list-content-bottom`}>{renderBottom}</div> | ||
149 | + </div> | ||
150 | + ); | ||
151 | + }, | ||
152 | +); | ||
153 | + | ||
154 | +interface FieldNames { | ||
155 | + label: string; | ||
156 | + value: string; | ||
157 | + options?: any; | ||
158 | +} | ||
159 | + | ||
160 | +export interface DropdownContentOptions { | ||
161 | + name: string | React.ReactNode; | ||
162 | + code: string | number; | ||
163 | +} | ||
164 | + | ||
165 | +export interface DropdownContentProps { | ||
166 | + placeholder?: string; | ||
167 | + options?: DropdownContentOptions[]; | ||
168 | + fieldNames?: FieldNames; | ||
169 | + onPopupScroll?: ( | ||
170 | + options: { left: number; top: number }, | ||
171 | + isBottom: boolean, | ||
172 | + ) => void; | ||
173 | + onChange?: (val: any) => void; | ||
174 | + onOpenOther?: () => void; | ||
175 | + showSearch?: boolean; | ||
176 | + onSearch?: (val: string) => void; | ||
177 | + renderBottom?: React.ReactNode; | ||
178 | + listHeight?: number; | ||
179 | + loading?: boolean; | ||
180 | + className?: string; | ||
181 | + style?: React.CSSProperties; | ||
182 | +} | ||
183 | + | ||
184 | +export default memo(DropdownContent); |
src/qx-input-select/index.tsx
0 → 100644
1 | +import { DownOutlined, UpOutlined } from '@ant-design/icons'; | ||
2 | +import { useSetState } from 'ahooks'; | ||
3 | +import { Dropdown, Input } from 'antd'; | ||
4 | +import type { InputProps } from 'antd/lib/input'; | ||
5 | +import cls from 'classnames'; | ||
6 | +import React, { useImperativeHandle } from 'react'; | ||
7 | +import type { | ||
8 | + DropdownContentOptions, | ||
9 | + DropdownContentProps, | ||
10 | +} from './dropdown-content'; | ||
11 | +import DropdownContent from './dropdown-content'; | ||
12 | + | ||
13 | +import './styles.less'; | ||
14 | + | ||
15 | +const prefix = 'qx-input-select'; | ||
16 | + | ||
17 | +/** | ||
18 | + * 下拉选择 | ||
19 | + */ | ||
20 | +export const QxInputSelect = React.forwardRef<any, InputSelectProps>( | ||
21 | + (props, ref) => { | ||
22 | + const { | ||
23 | + className, | ||
24 | + options = [], | ||
25 | + dropdownProps, | ||
26 | + onChange, | ||
27 | + disabled, | ||
28 | + ...rest | ||
29 | + } = props; | ||
30 | + | ||
31 | + const [state, setState] = useSetState<InputSelectState>({ | ||
32 | + visible: false, | ||
33 | + }); | ||
34 | + | ||
35 | + const inputSuffix = ( | ||
36 | + <div className={`${prefix}__input-suffix`}> | ||
37 | + {state.visible ? <UpOutlined /> : <DownOutlined />} | ||
38 | + </div> | ||
39 | + ); | ||
40 | + | ||
41 | + const handleChange = (val: DropdownContentOptions) => { | ||
42 | + onChange?.(val); | ||
43 | + setState({ visible: false }); | ||
44 | + }; | ||
45 | + | ||
46 | + /** | ||
47 | + * 菜单显示状态改变时调用 | ||
48 | + */ | ||
49 | + const onVisibleChange = (visible: boolean) => { | ||
50 | + setState({ visible }); | ||
51 | + }; | ||
52 | + | ||
53 | + useImperativeHandle(ref, () => ({ | ||
54 | + closeDropdown: () => { | ||
55 | + setState({ visible: false }); | ||
56 | + }, | ||
57 | + openDropdown: () => { | ||
58 | + setState({ visible: true }); | ||
59 | + }, | ||
60 | + })); | ||
61 | + | ||
62 | + return ( | ||
63 | + <div className={cls(prefix, className)}> | ||
64 | + <Dropdown | ||
65 | + visible={state.visible} | ||
66 | + destroyPopupOnHide | ||
67 | + trigger={['click']} | ||
68 | + className={`${prefix}__dropdown`} | ||
69 | + overlay={() => ( | ||
70 | + <DropdownContent | ||
71 | + options={options} | ||
72 | + onChange={handleChange} | ||
73 | + {...dropdownProps} | ||
74 | + /> | ||
75 | + )} | ||
76 | + getPopupContainer={(triggerNode) => triggerNode} | ||
77 | + onVisibleChange={onVisibleChange} | ||
78 | + disabled={disabled} | ||
79 | + > | ||
80 | + <Input | ||
81 | + placeholder="请选择" | ||
82 | + readOnly | ||
83 | + suffix={inputSuffix} | ||
84 | + onClick={() => setState({ visible: !state.visible })} | ||
85 | + {...rest} | ||
86 | + className={`${prefix}__input`} | ||
87 | + /> | ||
88 | + </Dropdown> | ||
89 | + </div> | ||
90 | + ); | ||
91 | + }, | ||
92 | +); | ||
93 | + | ||
94 | +export interface InputSelectProps extends Omit<InputProps, 'onChange'> { | ||
95 | + onChange?: (args: DropdownContentOptions) => void; | ||
96 | + options?: DropdownContentOptions[]; | ||
97 | + dropdownProps?: DropdownContentProps; | ||
98 | + disabled?: boolean; | ||
99 | +} | ||
100 | +export interface InputSelectState { | ||
101 | + visible: boolean; | ||
102 | +} |
src/qx-input-select/styles.less
0 → 100644
1 | +@import '~@qx/ui/src/style/variable.less'; | ||
2 | + | ||
3 | +@prefix: ~'qx-input-select'; | ||
4 | + | ||
5 | +.@{prefix} { | ||
6 | + &-dropdown { | ||
7 | + position: relative; | ||
8 | + width: 100%; | ||
9 | + padding: 10px 10px 50px; | ||
10 | + overflow: hidden; | ||
11 | + background-color: #fff; | ||
12 | + box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 12%), | ||
13 | + 0 6px 16px 0 rgba(0, 0, 0, 8%), 0 9px 28px 8px rgba(0, 0, 0, 5%); | ||
14 | + | ||
15 | + &__list { | ||
16 | + min-height: 150px; | ||
17 | + max-height: 260px; | ||
18 | + margin-top: 5px; | ||
19 | + overflow-y: auto; | ||
20 | + } | ||
21 | + | ||
22 | + &__list-empty { | ||
23 | + margin-top: 15%; | ||
24 | + } | ||
25 | + | ||
26 | + &__list-content { | ||
27 | + width: 100%; | ||
28 | + | ||
29 | + :global { | ||
30 | + .ant-menu-item { | ||
31 | + margin-bottom: 0; | ||
32 | + | ||
33 | + &:hover { | ||
34 | + background-color: #f5f5f5; | ||
35 | + } | ||
36 | + } | ||
37 | + } | ||
38 | + } | ||
39 | + | ||
40 | + &__list-content-bottom { | ||
41 | + position: absolute; | ||
42 | + bottom: 0; | ||
43 | + width: 100%; | ||
44 | + } | ||
45 | + | ||
46 | + &__input { | ||
47 | + :global { | ||
48 | + .anticon { | ||
49 | + padding: 0 5px; | ||
50 | + color: silver; | ||
51 | + } | ||
52 | + } | ||
53 | + | ||
54 | + padding-bottom: 5px !important; | ||
55 | + padding-left: 0 !important; | ||
56 | + border: none !important; | ||
57 | + border-bottom: 1px solid #e6eaf2 !important; | ||
58 | + outline: none !important; | ||
59 | + box-shadow: none !important; | ||
60 | + | ||
61 | + &:hover, | ||
62 | + &:focus, | ||
63 | + &:active { | ||
64 | + border-color: @B8; | ||
65 | + } | ||
66 | + } | ||
67 | + } | ||
68 | + | ||
69 | + &__input { | ||
70 | + :global { | ||
71 | + input { | ||
72 | + cursor: default; | ||
73 | + } | ||
74 | + } | ||
75 | + } | ||
76 | + | ||
77 | + &__input-suffix { | ||
78 | + color: silver; | ||
79 | + } | ||
80 | +} |
1 | -## 选人组件 | 1 | +### 选人组件 |
2 | 2 | ||
3 | ```tsx | 3 | ```tsx |
4 | /** | 4 | /** |
5 | * debug: true | 5 | * debug: true |
6 | */ | 6 | */ |
7 | -import React from 'react'; | ||
8 | import { QxUserSelector } from '@qx/common'; | 7 | import { QxUserSelector } from '@qx/common'; |
9 | -// import request from 'umi-request'; | 8 | +import React from 'react'; |
9 | +import request from 'umi-request'; | ||
10 | + | ||
11 | +request.interceptors.request.use((url, options) => { | ||
12 | + if (url.startsWith('/api/')) { | ||
13 | + return { url, options }; | ||
14 | + } | ||
15 | + const headers = { Authorization: 'dev_session:ZGuqjkCF3GMzorijXw7' }; | ||
16 | + // headers['SERVER-IP'] = '192.168.181.112'; | ||
17 | + | ||
18 | + const fullUrl = url.startsWith('http') | ||
19 | + ? url | ||
20 | + : `http://10.9.1.180/qx-api${url}`; | ||
21 | + return { | ||
22 | + url: fullUrl, | ||
23 | + options: { | ||
24 | + ...options, | ||
25 | + ...{ | ||
26 | + headers: { ...headers, ...(options.customHeaders || {}) }, | ||
27 | + isInternal: true, | ||
28 | + timeout: 30000, | ||
29 | + }, | ||
30 | + }, | ||
31 | + }; | ||
32 | +}); | ||
33 | + | ||
34 | +request.interceptors.response.use(async (response, options) => { | ||
35 | + if (response.status !== 200) { | ||
36 | + return Promise.reject(response); | ||
37 | + } | ||
38 | + | ||
39 | + if (!response.headers.get('content-type')?.includes('application/json')) { | ||
40 | + return response.blob(); | ||
41 | + } | ||
42 | + | ||
43 | + const body = await response.clone().json(); | ||
44 | + | ||
45 | + // 按正常逻辑处理"文件上传"系列接口 | ||
46 | + const fsUploadApis = ['/file/checkFile', '/file/uploadByExist', '/fss/file/']; | ||
47 | + const isFsUploadApis = fsUploadApis.filter( | ||
48 | + (api: string) => options.url.indexOf(api) !== -1, | ||
49 | + ); | ||
50 | + if (isFsUploadApis.length > 0) { | ||
51 | + return Promise.resolve(body || null); | ||
52 | + } | ||
53 | + | ||
54 | + if (body.success) { | ||
55 | + return Promise.resolve(body.data || null); | ||
56 | + } | ||
10 | 57 | ||
11 | -// request.interceptors.request.use((url, options) => { | ||
12 | -// if (url.startsWith('/api/')) { | ||
13 | -// return { url, options }; | ||
14 | -// } | ||
15 | -// const headers = { Authorization: 'dev_session:ZGuqjkCF3GMzorijXw7' }; | ||
16 | -// // headers['SERVER-IP'] = '192.168.181.112'; | ||
17 | -// | ||
18 | -// const fullUrl = url.startsWith('http') ? url : `http://10.9.1.180/qx-api${url}`; | ||
19 | -// return { | ||
20 | -// url: fullUrl, | ||
21 | -// options: { | ||
22 | -// ...options, | ||
23 | -// ...{ | ||
24 | -// headers: { ...headers, ...(options.customHeaders || {}) }, | ||
25 | -// isInternal: true, | ||
26 | -// timeout: 30000, | ||
27 | -// }, | ||
28 | -// }, | ||
29 | -// }; | ||
30 | -// }); | 58 | + console.error('网络请求出错:', body.msg); |
31 | 59 | ||
32 | -// request.interceptors.response.use(async (response, options) => { | ||
33 | -// if (response.status !== 200) { | ||
34 | -// return Promise.reject(response); | ||
35 | -// } | ||
36 | -// | ||
37 | -// if (!response.headers.get('content-type')?.includes('application/json')) { | ||
38 | -// return response.blob(); | ||
39 | -// } | ||
40 | -// | ||
41 | -// const body = await response.clone().json(); | ||
42 | -// | ||
43 | -// // 按正常逻辑处理"文件上传"系列接口 | ||
44 | -// const fsUploadApis = ['/file/checkFile', '/file/uploadByExist', '/fss/file/']; | ||
45 | -// const isFsUploadApis = fsUploadApis.filter((api: string) => options.url.indexOf(api) !== -1); | ||
46 | -// if (isFsUploadApis.length > 0) { | ||
47 | -// return Promise.resolve(body || null); | ||
48 | -// } | ||
49 | -// | ||
50 | -// if (body.success) { | ||
51 | -// return Promise.resolve(body.data || null); | ||
52 | -// } | ||
53 | -// | ||
54 | -// console.error('网络请求出错:', body.msg); | ||
55 | -// | ||
56 | -// return Promise.reject(body); | ||
57 | -// }); | 60 | + return Promise.reject(body); |
61 | +}); | ||
58 | 62 | ||
59 | export default () => { | 63 | export default () => { |
60 | return ( | 64 | return ( |
61 | <div> | 65 | <div> |
62 | <QxUserSelector | 66 | <QxUserSelector |
63 | - // request={request} | 67 | + request={request} |
64 | params={{ | 68 | params={{ |
65 | org: [{ relType: 'APPOINT_ORG', relIds: [''] }], | 69 | org: [{ relType: 'APPOINT_ORG', relIds: [''] }], |
66 | pos: null, | 70 | pos: null, |
67 | - range: ['ORG:MubDrwZm8IMxuLDU9FM', 'ORG:a0WZVI96GAdoI5g9IwX', 'ORG:QPLEku2yJU8hmbpLTtg'], | 71 | + range: [ |
72 | + 'ORG:MubDrwZm8IMxuLDU9FM', | ||
73 | + 'ORG:a0WZVI96GAdoI5g9IwX', | ||
74 | + 'ORG:QPLEku2yJU8hmbpLTtg', | ||
75 | + ], | ||
68 | }} | 76 | }} |
69 | /> | 77 | /> |
70 | <br /> | 78 | <br /> |
71 | - <QxUserSelector /> | 79 | + <QxUserSelector request={request} /> |
72 | <br /> | 80 | <br /> |
73 | <QxUserSelector | 81 | <QxUserSelector |
74 | readOnly | 82 | readOnly |
75 | value={['1212']} | 83 | value={['1212']} |
76 | defaultData={[{ id: '1212', name: '邢晴晴' }]} | 84 | defaultData={[{ id: '1212', name: '邢晴晴' }]} |
77 | - // request={request} | 85 | + request={request} |
78 | /> | 86 | /> |
79 | </div> | 87 | </div> |
80 | ); | 88 | ); |