antd pro mysql_antd pro table中的文件上传
概述
项目中经常会遇到在表格中展示图片的需求(比如展示用户信息时, 有一列是用户的头像).
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中的文件上传相关推荐
- Asp.net MVC 1.0 RTM中实现文件上传
Asp.net MVC 1.0 RTM中实现文件上传 在我们开始之前,你需要知道一个form以post方式上传文件的方式,你将要增加一个特别的enctype attribute到form标签上,为了这 ...
- IIS 6和IIS 7 中设置文件上传大小限制设置方法,两者是不一样的
在IIS 6.0中设置文件上传大小的方法,只要设置httpRuntime就可以了 <system.web><httpRuntime executionTimeout="36 ...
- 在Struts 2中实现文件上传
前一阵子有些朋友在电子邮件中问关于Struts 2实现文件上传的问题, 所以今天我们就来讨论一下这个问题. 实现原理 Struts 2是通过Commons FileUpload文件上传.Commons ...
- java语言 文件上传,java中实现文件上传的方法
java中实现文件上传的方法 发布时间:2020-06-19 10:29:11 来源:亿速云 阅读:86 作者:Leah 这篇文章给大家分享的是java中实现文件上传的方法,相信大部分人都还没学会这个 ...
- jsp servlet示例_Servlet和JSP中的文件上传示例
jsp servlet示例 使用Servlet和JSP将文件上传到服务器是Java Web应用程序中的常见任务. 在对Servlet或JSP进行编码以处理文件上传请求之前,您需要了解一点有关HTML和 ...
- Servlet和JSP中的文件上传示例
使用Servlet和JSP将文件上传到服务器是Java Web应用程序中的常见任务. 在对Servlet或JSP进行编码以处理文件上传请求之前,您需要了解一点有关HTML和HTTP协议中文件上传支持的 ...
- tomcat temp 大量 upload 文件_问题:JavaWeb中实现文件上传的方式有哪些?
问题:JavaWeb中实现文件上传的方式有哪些? 上回我们说了下文件下载的方式有哪些,这次我们从不同的环境下简单来说说文件上传的方式有哪些. 文件上传的方式 Servlet2.5 方式 Servlet ...
- 在Struts2中实现文件上传(二)
发布运行应用程序,在浏览器地址栏中键入:http://localhost:8080/Struts2_Fileupload/FileUpload.jsp,出现图示页面: 清单7 FileUpload ...
- (转)SpringMVC学习(九)——SpringMVC中实现文件上传
http://blog.csdn.net/yerenyuan_pku/article/details/72511975 这一篇博文主要来总结下SpringMVC中实现文件上传的步骤.但这里我只讲单个文 ...
最新文章
- Servlet3.0 Test
- 06/05/2015
- SAP Business One和SAP All-in-One
- python的userlist_Python Collections.UserList用法及代码示例
- 排序算法之希尔排序(Java)
- java后端面试笔记-自用
- websocket握手失败_WebSocket握手期间出错:意外的响应代码:500
- 58 同城 iOS 客户端搜索模块组件化实践
- MVC3+EF4.1学习系列(八)-----利用Repository and Unit of Wor
- 加密与解密 调试篇(一)
- Js返回顶部实例代码
- 【新手上路常见问答】关于物联网设计
- Php图片验证码显示不出来的解决过程
- android 373dpi对应的布局,[荣耀6X BLN-AL10] EMUI5.0 B373 自定义DPI 来电闪光 接听 录音 核心控制 性能调节 游戏模式 稳定精简顺畅等...
- 图像处理(8)–灰度变换函数增强空间域图像
- Carte作为Windows服务
- Personal Information
- 倒水问题题解(勿喷)
- 分享给你——2017我学到的方法论
- 留学生把“中国牛排”臭豆腐带到国外,18家连锁店开遍澳洲
热门文章
- arduino nano 蓝牙_Arduino使用HC05蓝牙模块与手机连接
- 防爆仪表管阀件公司网站模板源码+Eyou内核的
- Intellij java开发里里一些参数前有 ”s:“或者“o:” 字样
- 利用Mycat分库分表操作
- mycat分库分表配置
- MICCAI 2020 | 基于3D监督预训练的全身病灶检测SOTA(预训练代码和模型已公开)...
- python 文件路径切分
- UI自动化_python+selenium京东前台购物流程
- OpenResty介绍和CentOS6.5离线安装Openresty详细步骤
- ARM:NVIC VIC GIC SCB