7.1 用单选按钮选择性别

components/MemberDataForm.tsx中增加引用

  1. import { ProFormRadio } from '@ant-design/pro-form';

修改性别组件的定义

  1. - <ProFormSelect name="gender" label="性别" placeholder="选择性别" width={64}
  2. - options={getOptionsFormValueEnum(genderEnum)}
  3. - disabled={operation === 'create'}
  4. - allowClear={false}
  5. - />
  6. + <ProFormRadio.Group name="gender" label="性别"
  7. + radioType="button"
  8. + options={getOptionsFormValueEnum(genderEnum)}
  9. + disabled={operation === 'create'}
  10. + />

现在对话框变成如下的样子
image.png
更多的组件示例,参见官方范例:

7.2.1 下载并引用行政区划数据

户籍所在地通常使用民政部的行政区划代码到定义到区县一级。以往这些行政区划数据都是保存在数据库中,每次前端按需从后端动态请求。当网络质量较差的时候这种设计会给用户带来极差的体验。并且每次都向服务请求数据,给服务器增加了大量的不必要的负担。因此我们现在的设计都是把行政区划编码处理之后放在软件前端处理。点击下面的链接下载编码好的行政区划数据,解压缩后放到src/utils目录下。

行政区划数据: division-code.ts.zip
引从这个文件中引入两个函数

  1. import getDivisionOptions, { getDivisionArray } from '@/utils/division-code';
  2. import { Cascader } from 'antd';

7.2.2 联动选择行政区划

components/MemberDataForm.tsx的主数据form定义中增加Cascader组件(参见官方文档),和邮件地址放在一行

  1. <ProForm.Group>
  2. <ProForm.Item name="divisionCode" label="&nbsp;&nbsp;&nbsp;户&nbsp;&nbsp;籍&nbsp;&nbsp;地">
  3. <Cascader
  4. options={getDivisionOptions()}
  5. expandTrigger="hover"
  6. placeholder="点击选择行政区划"
  7. displayRender={(label) => label.length > 2
  8. ? label[label.length - 2] + '-' + label[label.length - 1]
  9. : label[label.length - 1]
  10. }
  11. />
  12. </ProForm.Item>
  13. <ProFormText name="email" label="&nbsp;&nbsp;&nbsp;邮件地址" placeholder="请输入e-maill地址"
  14. width="md"
  15. rules={fieldRuls['email']} />
  16. </ProForm.Group>

注意:Cascader是普通的antd组件而不是ProComponents组件(Ant Design官方公开表示尚未想好如何去做),因此需要把它用ProForm.Item组件包围起来。只有这样才能把他纳入统一的数据绑定。做完上述工作,我们在对话框中已经可以看到联动选择行政区划的效果了,但这还远远不够。联动组件的value是一个保存了多级路径数值的数组而不仅仅是最终的目标值。例如北京市朝阳区的编码是110105,而组件保存的数据为[11, 1101, 110105]。基于这个原因,我们在初始化、保存和重置等方面都有一些额外的编码工作。

7.2.3 保存前处理联动数据

修改onFinish函数,把多级联动结果值中的最后一个数字作为目标代码

  1. const onFinish = async (value:any) => {
  2. const {divisionCode, ...rest} = value
  3. const currentDivisionCodeValue = divisionCode[divisionCode.length - 1]
  4. if(dataChanged) {
  5. let result = {
  6. id: record.id,
  7. divisionCode: currentDivisionCodeValue,
  8. ...rest,
  9. }
  10. try {
  11. if(operation === 'edit')
  12. await updateMember(result);
  13. else {
  14. await addMember(result);
  15. }
  16. message.success('保存成功,即将刷新',1);
  17. handleResponse(true);
  18. } catch (error) {
  19. handleResponse(false);
  20. }
  21. } else {
  22. message.success('没有改动,直接退出了',1);
  23. handleResponse(false);
  24. }
  25. };

本节和接下来两节的代码都充分应用了扩展运算符…,这种方式直观、高效。如果对此还不熟悉的请尽快重新学习有关ES6的基础知识。

