概述

项目中经常会遇到在表格中展示图片的需求(比如展示用户信息时, 有一列是用户的头像).

antd pro table 的功能很强大, 对于常规的信息展示只需参照示例配置 column 就可以了. 但是对于文件(比如图片) 在表格中的展示, 介绍并不多.

下面通过示例来演示 antd pro table 中图片的上传和展示.

示例代码

前端主要包含如下 2 部分:

列表页面: 通过 antd pro table 显示数据信息

表单页面: 新建/修改数据的页面, 上传图片的功能就在其中

一个模块主要包含如下几个文件:

teacher.jsx: 显示数据列表信息

teacher-form.jsx: 用于添加/修改数据

model.js: list.jsx 和 form.jsx 之间共享数据

service.js: 访问后端的 API

下面的例子是实际项目中的一个简单的模块, 完成教师信息的 CURD, 教师的头像是图片文件

列表页面

1 import React, { useState, useRef } from 'react';

2 import { connect } from 'umi';

3 import { PageHeaderWrapper } from '@ant-design/pro-layout';

4 import { Button, Card, Modal, Space, Popconfirm, Form, message } from 'antd';

5 import { PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';

6 import ProTable from '@ant-design/pro-table';

7 import { queryAllTeacher, addTeacher, updateTeacher, deleteTeacher } from './service';

8 import { getDictDataByCatagory, getDownloadUrl } from '@/utils/common';

9 import TeacherForm from './teacher-form';

10

11 const Teacher = (props) => {

12 const { dicts, form, avatarFid } = props;

13 const [createModalVisible, handleModalVisible] = useState(false);

14

15 // preview state

16 const [previewVisible, handlePreviewVisible] = useState(false);

17 const [previewImageUrl, handlePreviewImageUrl] = useState('');

18

19 const [record, handleRecord] = useState(null);

20 const tableRef = useRef();

21

22 const previewAvatar = (record) => {

23 handlePreviewVisible(true);

24 if (record.avatar) handlePreviewImageUrl(getDownloadUrl(record.avatar));

25 else handlePreviewImageUrl('/nopic.jpg');

26 };

27

28 const teacherColumns = [

29 {

30 title: '头像图片',

31 dataIndex: 'avatar',

32 hideInSearch: true,

33 render: (_, record) => (

34 previewAvatar(record)}>

35 {record.avatar ? (

36

37 ) : (

38

39 )}

40

41 ),

42 },

43 {

44 title: '姓名',

45 dataIndex: 'login_name',

46 },

47 {

48 title: '性别',

49 dataIndex: 'sex',

50 hideInSearch: true,

51 },

52 {

53 title: '手机号',

54 dataIndex: 'mobile',

55 },

56 {

57 title: '身份证号码',

58 dataIndex: 'identity_card',

59 hideInSearch: true,

60 },

61 {

62 title: '个人简介',

63 dataIndex: 'comment',

64 ellipsis: true,

65 width: 300,

66 hideInSearch: true,

67 },

68 {

69 title: '来源类型',

70 dataIndex: 'teacher_source',

71 hideInSearch: true,

72 valueEnum: getDictDataByCatagory(dicts, 'teacher_source'),

73 },

74 {

75 title: '操作',

76 dataIndex: 'option',

77 valueType: 'option',

78 render: (_, record) => (

79

80

81 type="primary"

82 size="small"

83 onClick={() => {

84 handleRecord(record);

85 // 设置avatar数据

86 let avatarUrl = '/nopic.jpg';

87

88 if (record.avatar) avatarUrl = getDownloadUrl(record.avatar);

89

90 record.avatarFile = [

91 {

92 uid: '1',

93 name: 'avatar',

94 status: 'done',

95 url: avatarUrl,

96 },

97 ];

98 handleModalVisible(true);

99 }}

100 >

101 修改

102

103

104 placement="topRight"

105 title="是否删除?"

106 okText="Yes"

107 cancelText="No"

108 onConfirm={async () => {

109 const response = await deleteTeacher(record.id);

110 if (response.code === 10000) message.info('教师: [' + record.login_name + '] 已删除');

111 else

112 message.warn('教师: [' + record.login_name + '] 有关联的课程和班级信息, 无法删除');

113 tableRef.current.reload();

114 }}

115 >

116

117 删除

118

119

120

121 ),

122 },

123 ];

124

125 const okHandle = async () => {

126 const fieldsValue = await form.validateFields();

127 // handleAdd(fieldsValue);

128 console.log(fieldsValue);

129 fieldsValue.avatar = avatarFid;

130 const response = record

131 ? await updateTeacher(record.id, fieldsValue)

132 : await addTeacher(fieldsValue);

133

134 if (response.code !== 10000) {

135 if (

136 response.message.indexOf('Uniqueness violation') >= 0 &&

137 response.message.indexOf('teacher_mobile_key') >= 0

138 )

139 message.error('教师创建失败, 当前手机号已经存在');

140 }

141

142 if (response.code === 10000) {

143 handleModalVisible(false);

144 tableRef.current.reload();

145 }

146 };

147

148 return (

149

150

151

152 headerTitle="教师列表"

153 actionRef={tableRef}

154 rowKey="id"

155 toolBarRender={(action, { selectedRows }) => [

156

157 icon={}

158 type="primary"

159 onClick={() => {

160 handleRecord(null);

161 handleModalVisible(true);

162 }}

163 >

164 新建

165

,

166 ]}

167 request={async (params) => {

168 const response = await queryAllTeacher(params);

169 return {

170 data: response.data.teacher,

171 total: response.data.teacher_aggregate.aggregate.count,

172 };

173 }}

174 columns={teacherColumns}

175 />

176

177 destroyOnClose

178 forceRender

179 title="教师信息"

180 visible={createModalVisible}

181 onOk={okHandle}

182 onCancel={() => handleModalVisible(false)}

183 >

184

185

186

187 visible={previewVisible}

188 title={'用户头像'}

189 footer={null}

190 onCancel={() => handlePreviewVisible(false)}

191 >

192

193

194

195

196 );

197 };

