index.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. import type { ISchema } from '@formily/json-schema';
  2. import { createSchemaField } from '@formily/react';
  3. import {
  4. ArrayItems,
  5. DatePicker,
  6. Form,
  7. FormGrid,
  8. FormItem,
  9. FormTab,
  10. Input,
  11. NumberPicker,
  12. PreviewText,
  13. Select,
  14. Space,
  15. TreeSelect,
  16. } from '@formily/antd';
  17. import type { Field, FieldDataSource } from '@formily/core';
  18. import { createForm, onFieldReact, onFieldValueChange } from '@formily/core';
  19. import GroupNameControl from '@/components/SearchComponent/GroupNameControl';
  20. import {
  21. DeleteOutlined,
  22. DoubleRightOutlined,
  23. ReloadOutlined,
  24. SaveOutlined,
  25. SearchOutlined,
  26. } from '@ant-design/icons';
  27. import { Button, Card, Dropdown, Menu, Popconfirm, Popover, Typography } from 'antd';
  28. import { useEffect, useMemo, useRef, useState } from 'react';
  29. import type { ProColumns } from '@jetlinks/pro-table';
  30. import type { EnumData } from '@/utils/typings';
  31. import styles from './index.less';
  32. import Service from '@/components/SearchComponent/service';
  33. import _ from 'lodash';
  34. import { useIntl } from '@@/plugin-locale/localeExports';
  35. import classnames from 'classnames';
  36. import { onlyMessage, randomString } from '@/utils/util';
  37. import { useHistory, useLocation } from 'umi';
  38. import { Empty } from '@/components';
  39. import { useSize } from 'ahooks';
  40. const ui2Server = (source: SearchTermsUI): SearchTermsServer => [
  41. { terms: source.terms1 },
  42. { terms: source.terms2, type: source.type },
  43. ];
  44. const server2Ui = (source: SearchTermsServer): SearchTermsUI => ({
  45. terms1: source[0].terms,
  46. terms2: source[1]?.terms,
  47. type: source[0]?.type || 'and',
  48. });
  49. interface Props<T> {
  50. /** @name "搜索条件" */
  51. field: ProColumns<T>[];
  52. onSearch: (params: { terms: SearchTermsServer }) => void;
  53. target?: string;
  54. /**
  55. * @name "固定查询参数"
  56. * eg: 1: {[{ column: 'test', value: 'admin' }]}
  57. * 2: {[
  58. * {
  59. * terms: [{ column: 'parentId$isnull', value: '' }, { column: 'parentId$not', value: 'test', type: 'or' }],
  60. * },
  61. * {
  62. * terms: [{ column: 'id$not', value: 'test', type: 'and' }],
  63. * },
  64. * ]}
  65. * */
  66. defaultParam?: SearchTermsServer | Term[];
  67. /**
  68. * @name "搜索组件模式"
  69. * simple 限制只支持一组搜索条件,用于小弹窗搜索时使用
  70. */
  71. model?: 'simple' | 'advance';
  72. enableSave?: boolean;
  73. initParam?: SearchTermsServer;
  74. style?: React.CSSProperties;
  75. bodyStyle?: React.CSSProperties;
  76. }
  77. const termType = [
  78. { label: '=', value: 'eq' },
  79. { label: '!=', value: 'not' },
  80. { label: '包含', value: 'like' },
  81. { label: '不包含', value: 'nlike' },
  82. { label: '>', value: 'gt' },
  83. { label: '>=', value: 'gte' },
  84. { label: '<', value: 'lt' },
  85. { label: '<=', value: 'lte' },
  86. { label: '属于', value: 'in' },
  87. { label: '不属于', value: 'nin' },
  88. ];
  89. const service = new Service();
  90. const SchemaField = createSchemaField({
  91. components: {
  92. FormItem,
  93. FormTab,
  94. Input,
  95. Select,
  96. NumberPicker,
  97. FormGrid,
  98. ArrayItems,
  99. DatePicker,
  100. PreviewText,
  101. GroupNameControl,
  102. Space,
  103. TreeSelect,
  104. },
  105. });
  106. /**
  107. * 搜索字段排序
  108. * @param field
  109. */
  110. const sortField = (field: ProColumns[]) => {
  111. let _temp = false;
  112. field.forEach((item) => {
  113. if (item.index) {
  114. _temp = true;
  115. return;
  116. }
  117. });
  118. if (!_temp) {
  119. // 如果没有index 就默认name字段最第一个
  120. field.map((item) => {
  121. if (item.dataIndex === 'name') {
  122. item.index = 0;
  123. return item;
  124. } else {
  125. return item;
  126. }
  127. });
  128. }
  129. // index排序
  130. return _.sortBy(field, (i) => i.index);
  131. };
  132. // 保存历史记录
  133. // 过滤不参与搜索的列数据 ==> 字段排序
  134. // 场景一:简单模式
  135. // 默认搜索参数,根据Index 来判断,或者默认name为第一组条件
  136. // 场景二:高级模式
  137. // 默认六组搜索条件。根据字段index排序
  138. // const nodeFor = <T extends Record<string, any>>(props: Props<T>, ref) => {
  139. // console.log(props,ref)
  140. // return (<></>)
  141. // }
  142. //
  143. // export const node = forwardRef(nodeFor) as <RecordType extends Record<string, any>>(props: Props<RecordType>, ref?: React.Ref<HTMLDivElement>) => React.ReactElement
  144. const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
  145. const { field, target, onSearch, defaultParam, enableSave = true, initParam, model } = props;
  146. const _history = useHistory();
  147. const _location = useLocation();
  148. /**
  149. * 过滤不参与搜索的数据 ?
  150. * TODO Refactor 依赖透明?
  151. */
  152. const filterSearchTerm = (): ProColumns<any>[] =>
  153. field
  154. .filter((item) => item.dataIndex)
  155. .filter((item) => !item.hideInSearch)
  156. .filter((item) => !['index', 'option'].includes(item.dataIndex as string));
  157. /**
  158. * 根据dataIndex 过滤不参与查询的参数
  159. *
  160. * @param _field 查询的列
  161. * @param excludes 过滤的字段名称
  162. */
  163. // const filterSearchTerm2 = (_field: ProColumns<T>[] = [], excludes: string[] = []) =>
  164. // _field.filter(item => item.dataIndex)
  165. // .filter(item => !item.hideInSearch)
  166. // .filter(item => !excludes.includes(item.dataIndex as string))
  167. // 处理后的搜索条件
  168. const processedField = sortField(filterSearchTerm());
  169. const defaultTerms = (index: number) =>
  170. ({
  171. termType: 'like',
  172. column: (processedField[index]?.dataIndex as string) || null,
  173. type: 'or',
  174. } as Partial<Term>);
  175. const intl = useIntl();
  176. const [expand, setExpand] = useState<boolean>(true);
  177. const initForm = server2Ui(initParam || [{ terms: [defaultTerms(0)] }]);
  178. const [aliasVisible, setAliasVisible] = useState<boolean>(false);
  179. const [initParams, setInitParams] = useState<SearchTermsUI>(initForm);
  180. const [history, setHistory] = useState([]);
  181. const [logVisible, setLogVisible] = useState<boolean>(false);
  182. const uiParamRef = useRef(initParam);
  183. const formDivRef = useRef(null);
  184. const formDivSize = useSize(formDivRef);
  185. const form = useMemo(
  186. () =>
  187. createForm<SearchTermsUI>({
  188. validateFirst: true,
  189. initialValues: initParams,
  190. effects() {
  191. onFieldReact('*.*.column', async (typeFiled, f) => {
  192. // if ((typeFiled as Field).modified) {
  193. const isModified = (typeFiled as Field).modified;
  194. const _column = (typeFiled as Field).value;
  195. const _field = field.find((item) => item.dataIndex === _column);
  196. if (_column === 'id') {
  197. if (isModified) {
  198. f.setFieldState(typeFiled.query('.termType'), async (state) => {
  199. state.value = 'eq';
  200. state.dataSource = termType;
  201. });
  202. }
  203. f.setFieldState(typeFiled.query('.value'), async (state) => {
  204. state.componentType = 'Input';
  205. state.componentProps = {
  206. allowClear: true,
  207. // onchange:(event:any)=>{
  208. // console.log(event.target?.value)
  209. // }
  210. };
  211. });
  212. } else {
  213. switch (_field?.valueType) {
  214. case 'select':
  215. let __option: { label: any; value: any }[] | FieldDataSource | undefined = [];
  216. f.setFieldState(typeFiled.query('.termType'), async (state) => {
  217. state.value = 'eq';
  218. state.dataSource = termType;
  219. if (_field?.dataIndex === 'state') {
  220. state.dataSource = [
  221. { label: '=', value: 'eq' },
  222. { label: '!=', value: 'not' },
  223. { label: '>', value: 'gt' },
  224. { label: '>=', value: 'gte' },
  225. { label: '<', value: 'lt' },
  226. { label: '<=', value: 'lte' },
  227. { label: '属于', value: 'in' },
  228. { label: '不属于', value: 'nin' },
  229. ];
  230. }
  231. });
  232. if (_field?.valueEnum) {
  233. __option = Object.values(_field?.valueEnum || {}).map((item) => ({
  234. label: item.text,
  235. value: item.status,
  236. }));
  237. } else if (_field?.request) {
  238. __option = await _field.request();
  239. }
  240. if (isModified) {
  241. f.setFieldState(typeFiled.query('.termType'), async (state) => {
  242. state.value = 'eq';
  243. state.dataSource = termType;
  244. });
  245. }
  246. f.setFieldState(typeFiled.query('.value'), async (state) => {
  247. state.componentType = 'Select';
  248. state.dataSource = __option;
  249. state.componentProps = {
  250. allowClear: true,
  251. showSearch: true,
  252. filterOption: (input: string, option: any) =>
  253. option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
  254. };
  255. });
  256. break;
  257. case 'treeSelect':
  258. let _option: { label: any; value: any }[] | FieldDataSource | undefined = [];
  259. if (_field?.valueEnum) {
  260. _option = Object.values(_field?.valueEnum || {}).map((item) => ({
  261. label: item.text,
  262. value: item.status,
  263. }));
  264. } else if (_field?.request) {
  265. _option = await _field.request();
  266. }
  267. if (isModified) {
  268. f.setFieldState(typeFiled.query('.termType'), (_state) => {
  269. _state.value = 'eq';
  270. _state.dataSource = termType;
  271. });
  272. }
  273. f.setFieldState(typeFiled.query('.value'), (state) => {
  274. state.componentType = 'TreeSelect';
  275. state.dataSource = _option;
  276. state.componentProps = {
  277. ..._field.fieldProps,
  278. allowClear: true,
  279. showSearch: true,
  280. treeNodeFilterProp: 'name',
  281. filterOption: (input: string, option: any) =>
  282. option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
  283. };
  284. });
  285. break;
  286. case 'digit':
  287. f.setFieldState(typeFiled.query('.value'), async (state) => {
  288. state.componentType = 'NumberPicker';
  289. state.componentProps = { allowClear: true };
  290. });
  291. if (isModified) {
  292. f.setFieldState(typeFiled.query('.termType'), async (state) => {
  293. state.value = 'eq';
  294. state.dataSource = termType;
  295. });
  296. }
  297. break;
  298. case 'dateTime':
  299. f.setFieldState(typeFiled.query('.value'), async (state) => {
  300. state.componentType = 'DatePicker';
  301. state.componentProps = { showTime: true, allowClear: true };
  302. });
  303. f.setFieldState(typeFiled.query('.termType'), async (state) => {
  304. state.value = 'gt';
  305. state.dataSource = [
  306. { label: '>', value: 'gt' },
  307. { label: '<', value: 'lt' },
  308. ];
  309. });
  310. // console.log(isModified);
  311. if (isModified) {
  312. f.setFieldState(typeFiled.query('.termType'), async (state) => {
  313. state.value = 'gt';
  314. state.dataSource = [
  315. { label: '>', value: 'gt' },
  316. { label: '<', value: 'lt' },
  317. ];
  318. });
  319. }
  320. break;
  321. default:
  322. if (isModified) {
  323. f.setFieldState(typeFiled.query('.termType'), async (state) => {
  324. state.value = 'like';
  325. state.dataSource = termType;
  326. });
  327. }
  328. f.setFieldState(typeFiled.query('.value'), async (state) => {
  329. state.componentType = 'Input';
  330. state.componentProps = { allowClear: true };
  331. });
  332. break;
  333. }
  334. }
  335. // }
  336. });
  337. onFieldValueChange('*.*.column', (field1, form1) => {
  338. form1.setFieldState(field1.query('.value'), (state1) => {
  339. if (field1.modified) {
  340. state1.value = undefined;
  341. }
  342. });
  343. });
  344. },
  345. }),
  346. [target, expand, initParams],
  347. );
  348. const historyForm = createForm();
  349. const queryHistory = async () => {
  350. const response = await service.history.query(`${target}-search`);
  351. if (response.status === 200) {
  352. setHistory(response.result);
  353. }
  354. };
  355. const createGroup = (name: string): ISchema => ({
  356. 'x-decorator': 'FormItem',
  357. 'x-decorator-props': {
  358. gridSpan: 4,
  359. },
  360. 'x-component': 'ArrayItems',
  361. type: 'array',
  362. 'x-value': new Array(expand ? 1 : 3).fill({ termType: 'like' }),
  363. items: {
  364. type: 'object',
  365. 'x-component': 'FormGrid',
  366. 'x-component-props': {
  367. minColumns: 14,
  368. maxColumns: 14,
  369. columnGap: 24,
  370. // rowGap: 1,
  371. },
  372. properties: {
  373. type: {
  374. 'x-decorator': 'FormItem',
  375. 'x-component': 'GroupNameControl',
  376. 'x-decorator-props': {
  377. gridSpan: 3,
  378. },
  379. default: 'or',
  380. 'x-component-props': {
  381. name: name,
  382. },
  383. 'x-visible': !expand,
  384. },
  385. column: {
  386. type: 'string',
  387. 'x-decorator': 'FormItem',
  388. 'x-component': 'Select',
  389. 'x-decorator-props': {
  390. gridSpan: 3,
  391. },
  392. 'x-component-props': {
  393. placeholder: '请选择',
  394. },
  395. enum: filterSearchTerm().map((i) => ({ label: i.title, value: i.dataIndex } as EnumData)),
  396. },
  397. termType: {
  398. type: 'enum',
  399. 'x-decorator': 'FormItem',
  400. 'x-component': 'Select',
  401. 'x-decorator-props': {
  402. gridSpan: 3,
  403. },
  404. default: 'like',
  405. enum: termType,
  406. },
  407. value: {
  408. 'x-decorator-props': {
  409. gridSpan: 6,
  410. },
  411. 'x-decorator': 'FormItem',
  412. 'x-component': 'Input',
  413. },
  414. },
  415. },
  416. });
  417. const schema: ISchema = {
  418. type: 'object',
  419. properties: {
  420. layout: {
  421. type: 'void',
  422. 'x-component': 'FormGrid',
  423. 'x-component-props': {
  424. minColumns: 9,
  425. maxColumns: 9,
  426. },
  427. properties: {
  428. terms1: createGroup('第一组'),
  429. type: {
  430. 'x-decorator': 'FormItem',
  431. 'x-component': 'Select',
  432. 'x-decorator-props': {
  433. gridSpan: 1,
  434. style: {
  435. display: 'flex',
  436. alignItems: 'center',
  437. marginTop: '-22px',
  438. padding: '0 30px',
  439. },
  440. },
  441. default: 'and',
  442. enum: [
  443. { label: '并且', value: 'and' },
  444. { label: '或者', value: 'or' },
  445. ],
  446. },
  447. terms2: createGroup('第二组'),
  448. },
  449. },
  450. },
  451. };
  452. const handleForm = (_expand?: boolean) => {
  453. const value = form.values;
  454. const __expand = _expand !== undefined ? _expand : expand;
  455. // 第一组条件值
  456. const _terms1 = _.cloneDeep(value.terms1?.[0]);
  457. const uiParam = uiParamRef.current;
  458. // 判断一下条件。。是否展开。
  459. if (__expand) {
  460. value.terms1 = [
  461. uiParam?.[0]?.terms?.[0] || _terms1 || defaultTerms(0),
  462. uiParam?.[0]?.terms?.[1] || defaultTerms(1),
  463. uiParam?.[0]?.terms?.[2] || defaultTerms(2),
  464. ];
  465. value.terms2 = [
  466. uiParam?.[1]?.terms?.[0] || defaultTerms(3),
  467. uiParam?.[1]?.terms?.[1] || defaultTerms(4),
  468. uiParam?.[1]?.terms?.[2] || defaultTerms(5),
  469. ];
  470. } else {
  471. value.terms1 = [uiParam?.[0]?.terms?.[0] || _terms1 || defaultTerms(0)];
  472. value.terms2 = [];
  473. }
  474. setInitParams(value);
  475. };
  476. const handleExpand = () => {
  477. handleForm();
  478. setExpand(!expand);
  479. };
  480. useEffect(() => {
  481. // 1、一组条件时的表单值
  482. // 2、六组条件时的表单值
  483. // 3、拥有默认条件时的表单值
  484. // 合并初始化的值
  485. //expand false 6组条件 true 1组条件
  486. if (initParam && initParam[0].terms && initParam[0].terms.length > 1) {
  487. handleExpand();
  488. }
  489. }, [initParam]);
  490. const simpleSchema: ISchema = {
  491. type: 'object',
  492. properties: {
  493. terms1: createGroup('第一组'),
  494. },
  495. };
  496. const handleHistory = (item: SearchHistory) => {
  497. const log = JSON.parse(item.content) as SearchTermsUI;
  498. setLogVisible(false);
  499. uiParamRef.current = ui2Server(log);
  500. const _expand =
  501. !!(log.terms1 && log.terms1.length > 1) || !!(log.terms2 && log.terms2.length > 1);
  502. if (_expand) {
  503. setExpand(false);
  504. }
  505. handleForm(_expand);
  506. };
  507. const formatValue = (value: SearchTermsUI): SearchTermsServer => {
  508. let _value = ui2Server(value);
  509. // 处理默认查询参数
  510. if (defaultParam && defaultParam?.length > 0) {
  511. if ('terms' in defaultParam[0]) {
  512. _value = _value.concat(defaultParam as SearchTermsServer);
  513. } else if ('value' in defaultParam[0]) {
  514. _value = _value.concat([{ terms: defaultParam }]);
  515. }
  516. }
  517. return _value
  518. .filter((i) => i.terms && i.terms?.length > 0)
  519. .map((_term) => {
  520. _term.terms = _term.terms
  521. ?.filter((term) => term.value !== '')
  522. .map((item) => {
  523. if (item.termType === 'like' && item.value && item.value !== '') {
  524. item.value = `%${item.value}%`;
  525. return item;
  526. }
  527. if (item.termType === 'nlike' && item.value && item.value !== '') {
  528. item.value = `%${item.value}%`;
  529. return item;
  530. }
  531. return item;
  532. });
  533. return _term;
  534. });
  535. };
  536. const handleSearchValue = (
  537. data: SearchTermsServer,
  538. fields: ProColumns<T>[],
  539. ): SearchTermsServer => {
  540. return data.map((item) => {
  541. item.terms?.forEach((termsItem) => {
  542. const _fieldItem = fields.find((fieldItem) => fieldItem.dataIndex === termsItem.column);
  543. if (
  544. _fieldItem &&
  545. _fieldItem.search &&
  546. _fieldItem.search.transform &&
  547. _.isFunction(_fieldItem.search.transform)
  548. ) {
  549. termsItem.value = _fieldItem.search.transform(termsItem.value, '', '');
  550. }
  551. });
  552. return item;
  553. });
  554. };
  555. const handleSearch = async (type: boolean = true) => {
  556. const value = form.values;
  557. const filterTerms = (data: Partial<Term>[] | undefined) =>
  558. data && data.filter((item) => item.column != null).filter((item) => item.value !== undefined);
  559. const _terms = _.cloneDeep(value);
  560. _terms.terms1 = filterTerms(_terms.terms1);
  561. _terms.terms2 = filterTerms(_terms.terms2);
  562. const _temp = formatValue(_terms);
  563. uiParamRef.current = ui2Server(value);
  564. if (
  565. (_terms.terms1 && _terms.terms1.length > 1) ||
  566. (_terms.terms2 && _terms.terms2.length >= 1)
  567. ) {
  568. // 展开高级搜索
  569. setExpand(false);
  570. handleForm(true);
  571. } else {
  572. setExpand(true);
  573. handleForm(false);
  574. }
  575. const params = new URLSearchParams(_location.search);
  576. params.delete('q');
  577. params.delete('target');
  578. if (
  579. (value.terms1 && value.terms1.length && value.terms1?.some((item) => item.value)) ||
  580. (value.terms2 && value.terms2.length && value.terms2?.some((item) => item.value))
  581. ) {
  582. if (type) {
  583. params.append('q', JSON.stringify(value));
  584. if (props.target) {
  585. params.append('target', props.target);
  586. }
  587. _history.push({
  588. hash: _location.hash,
  589. search: '?' + params.toString(),
  590. });
  591. // setUrl({ q: JSON.stringify(value), target: props.target });
  592. }
  593. } else {
  594. _history.push({
  595. hash: _location.hash,
  596. search: '?' + params.toString(),
  597. });
  598. }
  599. const newTemp = handleSearchValue(_.cloneDeep(_temp), props.field);
  600. onSearch({ terms: newTemp });
  601. };
  602. const handleLocation = async (l: any, tar?: string) => {
  603. // 防止页面下多个TabsTabPane中的查询组件共享路由中的参数
  604. const params = new URLSearchParams(l.search);
  605. const q = params.get('q');
  606. const _target = params.get('target');
  607. const value = await form.submit<SearchTermsUI>();
  608. if (q && props.model !== 'simple' && value && !value.terms1?.[0].value && !value.terms2) {
  609. // 表单有值的情况下,不改变表单
  610. if (_target && tar && _target === tar) {
  611. const _qJson: any = JSON.parse(q);
  612. form.setInitialValues(_qJson);
  613. handleSearch(false);
  614. return;
  615. }
  616. // form.setInitialValues(JSON.parse(q));
  617. // handleSearch(false);
  618. }
  619. };
  620. useEffect(() => {
  621. handleLocation(_location, props.target);
  622. }, [_location, props.target]);
  623. useEffect(() => {
  624. if (defaultParam) {
  625. handleSearch(!(props.model === 'simple'));
  626. }
  627. }, []);
  628. const historyDom = (
  629. <Menu className={styles.history}>
  630. {history.length > 0 ? (
  631. history.map((item: SearchHistory) => (
  632. <Menu.Item key={item.id || randomString(9)}>
  633. <div className={styles.list}>
  634. <Typography.Text
  635. className={styles['list-text']}
  636. ellipsis={{ tooltip: item.name }}
  637. onClick={(e) => {
  638. e.stopPropagation();
  639. handleHistory(item);
  640. handleSearch();
  641. }}
  642. >
  643. {item.name}
  644. </Typography.Text>
  645. <Popconfirm
  646. title="确定删除嘛"
  647. onConfirm={async (e) => {
  648. e?.stopPropagation();
  649. const response = await service.history.remove(`${target}-search`, item.key);
  650. if (response.status === 200) {
  651. onlyMessage('操作成功');
  652. const temp = history.filter((h: any) => h.key !== item.key);
  653. setHistory(temp);
  654. }
  655. }}
  656. >
  657. <DeleteOutlined />
  658. </Popconfirm>
  659. </div>
  660. </Menu.Item>
  661. ))
  662. ) : (
  663. <Menu.Item>
  664. <div
  665. style={{
  666. display: 'flex',
  667. justifyContent: 'center',
  668. alignItems: 'center',
  669. width: '148px',
  670. }}
  671. >
  672. <Empty />
  673. </div>
  674. </Menu.Item>
  675. )}
  676. </Menu>
  677. );
  678. const handleSaveLog = async () => {
  679. const value = await form.submit<SearchTermsUI>();
  680. const value2 = await historyForm.submit<{ alias: string }>();
  681. const response = await service.history.save(`${target}-search`, {
  682. name: value2.alias,
  683. content: JSON.stringify(value),
  684. });
  685. if (response.status === 200) {
  686. onlyMessage('保存成功!');
  687. } else {
  688. onlyMessage('保存失败', 'error');
  689. }
  690. setAliasVisible(!aliasVisible);
  691. };
  692. const resetForm = async (type: boolean) => {
  693. const value = form.values;
  694. if (!expand) {
  695. value.terms1 = [defaultTerms(0), defaultTerms(1), defaultTerms(2)];
  696. value.terms2 = [defaultTerms(3), defaultTerms(4), defaultTerms(5)];
  697. } else {
  698. value.terms1 = [defaultTerms(0)];
  699. value.terms2 = [];
  700. }
  701. setInitParams(value);
  702. await handleSearch(type);
  703. };
  704. const SearchBtn = {
  705. simple: (
  706. <>
  707. {
  708. // @ts-ignore
  709. <Button
  710. icon={<SearchOutlined />}
  711. onClick={() => {
  712. handleSearch(false);
  713. }}
  714. type="primary"
  715. htmlType={'submit'}
  716. >
  717. 搜索
  718. </Button>
  719. }
  720. </>
  721. ),
  722. advance: (
  723. <Dropdown.Button
  724. icon={<SearchOutlined />}
  725. placement={'bottomLeft'}
  726. destroyPopupOnHide
  727. // @ts-ignore
  728. onClick={handleSearch}
  729. visible={logVisible}
  730. onVisibleChange={async (visible) => {
  731. setLogVisible(visible);
  732. if (visible) {
  733. await queryHistory();
  734. }
  735. }}
  736. type="primary"
  737. overlay={historyDom}
  738. overlayStyle={{ height: 350, overflow: 'auto' }}
  739. htmlType={'submit'}
  740. >
  741. 搜索
  742. </Dropdown.Button>
  743. ),
  744. };
  745. const SaveBtn = (
  746. <Popover
  747. content={
  748. <Form style={{ width: '217px' }} form={historyForm}>
  749. <SchemaField
  750. schema={{
  751. type: 'object',
  752. properties: {
  753. alias: {
  754. 'x-decorator': 'FormItem',
  755. 'x-component': 'Input.TextArea',
  756. 'x-validator': [
  757. {
  758. max: 64,
  759. message: '最多可输入64个字符',
  760. },
  761. {
  762. required: true,
  763. message: '请输入名称',
  764. },
  765. ],
  766. },
  767. },
  768. }}
  769. />
  770. <Button onClick={handleSaveLog} type="primary" className={styles.saveLog}>
  771. 保存
  772. </Button>
  773. </Form>
  774. }
  775. visible={aliasVisible}
  776. onVisibleChange={setAliasVisible}
  777. title="搜索名称"
  778. trigger="click"
  779. >
  780. <Button icon={<SaveOutlined />} block>
  781. {intl.formatMessage({
  782. id: 'pages.data.option.save',
  783. defaultMessage: '保存',
  784. })}
  785. </Button>
  786. </Popover>
  787. );
  788. return (
  789. <Card
  790. bordered={false}
  791. className={styles.container}
  792. style={props.style}
  793. bodyStyle={props.bodyStyle}
  794. >
  795. <Form
  796. form={form}
  797. className={styles.form}
  798. labelCol={4}
  799. wrapperCol={18}
  800. onAutoSubmit={() => (SearchBtn.advance ? handleSearch : handleSearch(false))}
  801. >
  802. <div
  803. className={classnames({
  804. [styles.simple]: expand,
  805. [styles['small-size']]:
  806. formDivSize?.width && expand && model !== 'simple' && formDivSize.width < 1000,
  807. })}
  808. ref={formDivRef}
  809. >
  810. <SchemaField schema={expand ? simpleSchema : schema} />
  811. <div
  812. className={classnames(styles.action, {
  813. [styles['small-action']]:
  814. formDivSize?.width && expand && model !== 'simple' && formDivSize.width < 1000,
  815. })}
  816. style={{ marginTop: expand ? 0 : -12 }}
  817. >
  818. <Space>
  819. {enableSave ? SearchBtn.advance : SearchBtn.simple}
  820. {enableSave && SaveBtn}
  821. <Button
  822. icon={<ReloadOutlined />}
  823. block
  824. onClick={() => {
  825. resetForm(model !== 'simple');
  826. }}
  827. >
  828. 重置
  829. </Button>
  830. </Space>
  831. {model !== 'simple' && (
  832. <div className={classnames(styles.more, !expand ? styles.simple : styles.advance)}>
  833. <Button type="link" onClick={handleExpand}>
  834. 更多筛选
  835. <DoubleRightOutlined style={{ marginLeft: 32 }} rotate={expand ? 90 : -90} />
  836. </Button>
  837. </div>
  838. )}
  839. </div>
  840. </div>
  841. </Form>
  842. </Card>
  843. );
  844. };
  845. export default SearchComponent;