7.2.4 重置前合成路径数组

和保存数据的处理逻辑想法,重置联动组件的数值时,需要使用一个包含路径数值的数组。具体做法是是把当前行政区划代码按段分解后装入数组,我们在division-code中定义了一个函数getDivisionArray来完成这个工作。

  1. const resetFormDate = (theForm: FormInstance | undefined) => {
  2. if(operation == 'edit') {
  3. const {divisionCode, ...rest} = record
  4. const forData = {
  5. divisionCode: getDivisionArray(divisionCode),
  6. ...rest,
  7. }
  8. theForm?.setFieldsValue(forData);
  9. }
  10. else {
  11. theForm?.resetFields();
  12. }
  13. }

到这里就完成了用多级联动组件选择行政区划的基本功能
image.png

7.2.5 记住上一次的的选择

在实际的项目实践中,软件用户确实希望能够记住上一次创建新数据时类似行政区划这样的多级选择,因为他们往往是批量的添加同一个地方的人的数据。所以尽管此项的工作不是必要的环节,但我们还是设计这样一个功能,让软件能够记住上一次新增数据的操作中选择的行政区划。具体的,我们在向后端传递新创建数据的同时,把当前的行政区划组件值(数组)保存在浏览器本地存储空间(Local Storage)。每次因“新建”而打开对话框时,把存储的数组作为初始值设置给级联组件。

为简化操作并且展示框架的能力,我们使用UmiJS的useLocalStorageState来读写本地存储(具体用法参见官方文档)。

首先,安装UmiJS Hook

  1. $ tyarn add @umijs/hooks
  2. // 或
  3. $ cnpm install --save @umijs/hooks

然后引用useLocalStorageState

  1. import { useLocalStorageState } from '@umijs/hooks';

在DataForm里定义LocalStorageState Hook

  1. const [divisionCodeValue, saveDivisionCodeValue] = useLocalStorageState('memberData-lastDivision');

修改resetFormDate函数中操作类型为创建数据时的代码,显式设置多级组件的数值

  1. else {
  2. theForm?.resetFields();
  3. + theForm?.setFieldsValue({
  4. + divisionCode: divisionCodeValue
  5. + });
  6. }

修改onFinish函数中操作类型为创建数据时的代码,显式把多级组件的数值保存在本地存储

  1. if(operation === 'edit')
  2. await updateMember(result);
  3. else {
  4. await addMember(result);
  5. + saveDivisionCodeValue(divisionCode)
  6. }

7.3 详情页面显示全部的内容

7.3.1 删除年收入字段的相关定义

之前设置年收入字段是为了展示数据列表的自定义渲染的功能,在我们设计创建和更新的对话框时并没有用到它,现在我们彻底删除它的定义