198

199 export default connect(({ dict, teacher }) => ({

200 dicts: dict.dicts,

201 form: teacher.form,

202 avatarFid: teacher.avatarFid,

203 }))(Teacher);

form 页面

1 import React, { useState, useEffect } from 'react';

2 import _ from 'lodash';

3 import { connect } from 'umi';

4 import { formLayout } from '@/utils/common';

5 import { Form, Select, Input, Upload, Modal } from 'antd';

6 import { PlusOutlined, LoadingOutlined } from '@ant-design/icons';

7 import { upload } from '@/services/file';

8

9 const FormItem = Form.Item;

10 const { Option } = Select;

11 const { TextArea } = Input;

12

13 const TeacherForm = (props) => {

14 const { dispatch, dicts, record } = props;

15 const sexes = ['男', '女'];

16 const [fileList, handleFileList] = useState([]);

17 const [loading, handleLoading] = useState(false);

18 const [previewVisible, handlePreviewVisible] = useState(false);

19 const [previewTitle, handlePreviewTitle] = useState('');

20 const [previewImageUrl, handlePreviewImageUrl] = useState('');

21

22 const [form] = Form.useForm();

23 useEffect(() => {

24 if (form) {

25 form.resetFields();

26 dispatch({ type: 'teacher/setForm', payload: form });

27 }

28

29 // 初始化avatar

30 if (record && record.avatarFile) handleFileList(record.avatarFile);

31

32 if (record) dispatch({ type: 'teacher/setAvatarFid', payload: record.avatar });

33 else dispatch({ type: 'teacher/setAvatarFid', payload: '' });

34 }, []);

35

36 const handleChange = async ({ file, fileList }) => {

37 handleFileList(fileList);

38 if (file.status === 'uploading') handleLoading(true);

39 if (file.status === 'done') handleLoading(false);

40 };

41

42 const uploadButton = (

43

44 {loading ? : }

45

上传照片

46

47 );

48

49 const uploadAvatar = async ({ onSuccess, onError, file }) => {

50 const response = await upload('avatar', file);

51 try {

52 const {

53 code,

54 data: { fid },

55 } = response;

56

57 onSuccess(response, file);

58

59 dispatch({ type: 'teacher/setAvatarFid', payload: fid });

60 } catch (e) {

61 onError(e);

62 }

63 };

64

65 const previewImage = async (file) => {

66 handlePreviewVisible(true);

67 handlePreviewTitle(file.name);

68 let src = file.url;

69 if (!src) {

70 src = await new Promise((resolve) => {

71 const reader = new FileReader();

72 reader.readAsDataURL(file.originFileObj);

73 reader.onload = () => resolve(reader.result);

74 });

75 }

76 handlePreviewImageUrl(src);

77 };

78

79 const removeImage = () => {

80 handleFileList([]);

81 dispatch({ type: 'teacher/setAvatarFid', payload: '' });

82 };

83

84 const normFile = (e) => {

85 if (Array.isArray(e)) {

86 return e;

87 }

88 return e && e.fileList;

89 };

90

91 const uploadProps = {

92 name: 'avatar',

93 listType: 'picture-card',

94 className: 'avatar-uploader',

95 customRequest: uploadAvatar,

96 onPreview: previewImage,

97 onRemove: removeImage,

98 fileList: fileList,

99 };

100

101 return (

102

103

104

105 label="来源类型"

106 name="teacher_source"

107 rules={[

108 {

109 required: true,

110 },

111 ]}

112 >

113

114 style={{

115 width: '100%',

116 }}

117 >

118 {_.filter(dicts, (d) => d.catagory === 'teacher_source').map((r) => (

119

120 {r.val}

121

122 ))}

123

124

125

126 label="姓名"

127 name="login_name"

128 rules={[

129 {

130 required: true,

131 },

132 ]}

133 >

134

135

136

137 label="性别"

138 name="sex"

139 rules={[

140 {

141 required: true,

142 },

143 ]}

144 >

145

146 style={{

147 width: '100%',

148 }}

149 >

150 {sexes.map((r) => (

151

152 {r}

153

154 ))}

155

156

157

158 label="手机号"

159 name="mobile"

160 rules={[

161 {

162 pattern: new RegExp(/^1[3-9]\d{9}$/, 'g'),

163 message: '手机号格式不正确',

164 },

165 ]}

166 >

167

168

169

170

171

172

173

174

175

176 label="用户头像"

177 name="avatarFile"

178 valuePropName="fileList"

179 getValueFromEvent={normFile}

180 >

181

182 {fileList.length >= 1 ? null : uploadButton}

183

184

185

186

187 visible={previewVisible}

188 title={previewTitle}

189 footer={null}

190 onCancel={() => handlePreviewVisible(false)}

191 >

192

193

194

195 );

196 };

