index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. import type { ISchema } from '@formily/json-schema';
  2. import { createSchemaField } from '@formily/react';
  3. import {
  4. ArrayItems,
  5. Form,
  6. FormButtonGroup,
  7. FormGrid,
  8. FormItem,
  9. FormTab,
  10. Input,
  11. PreviewText,
  12. Select,
  13. } from '@formily/antd';
  14. import { createForm } from '@formily/core';
  15. import GroupNameControl from '@/components/SearchComponent/GroupNameControl';
  16. import { DeleteOutlined, DoubleRightOutlined } from '@ant-design/icons';
  17. import { Button, Dropdown, Input as AInput, Menu, message, Popconfirm, Popover } from 'antd';
  18. import { useState } from 'react';
  19. import type { ProColumns } from '@jetlinks/pro-table';
  20. import type { EnumData } from '@/utils/typings';
  21. import styles from './index.less';
  22. import Service from '@/components/SearchComponent/service';
  23. import _ from 'lodash';
  24. import { useIntl } from '@@/plugin-locale/localeExports';
  25. const ui2Server = (source: SearchTermsUI): SearchTermsServer => [
  26. { terms: source.terms1, type: source.type },
  27. { terms: source.terms2 },
  28. ];
  29. const server2Ui = (source: SearchTermsServer): SearchTermsUI => ({
  30. terms1: source[0].terms,
  31. terms2: source[1].terms,
  32. type: source[0].type || 'and',
  33. });
  34. interface Props<T> {
  35. field: ProColumns<T>[];
  36. onSearch: (params: any) => void;
  37. target?: string;
  38. }
  39. const termType = [
  40. { label: '=', value: 'eq' },
  41. { label: '!=', value: 'not' },
  42. { label: '包含', value: 'like' },
  43. { label: '不包含', value: 'not like' },
  44. { label: '>', value: 'gt' },
  45. { label: '>=', value: 'gte' },
  46. { label: '<', value: 'lt' },
  47. { label: '<=', value: 'lte' },
  48. { label: '属于', value: 'in' },
  49. { label: '不属于', value: 'not in' },
  50. ];
  51. const service = new Service();
  52. const defaultTerm = { termType: 'like' };
  53. const SearchComponent = <T extends Record<string, any>>({ field, onSearch, target }: Props<T>) => {
  54. const intl = useIntl();
  55. const [expand, setExpand] = useState<boolean>(true);
  56. const initForm = server2Ui([{ terms: [defaultTerm], type: 'and' }, { terms: [defaultTerm] }]);
  57. const [logVisible, setLogVisible] = useState<boolean>(false);
  58. const [alias, setAlias] = useState<string>('');
  59. const [aliasVisible, setAliasVisible] = useState<boolean>(false);
  60. const [initParams, setInitParams] = useState<SearchTermsUI>(initForm);
  61. const [history, setHistory] = useState([]);
  62. const form = createForm<SearchTermsUI>({
  63. validateFirst: true,
  64. initialValues: initParams,
  65. });
  66. const queryHistory = async () => {
  67. const response = await service.history.query(`${target}-search`);
  68. if (response.status === 200) {
  69. setHistory(response.result);
  70. }
  71. };
  72. const handleExpand = () => {
  73. const value = form.values;
  74. if (!expand) {
  75. value.terms1.splice(1, 2);
  76. value.terms2.splice(1, 2);
  77. } else {
  78. value.terms2.push(defaultTerm, defaultTerm);
  79. value.terms1.push(defaultTerm, defaultTerm);
  80. }
  81. setInitParams(value);
  82. setExpand(!expand);
  83. };
  84. const SchemaField = createSchemaField({
  85. components: {
  86. FormItem,
  87. FormTab,
  88. Input,
  89. Select,
  90. FormGrid,
  91. ArrayItems,
  92. PreviewText,
  93. GroupNameControl,
  94. },
  95. });
  96. const filterSearchTerm = (): EnumData[] =>
  97. field
  98. .filter((item) => item.dataIndex)
  99. .filter((item) => !['index', 'option'].includes(item.dataIndex as string))
  100. .map((i) => ({ label: i.title, value: i.dataIndex } as EnumData));
  101. const createGroup = (name: string): ISchema => ({
  102. 'x-decorator': 'FormItem',
  103. 'x-decorator-props': {
  104. gridSpan: 4,
  105. },
  106. 'x-component': 'ArrayItems',
  107. type: 'array',
  108. 'x-value': new Array(expand ? 1 : 3).fill({ termType: 'like' }),
  109. items: {
  110. type: 'object',
  111. 'x-component': 'FormGrid',
  112. 'x-component-props': {
  113. minColumns: 6,
  114. maxColumns: 6,
  115. },
  116. properties: {
  117. type: {
  118. 'x-decorator': 'FormItem',
  119. 'x-component': 'GroupNameControl',
  120. 'x-decorator-props': {
  121. gridSpan: 1,
  122. },
  123. 'x-component-props': {
  124. name: name,
  125. },
  126. },
  127. column: {
  128. type: 'string',
  129. 'x-decorator': 'FormItem',
  130. 'x-component': 'Select',
  131. 'x-decorator-props': {
  132. gridSpan: 2,
  133. },
  134. 'x-component-props': {
  135. placeholder: '请选择',
  136. },
  137. enum: filterSearchTerm(),
  138. },
  139. termType: {
  140. type: 'enum',
  141. 'x-decorator': 'FormItem',
  142. 'x-component': 'Select',
  143. 'x-decorator-props': {
  144. gridSpan: 1,
  145. },
  146. default: 'like',
  147. enum: termType,
  148. },
  149. value: {
  150. 'x-decorator-props': {
  151. gridSpan: 2,
  152. },
  153. 'x-decorator': 'FormItem',
  154. 'x-component': 'Input',
  155. },
  156. },
  157. },
  158. });
  159. const schema: ISchema = {
  160. type: 'object',
  161. properties: {
  162. layout: {
  163. type: 'void',
  164. 'x-component': 'FormGrid',
  165. 'x-component-props': {
  166. minColumns: 9,
  167. maxColumns: 9,
  168. },
  169. properties: {
  170. terms1: createGroup('第一组'),
  171. type: {
  172. 'x-decorator': 'FormItem',
  173. 'x-component': 'Select',
  174. 'x-decorator-props': {
  175. gridSpan: 1,
  176. style: {
  177. display: 'flex',
  178. alignItems: 'center',
  179. marginTop: '-22px',
  180. padding: '0 30px',
  181. },
  182. },
  183. default: 'and',
  184. enum: [
  185. { label: '并且', value: 'and' },
  186. { label: '或者', value: 'or' },
  187. ],
  188. },
  189. terms2: createGroup('第二组'),
  190. },
  191. },
  192. },
  193. };
  194. const handleHistory = (item: SearchHistory) => {
  195. const log = JSON.parse(item.content) as SearchTermsUI;
  196. form.setValues(log);
  197. setExpand(!(log.terms1?.length > 1 || log.terms2?.length > 1));
  198. setInitParams(log);
  199. };
  200. const historyDom = (
  201. <Menu>
  202. {history.map((item: SearchHistory) => (
  203. <Menu.Item onClick={() => handleHistory(item)} key={item.id}>
  204. <div
  205. style={{
  206. display: 'flex',
  207. justifyContent: 'space-between',
  208. alignItems: 'center',
  209. }}
  210. >
  211. <span style={{ marginRight: '5px' }}>{item.name}</span>
  212. <Popconfirm
  213. onConfirm={async () => {
  214. const response = await service.history.remove(`${target}-search`, item.key);
  215. if (response.status === 200) {
  216. message.success('操作成功');
  217. const temp = history.filter((h: any) => h.key !== item.key);
  218. setHistory(temp);
  219. }
  220. }}
  221. title={'确认删除吗?'}
  222. >
  223. <DeleteOutlined />
  224. </Popconfirm>
  225. </div>
  226. </Menu.Item>
  227. ))}
  228. </Menu>
  229. );
  230. const formatValue = (value: SearchTermsUI): SearchTermsServer =>
  231. ui2Server(value).map((term) => {
  232. term.terms.map((item) => {
  233. if (item.termType === 'like') {
  234. item.value = `%${item.value}%`;
  235. return item;
  236. }
  237. return item;
  238. });
  239. return term;
  240. });
  241. const handleSearch = async () => {
  242. const value = form.values;
  243. setInitParams(value);
  244. const filterTerms = (data: Partial<Term>[]) =>
  245. data.filter((item) => item.column != null).filter((item) => item.value);
  246. const temp = _.cloneDeep(value);
  247. temp.terms1 = filterTerms(temp.terms1);
  248. temp.terms2 = filterTerms(temp.terms2);
  249. onSearch(formatValue(temp));
  250. };
  251. const handleSaveLog = async () => {
  252. const value = await form.submit<SearchTermsUI>();
  253. const response = await service.history.save(`${target}-search`, {
  254. name: alias,
  255. content: JSON.stringify(value),
  256. });
  257. if (response.status === 200) {
  258. message.success('保存成功!');
  259. } else {
  260. message.error('保存失败');
  261. }
  262. setAliasVisible(!aliasVisible);
  263. };
  264. const resetForm = async () => {
  265. const temp = initParams;
  266. temp.terms1 = temp.terms1.map(() => defaultTerm);
  267. temp.terms2 = temp.terms2.map(() => defaultTerm);
  268. setInitParams(temp);
  269. await form.reset();
  270. };
  271. return (
  272. <div>
  273. <Form form={form} labelCol={4} wrapperCol={18}>
  274. <SchemaField schema={schema} />
  275. <div className={styles.action}>
  276. <FormButtonGroup.FormItem labelCol={10} wrapperCol={14}>
  277. <Dropdown.Button
  278. placement={'bottomLeft'}
  279. trigger={['click']}
  280. onClick={handleSearch}
  281. visible={logVisible}
  282. onVisibleChange={async (visible) => {
  283. setLogVisible(visible);
  284. if (visible) {
  285. await queryHistory();
  286. }
  287. }}
  288. type="primary"
  289. overlay={historyDom}
  290. >
  291. 搜索
  292. </Dropdown.Button>
  293. <Popover
  294. content={
  295. <>
  296. <AInput.TextArea
  297. rows={3}
  298. value={alias}
  299. onChange={(e) => setAlias(e.target.value)}
  300. />
  301. <Button onClick={handleSaveLog} type="primary" className={styles.saveLog}>
  302. 保存
  303. </Button>
  304. </>
  305. }
  306. visible={aliasVisible}
  307. onVisibleChange={(visible) => {
  308. setAlias('');
  309. setInitParams(form.values);
  310. setAliasVisible(visible);
  311. }}
  312. title="搜索名称"
  313. trigger="click"
  314. >
  315. <Button block>
  316. {intl.formatMessage({
  317. id: 'pages.data.option.save',
  318. defaultMessage: '保存',
  319. })}
  320. </Button>
  321. </Popover>
  322. <Button block onClick={resetForm}>
  323. 重置
  324. </Button>
  325. </FormButtonGroup.FormItem>
  326. <div>
  327. <DoubleRightOutlined
  328. onClick={handleExpand}
  329. style={{ fontSize: 20 }}
  330. rotate={expand ? 90 : -90}
  331. />
  332. </div>
  333. </div>
  334. </Form>
  335. </div>
  336. );
  337. };
  338. export default SearchComponent;