index.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. import { message, TreeSelect as ATreeSelect } from 'antd';
  2. import { useIntl } from 'umi';
  3. import type { Field } from '@formily/core';
  4. import { createForm } from '@formily/core';
  5. import { createSchemaField } from '@formily/react';
  6. import React, { useEffect, useState } from 'react';
  7. import * as ICONS from '@ant-design/icons';
  8. import { PlusOutlined } from '@ant-design/icons';
  9. import {
  10. Form,
  11. FormGrid,
  12. FormItem,
  13. Input,
  14. Password,
  15. Select,
  16. Switch,
  17. TreeSelect,
  18. } from '@formily/antd';
  19. import type { ISchema } from '@formily/json-schema';
  20. import { action } from '@formily/reactive';
  21. import type { Response } from '@/utils/typings';
  22. import { service } from '@/pages/system/User';
  23. import { Modal, PermissionButton } from '@/components';
  24. import usePermissions from '@/hooks/permission';
  25. interface Props {
  26. model: 'add' | 'edit' | 'query';
  27. data: Partial<UserItem>;
  28. close: () => void;
  29. }
  30. const Save = (props: Props) => {
  31. const { model } = props;
  32. const intl = useIntl();
  33. const { permission: deptPermission } = usePermissions('system/Department');
  34. const { permission: rolePermission } = usePermissions('system/Role');
  35. const [data, setData] = useState<Partial<UserItem>>(props.data);
  36. const getRole = () => service.queryRoleList();
  37. const getOrg = () => service.queryOrgList();
  38. const useAsyncDataSource = (api: any) => (field: Field) => {
  39. field.loading = true;
  40. api(field).then(
  41. action.bound!((resp: Response<any>) => {
  42. field.dataSource = resp.result?.map((item: Record<string, unknown>) => ({
  43. ...item,
  44. label: item.name,
  45. value: item.id,
  46. }));
  47. field.loading = false;
  48. }),
  49. );
  50. };
  51. const getUser = async () => {
  52. if (props.data.id) {
  53. const response: Response<UserItem> = await service.queryDetail(props.data?.id);
  54. if (response.status === 200) {
  55. const temp = response.result as UserItem;
  56. temp.orgIdList = (temp.orgList as { id: string; name: string }[]).map((item) => item.id);
  57. temp.roleIdList = (temp.roleList as { id: string; name: string }[]).map((item) => item.id);
  58. setData(temp);
  59. }
  60. }
  61. };
  62. useEffect(() => {
  63. if (model === 'edit') {
  64. getUser();
  65. } else {
  66. setData({});
  67. }
  68. }, [props.data, props.model]);
  69. const form = createForm({
  70. validateFirst: true,
  71. initialValues: data,
  72. });
  73. const SchemaField = createSchemaField({
  74. components: {
  75. FormItem,
  76. Input,
  77. Password,
  78. Switch,
  79. Select,
  80. TreeSelect,
  81. FormGrid,
  82. },
  83. scope: {
  84. icon(name: any) {
  85. return React.createElement(ICONS[name]);
  86. },
  87. },
  88. });
  89. const schema: ISchema = {
  90. type: 'object',
  91. properties: {
  92. layout: {
  93. type: 'void',
  94. 'x-decorator': 'FormGrid',
  95. 'x-decorator-props': {
  96. maxColumns: 2,
  97. minColumns: 2,
  98. columnGap: 24,
  99. },
  100. properties: {
  101. name: {
  102. title: intl.formatMessage({
  103. id: 'pages.system.name',
  104. defaultMessage: '姓名',
  105. }),
  106. type: 'string',
  107. 'x-decorator': 'FormItem',
  108. 'x-component': 'Input',
  109. 'x-decorator-props': {
  110. gridSpan: 1,
  111. },
  112. 'x-component-props': {
  113. placeholder: '请输入姓名',
  114. },
  115. name: 'name',
  116. 'x-validator': [
  117. {
  118. max: 64,
  119. message: '最多可输入64个字符',
  120. },
  121. {
  122. required: true,
  123. message: '请输入姓名',
  124. },
  125. ],
  126. // required: true,
  127. },
  128. username: {
  129. title: intl.formatMessage({
  130. id: 'pages.system.username',
  131. defaultMessage: '用户名',
  132. }),
  133. 'x-decorator-props': {
  134. gridSpan: 1,
  135. },
  136. type: 'string',
  137. 'x-decorator': 'FormItem',
  138. 'x-component': 'Input',
  139. 'x-component-props': {
  140. disabled: model === 'edit',
  141. placeholder: '请输入用户名',
  142. },
  143. 'x-validator': [
  144. {
  145. max: 64,
  146. message: '最多可输入64个字符',
  147. },
  148. {
  149. required: true,
  150. message: '请输入用户名',
  151. },
  152. {
  153. triggerType: 'onBlur',
  154. validator: (value: string) => {
  155. return new Promise((resolve) => {
  156. service
  157. .validateField('username', value)
  158. .then((resp) => {
  159. if (resp.status === 200) {
  160. if (resp.result.passed) {
  161. resolve('');
  162. } else {
  163. resolve(model === 'edit' ? '' : resp.result.reason);
  164. }
  165. }
  166. resolve('');
  167. })
  168. .catch(() => {
  169. return '验证失败!';
  170. });
  171. });
  172. },
  173. },
  174. ],
  175. name: 'username',
  176. required: true,
  177. },
  178. },
  179. },
  180. password: {
  181. type: 'string',
  182. title: intl.formatMessage({
  183. id: 'pages.system.password',
  184. defaultMessage: '密码',
  185. }),
  186. 'x-decorator': 'FormItem',
  187. 'x-component': 'Password',
  188. 'x-component-props': {
  189. checkStrength: true,
  190. placeholder: '请输入密码',
  191. },
  192. 'x-visible': model === 'add',
  193. 'x-reactions': [
  194. {
  195. dependencies: ['.confirmPassword'],
  196. fulfill: {
  197. state: {
  198. selfErrors:
  199. '{{$deps[0] && $self.value && $self.value !==$deps[0] ? "两次密码输入不一致" : ""}}',
  200. },
  201. },
  202. },
  203. ],
  204. name: 'password',
  205. 'x-validator': [
  206. {
  207. max: 128,
  208. message: '密码最多可输入128位',
  209. },
  210. {
  211. min: 8,
  212. message: '密码不能少于6位',
  213. },
  214. {
  215. required: model === 'add',
  216. message: '请输入密码',
  217. },
  218. ],
  219. },
  220. confirmPassword: {
  221. type: 'string',
  222. title: intl.formatMessage({
  223. id: 'pages.system.confirmPassword',
  224. defaultMessage: '确认密码?',
  225. }),
  226. 'x-decorator': 'FormItem',
  227. 'x-component': 'Password',
  228. 'x-component-props': {
  229. checkStrength: true,
  230. placeholder: '请再次输入密码',
  231. },
  232. 'x-visible': model === 'add',
  233. 'x-validator': [
  234. {
  235. max: 128,
  236. message: '密码最多可输入128位',
  237. },
  238. {
  239. min: 8,
  240. message: '密码不能少于6位',
  241. },
  242. {
  243. required: model === 'add',
  244. message: '请输入确认密码',
  245. },
  246. ],
  247. 'x-reactions': [
  248. {
  249. dependencies: ['.password'],
  250. fulfill: {
  251. state: {
  252. selfErrors:
  253. '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "两次密码输入不一致" : ""}}',
  254. },
  255. },
  256. },
  257. ],
  258. 'x-decorator-props': {},
  259. name: 'confirmPassword',
  260. },
  261. layout2: {
  262. type: 'void',
  263. 'x-decorator': 'FormGrid',
  264. 'x-decorator-props': {
  265. maxColumns: 2,
  266. minColumns: 2,
  267. columnGap: 24,
  268. },
  269. properties: {
  270. roleIdList: {
  271. title: '角色',
  272. 'x-decorator': 'FormItem',
  273. 'x-component': 'Select',
  274. 'x-component-props': {
  275. mode: 'multiple',
  276. showArrow: true,
  277. placeholder: '请选择角色',
  278. filterOption: (input: string, option: any) =>
  279. option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
  280. },
  281. 'x-reactions': ['{{useAsyncDataSource(getRole)}}'],
  282. 'x-decorator-props': {
  283. gridSpan: 1,
  284. addonAfter: (
  285. <PermissionButton
  286. type="link"
  287. style={{ padding: 0 }}
  288. isPermission={rolePermission.add}
  289. onClick={() => {
  290. const tab: any = window.open(`${origin}/#/system/role?save=true`);
  291. tab!.onTabSaveSuccess = (value: any) => {
  292. form.setFieldState('roleIdList', async (state) => {
  293. state.dataSource = await getRole().then((resp) =>
  294. resp.result?.map((item: Record<string, unknown>) => ({
  295. ...item,
  296. label: item.name,
  297. value: item.id,
  298. })),
  299. );
  300. state.value = [...(state.value || []), value.id];
  301. });
  302. };
  303. }}
  304. >
  305. <PlusOutlined />
  306. </PermissionButton>
  307. ),
  308. },
  309. },
  310. orgIdList: {
  311. title: '部门',
  312. 'x-decorator': 'FormItem',
  313. 'x-component': 'TreeSelect',
  314. 'x-component-props': {
  315. multiple: true,
  316. showArrow: true,
  317. placeholder: '请选择角色',
  318. showCheckedStrategy: ATreeSelect.SHOW_ALL,
  319. filterOption: (input: string, option: any) =>
  320. option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
  321. fieldNames: {
  322. label: 'name',
  323. value: 'id',
  324. },
  325. treeNodeFilterProp: 'name',
  326. },
  327. 'x-decorator-props': {
  328. gridSpan: 1,
  329. addonAfter: (
  330. <PermissionButton
  331. type="link"
  332. style={{ padding: 0 }}
  333. isPermission={deptPermission.add}
  334. onClick={() => {
  335. const tab: any = window.open(`${origin}/#/system/department?save=true`);
  336. tab!.onTabSaveSuccess = (value: any) => {
  337. form.setFieldState('orgIdList', async (state) => {
  338. state.dataSource = await getOrg().then((resp) =>
  339. resp.result?.map((item: Record<string, unknown>) => ({
  340. ...item,
  341. label: item.name,
  342. value: item.id,
  343. })),
  344. );
  345. state.value = [...(state.value || []), value.id];
  346. });
  347. };
  348. }}
  349. >
  350. <PlusOutlined />
  351. </PermissionButton>
  352. ),
  353. },
  354. 'x-reactions': ['{{useAsyncDataSource(getOrg)}}'],
  355. },
  356. telephone: {
  357. title: '手机号',
  358. 'x-decorator': 'FormItem',
  359. 'x-component': 'Input',
  360. 'x-decorator-props': {
  361. gridSpan: 1,
  362. },
  363. },
  364. email: {
  365. title: '邮箱',
  366. 'x-decorator': 'FormItem',
  367. 'x-component': 'Input',
  368. 'x-decorator-props': {
  369. gridSpan: 1,
  370. },
  371. },
  372. },
  373. },
  374. },
  375. };
  376. const save = async () => {
  377. const value = await form.submit<UserItem>();
  378. const temp: any = {};
  379. temp.id = value.id;
  380. temp.user = value;
  381. temp.orgIdList = value.orgIdList;
  382. temp.roleIdList = value.roleIdList;
  383. const response = await service.saveUser(temp, model);
  384. if (response.status === 200) {
  385. message.success(
  386. intl.formatMessage({
  387. id: 'pages.data.option.success',
  388. defaultMessage: '操作成功',
  389. }),
  390. );
  391. props.close();
  392. } else {
  393. message.error('操作失败!');
  394. }
  395. };
  396. return (
  397. <Modal
  398. title={intl.formatMessage({
  399. id: `pages.data.option.${model}`,
  400. defaultMessage: '编辑',
  401. })}
  402. maskClosable={false}
  403. visible={model !== 'query'}
  404. onCancel={props.close}
  405. onOk={save}
  406. width="35vw"
  407. permissionCode={'system/User'}
  408. permission={['add', 'edit']}
  409. >
  410. <Form form={form} layout="vertical">
  411. <SchemaField schema={schema} scope={{ useAsyncDataSource, getRole, getOrg }} />
  412. </Form>
  413. </Modal>
  414. );
  415. };
  416. export default Save;