197

198 export default connect(({ dict }) => ({

199 dicts: dict.dicts,

200 }))(TeacherForm);

model.js

1 import { message } from 'antd';

2

3 const Model = {

4 namespace: 'teacher',

5 state: {

6 form: null,

7 avatarFid: '',

8 },

9

10 effects: {},

11 reducers: {

12 setForm(state, { payload }) {

13 return {

14 ...state,

15 form: payload,

16 };

17 },

18 setAvatarFid(state, { payload }) {

19 return {

20 ...state,

21 avatarFid: payload,

22 };

23 },

24 },

25 };

26 export default Model;

service.js

1 import { graphql } from '@/services/graphql_client';

2 import md5 from 'md5';

3 import moment from 'moment';

4

5 const gqlQueryAll = `

6 query search_teacher($login_name: String, $mobile: String, $limit: Int!, $offset: Int!) {

7 teacher(order_by: {updated_at: desc}, limit: $limit, offset: $offset, where: {login_name: {_ilike: $login_name}, mobile: {_ilike: $mobile}}) {

8 id

9 avatar

10 comment

11 identity_card

12 login_name

13 mobile

14 sex

15 teacher_source

16 }

17 teacher_aggregate(where: {login_name: {_ilike: $login_name}, mobile: {_ilike: $mobile}}) {

18 aggregate {

19 count

20 }

21 }

22 }

23 `;

24