services/type.d.ts

  1. declare namespace TYPE {
  2. type Member = {
  3. ...
  4. - salary?: number;
  5. + divisionCode?: number;

MemberList/index.tsx

  1. const columns: ProColumns<TYPE.Member>[] = [
  2. ...
  3. - {
  4. - title: '年收入',
  5. - dataIndex: 'salary',
  6. - align: 'right',
  7. - renderText: (val: number) =>
  8. - `${(val / 10000).toFixed(2)} 万元`,
  9. - },

mock/member.ts

  1. const genList = (current: number, pageSize: number) => {
  2. const memberListDataSource: TYPE.Member[] = [];
  3. for (let i = 0; i < pageSize; i += 1) {
  4. const index = (current - 1) * 10 + i;
  5. memberListDataSource.push({
  6. ...
  7. - salary: Math.floor(Math.random() * 100000000) / 100,

7.3.2 补充代码表定义

services/member.enum.ts

  1. export const genderEnum = {
  2. 0: '未选',
  3. 1: '男',
  4. 2: '女',
  5. }
  6. export const nationalityEnum = {
  7. 0: '汉族',
  8. 1: '满族',
  9. 2: '蒙古族',
  10. 3: '回族',
  11. 4: '藏族',
  12. 5: '维吾尔族',
  13. 6: '苗族',
  14. 7: '彝族',
  15. 8: '壮族',
  16. 9: '布依族',
  17. 10: '侗族',
  18. 11: '瑶族',
  19. 12: '白族',
  20. 13: '土家族',
  21. 14: '哈尼族',
  22. 15: '哈萨克族',
  23. 16: '傣族',
  24. 17: '黎族',
  25. 18: '傈僳族',
  26. 19: '佤族',
  27. 20: '畲族',
  28. 21: '高山族',
  29. 22: '拉祜族',
  30. 23: '水族',
  31. 24: '东乡族',
  32. 25: '纳西族',
  33. 26: '景颇族',
  34. 27: '柯尔克孜族',
  35. 28: '土族',
  36. 29: '达斡尔族',
  37. 30: '仫佬族',
  38. 31: '羌族',
  39. 32: '布朗族',
  40. 33: '撒拉族',
  41. 34: '毛南族',
  42. 35: '仡佬族',
  43. 36: '锡伯族',
  44. 37: '阿昌族',
  45. 38: '普米族',
  46. 39: '朝鲜族',
  47. 40: '塔吉克族',
  48. 41: '怒族',
  49. 42: '乌孜别克族',
  50. 43: '俄罗斯族',
  51. 44: '鄂温克族',
  52. 45: '德昂族',
  53. 46: '保安族',
  54. 47: '裕固族',
  55. 48: '京族',
  56. 49: '塔塔尔族',
  57. 50: '独龙族',
  58. 51: '鄂伦春族',
  59. 52: '赫哲族',
  60. 53: '门巴族',
  61. 54: '珞巴族',
  62. 55: '基诺族',
  63. }

7.3.3 补充列表定义

增加引用的内容

  1. -import { genderEnum, degreeEnum } from '@/services/member.enum';
  2. +import { genderEnum, degreeEnum, nationalityEnum, partyEnum,educationEnum } from '@/services/member.enum';
  3. +import { getDivisionName } from '@/utils/division-code';

MemberList/index.tsxcolumns: ProColumns<TYPE.Member>[] 的内容补充完整并且调整除姓名以外的定义和顺序

  1. {
  2. title: '身份证号',
  3. dataIndex: 'identityNumber',
  4. renderText: (val: string) =>
  5. `${val.substr(0,3)}***${val.substr(val.length-3,3)}`,
  6. },
  7. {
  8. title: '性别',
  9. dataIndex: 'gender',
  10. hideInForm: true,
  11. valueEnum: genderEnum,
  12. },
  13. {
  14. title: '出生日期',
  15. dataIndex: 'birthday',
  16. valueType: 'date',
  17. },
  18. {
  19. title: '民族',
  20. dataIndex: 'nationality',
  21. hideInTable: true,
  22. valueEnum: nationalityEnum,
  23. },
  24. {
  25. title: '手机号码',
  26. dataIndex: 'mobile',
  27. renderText: (val: string) =>
  28. `${val.substr(0,3)}***${val.substr(val.length-4,4)}`,
  29. },
  30. {
  31. title: '政党',
  32. dataIndex: 'party',
  33. hideInTable: true,
  34. valueEnum: partyEnum,
  35. },
  36. {
  37. title: '邮件地址',
  38. dataIndex: 'email',
  39. },
  40. {
  41. title: '学历',
  42. dataIndex: 'education',
  43. hideInTable: true,
  44. valueEnum: educationEnum,
  45. },
  46. {
  47. title: '最高学位',
  48. dataIndex: 'degree',
  49. hideInTable: true,
  50. valueEnum: degreeEnum,
  51. },
  52. {
  53. title: '户籍所在地',
  54. dataIndex: 'divisionCode',
  55. hideInTable: true,
  56. renderText: (val: number) =>
  57. getDivisionName(val),
  58. },

现在看到的详情页面应该是下面这样
image.png

版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。