Showing
12 changed files
with
1437 additions
and
53 deletions
... | ... | @@ -54,6 +54,7 @@ |
54 | 54 | "devDependencies": { |
55 | 55 | "@commitlint/cli": "^17.1.2", |
56 | 56 | "@commitlint/config-conventional": "^17.1.0", |
57 | + "@qx/utils": "0.0.58", | |
57 | 58 | "@types/lodash-es": "^4.17.8", |
58 | 59 | "@types/react": "^18.0.0", |
59 | 60 | "@types/react-dom": "^18.0.0", |
... | ... | @@ -69,6 +70,7 @@ |
69 | 70 | "prettier-plugin-organize-imports": "^3.0.0", |
70 | 71 | "prettier-plugin-packagejson": "^2.2.18", |
71 | 72 | "react": "^18.0.0", |
73 | + "react-cookies": ">=0.1.1", | |
72 | 74 | "react-dom": "^18.0.0", |
73 | 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
1 | +@prefix: ~'qx-datasource-select'; | |
2 | + | |
3 | +.@{prefix} { | |
4 | + width: 100%; | |
5 | + | |
6 | + &__dropdown-bottom { | |
7 | + width: 100% !important; | |
8 | + height: 50px !important; | |
9 | + text-align: left !important; | |
10 | + background-color: #fff !important; | |
11 | + border-top: 1px solid #e6eaf2 !important; | |
12 | + } | |
13 | +} | ... | ... |
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 | 3 | ```tsx |
4 | 4 | /** |
5 | 5 | * debug: true |
6 | 6 | */ |
7 | -import React from 'react'; | |
8 | 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 | 63 | export default () => { |
60 | 64 | return ( |
61 | 65 | <div> |
62 | 66 | <QxUserSelector |
63 | - // request={request} | |
67 | + request={request} | |
64 | 68 | params={{ |
65 | 69 | org: [{ relType: 'APPOINT_ORG', relIds: [''] }], |
66 | 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 | 78 | <br /> |
71 | - <QxUserSelector /> | |
79 | + <QxUserSelector request={request} /> | |
72 | 80 | <br /> |
73 | 81 | <QxUserSelector |
74 | 82 | readOnly |
75 | 83 | value={['1212']} |
76 | 84 | defaultData={[{ id: '1212', name: '邢晴晴' }]} |
77 | - // request={request} | |
85 | + request={request} | |
78 | 86 | /> |
79 | 87 | </div> |
80 | 88 | ); | ... | ... |