25 const qplAddTeacher = `

26 mutation add_teacher($avatar: uuid, $comment: String, $identity_card: String, $login_name: String!, $mobile: String, $sex: String!, $teacher_source: String!, $password: String!){

27 insert_teacher_one(object: {avatar: $avatar, comment: $comment, identity_card: $identity_card, login_name: $login_name, mobile: $mobile, sex: $sex, teacher_source: $teacher_source, password: $password}) {

28 id

29 }

30 }

31 `;

32

33 const qplUpdateTeacher = `

34 mutation update_teacher($id: uuid!, $avatar: uuid, $comment: String, $identity_card: String, $login_name: String, $mobile: String, $sex: String, $teacher_source: String) {

35 update_teacher_by_pk(_set: {avatar: $avatar, comment: $comment, identity_card: $identity_card, login_name: $login_name, mobile: $mobile, sex: $sex, teacher_source: $teacher_source}, pk_columns: {id: $id}) {

36 id

37 }

38 }

39 `;

40

41 const qplDeleteTeacher = `

42 mutation del_teacher($id: uuid!){

43 delete_teacher_by_pk(id: $id) {

44 id

45 }

46 }

47 `;

48

49 export async function queryAllTeacher(params) {

50 let qplVar = {

51 limit: params.pageSize,

52 offset: (params.current - 1) * params.pageSize,

53 };

54

55 if (params.login_name) qqlVar.login_name = '%' + params.login_name + '%';

56 if (params.mobile) qqlVar.mobile = '%' + params.mobile + '%';

57

58 return graphql(gqlQueryAll, qplVar);

59 }

60

61 export async function addTeacher(params) {

62 const { avatar, comment, identity_card, mobile, sex, login_name, teacher_source } = params;

63

64 let insertVar = { login_name, sex, mobile, teacher_source };

65 if (avatar !== '') insertVar.avatar = avatar;

66 if (identity_card) insertVar.identity_card = identity_card;

67 if (comment) insertVar.comment = comment;

68 if (mobile) {

69 insertVar.mobile = mobile;

70 insertVar.password = md5(mobile.slice(-6));

71 } else {

72 // default password

73 insertVar.password = md5('123456');

74 }

75

76 return graphql(qplAddTeacher, {

77 ...insertVar,

78 });

79 }

80

81 export async function updateTeacher(id, params) {

82 let { avatar, comment, identity_card, mobile, sex, login_name, teacher_source } = params;

83 if (avatar === '') avatar = null;

84 return graphql(qplUpdateTeacher, {

85 id,

86 avatar,

87 comment,

88 identity_card,

89 mobile,

90 sex,

91 login_name,

92 teacher_source,

93 });

94 }

95

96 export async function deleteTeacher(id) {

97 return graphql(qplDeleteTeacher, { id });

98 }

service.js 中的请求是 graphql api

总结

这个模块的 增和改 用的同一个页面, 因为是弹出的 modal, 所有实际的提交功能是在 teacher.jsx 中完成的

antd upload 组件的 外围 FormItem 需要加上如下属性(valuePropName 和 getValueFromEvent):

1

2 label="用户头像"

3 name="avatarFile"

4 valuePropName="fileList"

5 getValueFromEvent={normFile}

6 >

7

8

antd upload 组件虽然有默认的上传事件, 但是如果自定义上传的事件, 可以更方便的和自己的后端 API 进行对接

1 const uploadAvatar = async ({ onSuccess, onError, file }) => {

2 const response = await upload('avatar', file);

3 try {

4 const {

5 code,

6 data: { fid },

7 } = response;

8

9 onSuccess(response, file);

10

11 dispatch({ type: 'teacher/setAvatarFid', payload: fid });

12 } catch (e) {

13 onError(e);

14 }

15 };

antd pro mysql_antd pro table中的文件上传相关推荐

  1. Asp.net MVC 1.0 RTM中实现文件上传

    Asp.net MVC 1.0 RTM中实现文件上传 在我们开始之前,你需要知道一个form以post方式上传文件的方式,你将要增加一个特别的enctype attribute到form标签上,为了这 ...

  2. IIS 6和IIS 7 中设置文件上传大小限制设置方法,两者是不一样的

    在IIS 6.0中设置文件上传大小的方法,只要设置httpRuntime就可以了 <system.web><httpRuntime executionTimeout="36 ...

  3. 在Struts 2中实现文件上传

    前一阵子有些朋友在电子邮件中问关于Struts 2实现文件上传的问题, 所以今天我们就来讨论一下这个问题. 实现原理 Struts 2是通过Commons FileUpload文件上传.Commons ...

  4. java语言 文件上传,java中实现文件上传的方法

    java中实现文件上传的方法 发布时间:2020-06-19 10:29:11 来源:亿速云 阅读:86 作者:Leah 这篇文章给大家分享的是java中实现文件上传的方法,相信大部分人都还没学会这个 ...

  5. jsp servlet示例_Servlet和JSP中的文件上传示例

    jsp servlet示例 使用Servlet和JSP将文件上传到服务器是Java Web应用程序中的常见任务. 在对Servlet或JSP进行编码以处理文件上传请求之前,您需要了解一点有关HTML和 ...

  6. Servlet和JSP中的文件上传示例

    使用Servlet和JSP将文件上传到服务器是Java Web应用程序中的常见任务. 在对Servlet或JSP进行编码以处理文件上传请求之前,您需要了解一点有关HTML和HTTP协议中文件上传支持的 ...

  7. tomcat temp 大量 upload 文件_问题:JavaWeb中实现文件上传的方式有哪些?

    问题:JavaWeb中实现文件上传的方式有哪些? 上回我们说了下文件下载的方式有哪些,这次我们从不同的环境下简单来说说文件上传的方式有哪些. 文件上传的方式 Servlet2.5 方式 Servlet ...

  8. 在Struts2中实现文件上传(二)

    发布运行应用程序,在浏览器地址栏中键入:http://localhost:8080/Struts2_Fileupload/FileUpload.jsp,出现图示页面:   清单7 FileUpload ...

  9. (转)SpringMVC学习(九)——SpringMVC中实现文件上传

    http://blog.csdn.net/yerenyuan_pku/article/details/72511975 这一篇博文主要来总结下SpringMVC中实现文件上传的步骤.但这里我只讲单个文 ...

最新文章

  1. Servlet3.0 Test
  2. 06/05/2015
  3. SAP Business One和SAP All-in-One
  4. python的userlist_Python Collections.UserList用法及代码示例
  5. 排序算法之希尔排序(Java)
  6. java后端面试笔记-自用
  7. websocket握手失败_WebSocket握手期间出错:意外的响应代码:500
  8. 58 同城 iOS 客户端搜索模块组件化实践
  9. MVC3+EF4.1学习系列(八)-----利用Repository and Unit of Wor
  10. 加密与解密 调试篇(一)
  11. Js返回顶部实例代码
  12. 【新手上路常见问答】关于物联网设计
  13. Php图片验证码显示不出来的解决过程
  14. android 373dpi对应的布局,[荣耀6X BLN-AL10] EMUI5.0 B373 自定义DPI 来电闪光 接听 录音 核心控制 性能调节 游戏模式 稳定精简顺畅等...
  15. 图像处理(8)–灰度变换函数增强空间域图像
  16. Carte作为Windows服务
  17. Personal Information
  18. 倒水问题题解(勿喷)
  19. 分享给你——2017我学到的方法论
  20. 留学生把“中国牛排”臭豆腐带到国外,18家连锁店开遍澳洲

热门文章

  1. arduino nano 蓝牙_Arduino使用HC05蓝牙模块与手机连接
  2. 防爆仪表管阀件公司网站模板源码+Eyou内核的
  3. Intellij java开发里里一些参数前有 ”s:“或者“o:” 字样
  4. 利用Mycat分库分表操作
  5. mycat分库分表配置
  6. MICCAI 2020 | 基于3D监督预训练的全身病灶检测SOTA(预训练代码和模型已公开)...
  7. python 文件路径切分
  8. UI自动化_python+selenium京东前台购物流程
  9. OpenResty介绍和CentOS6.5离线安装Openresty详细步骤
  10. ARM:NVIC VIC GIC SCB