Skip to content

Commit c3c3bb3

Browse files
committed
dataSchema add showType=file
1 parent 2875cd4 commit c3c3bb3

File tree

7 files changed

+217
-58
lines changed

7 files changed

+217
-58
lines changed

src/components/DBTable/InnerTable.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ class InnerTable extends React.PureComponent {
210210
// 对于某些showType我会给个默认的render
211211
else if (field.showType === 'image') {
212212
col.render = this.renderImage;
213+
} else if (field.showType === 'file') {
214+
col.render = this.renderFile;
213215
}
214216
});
215217
return tableSchema;
@@ -250,6 +252,33 @@ class InnerTable extends React.PureComponent {
250252
cancelPreview = () => {
251253
this.setState({previewVisible: false});
252254
};
255+
256+
/**
257+
* 针对file字段的render方法
258+
*
259+
* @param text
260+
* @returns {*}
261+
*/
262+
renderFile = (text) => {
263+
if (Utils.isString(text) && text.length > 0) {
264+
// 单个文件, 显示为超链接
265+
return <a href={text} target="_blank">{text.substr(text.lastIndexOf('/') + 1)}</a>;
266+
} else if (text instanceof Array) {
267+
if (text.length === 0) {
268+
return null;
269+
}
270+
// 多个文件, 显示为一组超链接
271+
const urlArray = [];
272+
urlArray.push(<a key={0} href={text[0]} target="_blank">{text[0].substr(text[0].lastIndexOf('/') + 1)}</a>);
273+
for (let i = 1; i < text.length; i++) {
274+
urlArray.push(<br key={ -1 - i }/>);
275+
urlArray.push(<a key={i} href={text[i]} target="_blank">{text[i].substr(text[i].lastIndexOf('/') + 1)}</a>);
276+
}
277+
return <div>{urlArray}</div>
278+
} else {
279+
return text;
280+
}
281+
};
253282
/*END*/
254283

255284

@@ -445,7 +474,7 @@ class InnerTable extends React.PureComponent {
445474
// 这里有个问题, 更新的时候, 某个字段后端接收到了null, 到底是忽略这个字段还是将字段更新为null(默认值)? 用过mybatis的应该能明白啥意思
446475
// 这个问题貌似是无解的, 在后端字段只有null/not null两种状态, 而前端可以用3种状态: undefined表示不更新, null表示更新为null, 其他值表示更新为特定的值
447476
// 只能认为undefined/null都对应于后端的null
448-
// 换句话说, 如果DB里某个字段已经有值了, 就不可能再修改为null了, 即使建表时是允许null的. 最多更新成空字符串.
477+
// 换句话说, 如果DB里某个字段已经有值了, 就不可能再修改为null了, 即使建表时是允许null的. 最多更新成空字符串. 除非跟后端约定一个特殊的值去表示null.
449478
// 一般情况下这不会有什么影响, 但某些corner case里可能有bug...
450479

451480
// 另外, 要理解antd form的取值逻辑. antd的form是controlled components, 只有当FormItem变化时才会取到值(通过onChange方法), 否则对应的key就是undefined

src/components/DBTable/InnerTableSchemaUtils.js

+22-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
InputNumber,
99
Checkbox
1010
} from 'antd';
11-
import ImageUploader from '../ImageUploader';
11+
import FileUploader from '../FileUploader';
1212
import moment from 'moment';
1313
import Logger from '../../utils/Logger';
1414

@@ -105,6 +105,8 @@ const SchemaUtils = {
105105
return this.transformTextArea(field);
106106
case 'image':
107107
return this.transformImage(field);
108+
case 'file':
109+
return this.transformFile(field);
108110
default:
109111
return this.transformNormal(field);
110112
}
@@ -226,7 +228,25 @@ const SchemaUtils = {
226228
initialValue: forUpdate ? undefined : field.defaultValue,
227229
rules: forUpdate ? field.$$updateValidator : field.validator,
228230
})(
229-
<ImageUploader max={field.max} url={field.url} sizeLimit={field.sizeLimit}/>
231+
<FileUploader max={field.max} url={field.url} sizeLimit={field.sizeLimit} accept={field.accept}
232+
placeholder={field.placeholder} type="image"/>
233+
), field);
234+
},
235+
236+
/**
237+
* 转换为文件上传组件
238+
*
239+
* @param field
240+
* @returns {XML}
241+
*/
242+
transformFile(field) {
243+
logger.debug('transform field %o to file component', field);
244+
return this.colWrapper((getFieldDecorator, forUpdate) => getFieldDecorator(field.key, {
245+
initialValue: forUpdate ? undefined : field.defaultValue,
246+
rules: forUpdate ? field.$$updateValidator : field.validator,
247+
})(
248+
<FileUploader max={field.max} url={field.url} sizeLimit={field.sizeLimit} accept={field.accept}
249+
placeholder={field.placeholder}/>
230250
), field);
231251
},
232252

src/components/ImageUploader/index.js src/components/FileUploader/index.js

+110-46
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,41 @@
11
import React from 'react';
2-
import {Upload, Icon, Modal, message} from 'antd';
2+
import {Upload, Icon, Modal, message, Button, Tooltip} from 'antd';
33
import globalConfig from '../../config.js';
44
import Utils from '../../utils';
55
import Logger from '../../utils/Logger.js';
66
import './index.less';
77

8-
const logger = Logger.getLogger('ImageUploader');
8+
const logger = Logger.getLogger('FileUploader');
99

1010
/**
11-
* 图片上传组件, 样式基本是从antd官网抄过来的
11+
* 文件上传组件, 样式基本是从antd官网抄过来的
12+
* 可以上传图片, 也可以上传普通文件, 样式会不一样, 通过props中传入的type字段判断
1213
*
1314
* 这个组件可以配合antd的FormItem使用, 以后可以参考
1415
*/
15-
class ImageUploader extends React.Component {
16+
class FileUploader extends React.Component {
1617

1718
// 注意这个组件不能做成PureComponent, 会有bug, 因为上传的过程中会不断触发onChange, 进而导致状态不断变化
1819

1920
state = {
20-
previewVisible: false, // 是否显示预览
21+
previewVisible: false, // 是否显示图片预览modal
2122
previewImage: '', // 要预览的图片
2223
fileList: [], // 已经上传的文件列表
2324
};
2425

25-
// 上传按钮, 每次render的时候重新定义也可以, 但感觉拿出来只定义一次更好
26-
uploadButton = (
27-
<div>
28-
<Icon type="plus"/>
29-
<div className="ant-upload-text">上传图片</div>
30-
</div>
31-
);
32-
3326
componentWillMount() {
34-
const {defaultValue, max, url} = this.props;
27+
const {defaultValue, max, url, type} = this.props;
28+
// 当前是要上传图片还是普通图片? 会影响后续的很多东西
29+
const forImage = type === 'image';
30+
if (forImage) {
31+
this.listType = 'picture-card'; // 对于图片类型的上传, 要显示缩略图
32+
} else {
33+
this.listType = 'text'; // 对于其他类型的上传, 只显示个文件名就可以了
34+
}
35+
3536
// 组件第一次加载的时候, 设置默认值
3637
this.forceUpdateStateByValue(defaultValue, max);
38+
3739
// 是否自定义了图片上传的路径
3840
if (url) {
3941
if (url.startsWith('http')) {
@@ -42,10 +44,31 @@ class ImageUploader extends React.Component {
4244
this.uploadUrl = `${globalConfig.getAPIPath()}${url}`;
4345
}
4446
} else {
45-
this.uploadUrl = `${globalConfig.getAPIPath()}${globalConfig.upload.image}`; // 默认路径
47+
this.uploadUrl = `${globalConfig.getAPIPath()}${forImage ? globalConfig.upload.image : globalConfig.upload.file}`; // 默认上传接口
48+
}
49+
50+
// 上传时的文件大小限制
51+
if (this.props.sizeLimit) {
52+
this.sizeLimit = this.props.sizeLimit;
53+
} else {
54+
// 默认的大小限制
55+
if (forImage) {
56+
this.sizeLimit = globalConfig.upload.imageSizeLimit;
57+
} else {
58+
this.sizeLimit = globalConfig.upload.fileSizeLimit;
59+
}
60+
}
61+
62+
// 允许上传的文件类型
63+
if (this.props.accept) {
64+
this.accept = this.props.accept;
65+
} else if (forImage) {
66+
this.accept = '.jpg,.png,.gif,.jpeg'; // 上传图片时有默认的accept
4667
}
4768

48-
logger.debug('image upload url = %s', this.uploadUrl);
69+
logger.debug('type = %s, upload url = %s, sizeLimit = %d, accept = %s', type, this.uploadUrl, this.sizeLimit, this.accept);
70+
71+
this.forImage = forImage;
4972
}
5073

5174
componentWillReceiveProps(nextProps) {
@@ -113,7 +136,7 @@ class ImageUploader extends React.Component {
113136
if (Utils.isString(value) && value.length > 0) {
114137
this.state.fileList.push({
115138
uid: -1,
116-
name: value.substr(value.lastIndexOf('/')), // 取url中的最后一部分作为文件名, 不过这个名字其实没啥用...
139+
name: value.substr(value.lastIndexOf('/') + 1), // 取url中的最后一部分作为文件名
117140
status: 'done',
118141
url: value,
119142
});
@@ -123,15 +146,15 @@ class ImageUploader extends React.Component {
123146
// 但如果传进来一个数组, 就只取第一个元素
124147
this.state.fileList.push({
125148
uid: -1,
126-
name: value[0].substr(value[0].lastIndexOf('/')),
149+
name: value[0].substr(value[0].lastIndexOf('/') + 1),
127150
status: 'done',
128151
url: value[0],
129152
});
130153
} else {
131154
for (let i = 0; i < value.length; i++) {
132155
this.state.fileList.push({
133156
uid: -1 - i,
134-
name: value[i].substr(value[i].lastIndexOf('/')),
157+
name: value[i].substr(value[i].lastIndexOf('/') + 1),
135158
status: 'done',
136159
url: value[i],
137160
});
@@ -147,11 +170,9 @@ class ImageUploader extends React.Component {
147170
* @returns {boolean}
148171
*/
149172
beforeUpload = (file) => {
150-
const sizeLimit = this.props.sizeLimit || globalConfig.upload.imageSizeLimit;
151-
logger.debug('sizeLimit = %d', sizeLimit);
152-
if (sizeLimit) {
153-
if (file.size / 1024 > sizeLimit) {
154-
message.error(`图片过大,最大只允许${sizeLimit}KB`);
173+
if (this.sizeLimit) {
174+
if (file.size / 1024 > this.sizeLimit) {
175+
message.error(`${this.forImage ? '图片' : '文件'}过大,最大只允许${this.sizeLimit}KB`);
155176
return false;
156177
}
157178
}
@@ -187,31 +208,31 @@ class ImageUploader extends React.Component {
187208
// 还要自己处理一下fileList
188209
for (const tmp of fileList) {
189210
if (tmp.status === 'done' && !tmp.url && tmp.response && tmp.response.success) {
190-
tmp.url = tmp.response.data; // 服务端返回的图片地址
211+
tmp.url = tmp.response.data; // 服务端返回的url
191212
}
192213
}
193214

194215
// 上传失败
195216
if (file.status === 'error') {
196217
// debug模式下, 上传是必定失败的, 为了测试用, 给一个默认图片
197218
if (globalConfig.debug) {
198-
message.info('debug模式下使用测试图片', 2.5);
219+
message.info(`debug模式下使用测试${this.forImage ? '图片' : '文件'}`, 2.5);
199220
fileList.push({
200221
uid: Date.now(),
201-
name: 'avatar.jpg',
222+
name: this.forImage ? 'avatar.jpg' : 'mapreduce-osdi04.pdf',
202223
status: 'done',
203-
url: 'http://jxy.me/about/avatar.jpg',
224+
url: this.forImage ? 'http://jxy.me/about/avatar.jpg' : 'https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/mapreduce-osdi04.pdf',
204225
});
205226
this.notifyFileChange();
206227
} else {
207-
message.error(`图片${file.name}上传失败`, 2.5);
228+
message.error(`${file.name}上传失败`, 2.5);
208229
}
209230
}
210231
// 上传成功 or 删除图片
211232
else if (file.status === 'done' || file.status === 'removed') {
212233
this.notifyFileChange();
213234
}
214-
// 其实还有正在上传(uploading)的状态, 不过这里不关心
235+
// 其实还有正在上传(uploading)/错误(error)的状态, 不过这里不关心
215236

216237
// 注意对于controlled components而言, 这步setState必不可少
217238
// 见https://github.com/ant-design/ant-design/issues/2423
@@ -251,52 +272,95 @@ class ImageUploader extends React.Component {
251272
}
252273
};
253274

275+
/**
276+
* 上传按钮的样式, 跟文件类型/当前状态都有关
277+
*/
278+
renderUploadButton() {
279+
const {fileList} = this.state;
280+
const disabled = fileList.length >= this.props.max;
281+
282+
if (this.forImage) {
283+
const button = (<div>
284+
<Icon type="plus"/>
285+
<div className="ant-upload-text">上传图片</div>
286+
</div>);
287+
// 对于图片而言, 如果文件数量达到max, 上传按钮直接消失
288+
if (disabled) {
289+
return null;
290+
}
291+
// 是否有提示语
292+
if (this.props.placeholder) {
293+
return <Tooltip title={this.props.placeholder} mouseLeaveDelay={0}>
294+
{button}
295+
</Tooltip>;
296+
} else {
297+
return button;
298+
}
299+
} else {
300+
// 对于普通文件而言, 如果数量达到max, 上传按钮不可用
301+
const button = <Button disabled={disabled}><Icon type="upload"/> 上传</Button>;
302+
// 是否要有提示语
303+
if (this.props.placeholder && !disabled) {
304+
return <Tooltip title={this.props.placeholder} mouseLeaveDelay={0}>
305+
{button}
306+
</Tooltip>;
307+
} else {
308+
return button;
309+
}
310+
}
311+
}
312+
254313

255314
render() {
256315
const {previewVisible, previewImage, fileList} = this.state;
257316

258317
// 我本来是写成accept="image/*"的, 但chrome下有些bug, 要很久才能弹出文件选择框
259-
// 只能用后缀名的写法了, 只支持常见的图片格式
318+
// 只能用后缀名的写法了
260319
return (
261320
<div>
262321
<Upload
263322
action={this.uploadUrl}
264-
listType="picture-card"
323+
listType={this.listType}
265324
fileList={fileList}
266-
onPreview={this.handlePreview}
325+
onPreview={this.forImage ? this.handlePreview : undefined}
267326
onChange={this.handleChange}
268327
beforeUpload={this.beforeUpload}
269-
accept=".jpg,.png,.gif,.jpeg"
328+
accept={this.accept}
270329
withCredentials={globalConfig.isCrossDomain()}
271330
>
272-
{fileList.length >= this.props.max ? null : this.uploadButton}
331+
{this.renderUploadButton()}
273332
</Upload>
333+
{/*只有上传图片时才需要这个预览modal*/}
334+
{this.forImage &&
274335
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
275-
<img alt="加载失败" style={{ width: '100%' }} src={previewImage}/>
276-
</Modal>
336+
<img alt="图片加载失败" style={{ width: '100%' }} src={previewImage}/>
337+
</Modal>}
277338
</div>
278339
);
279340
}
280341

281342
}
282343

283-
ImageUploader.propTypes = {
284-
max: React.PropTypes.number.isRequired, // 最多可以上传几张图片
285-
sizeLimit: React.PropTypes.number, // 图片的大小限制, 单位KB
286-
onChange: React.PropTypes.func, // 图片上传后的回调函数, 传入参数是图片的url
287-
defaultValue: React.PropTypes.oneOfType([ // 默认值, 可以是单张图片, 也可以是一组图片
344+
FileUploader.propTypes = {
345+
max: React.PropTypes.number.isRequired, // 最多可以上传文件数量
346+
sizeLimit: React.PropTypes.number, // 大小限制, 单位KB
347+
onChange: React.PropTypes.func, // 上传后的回调函数
348+
defaultValue: React.PropTypes.oneOfType([ // 默认值, 可以是单个文件, 也可以是一组文件
288349
React.PropTypes.string,
289350
React.PropTypes.array,
290351
]),
291352
value: React.PropTypes.oneOfType([ // 受控组件
292353
React.PropTypes.string,
293354
React.PropTypes.array,
294355
]),
295-
url: React.PropTypes.string, // 自定义图片的上传地址
356+
url: React.PropTypes.string, // 自定义上传接口
357+
type: React.PropTypes.string, // type=image表示上传图片, 否则上传普通文件
358+
accept: React.PropTypes.string, // 上传时允许选择的文件类型, 例子:".jpg,.png,.gif"
359+
placeholder: React.PropTypes.string, // 提示语
296360
};
297361

298-
ImageUploader.defaultProps = {
299-
max: 1, // 默认只上传一张图片
362+
FileUploader.defaultProps = {
363+
max: 1, // 默认只能上传一个文件
300364
};
301365

302-
export default ImageUploader;
366+
export default FileUploader;
File renamed without changes.

src/config.js

+4
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ module.exports = {
4444
},
4545

4646
upload: { // 上传相关配置
47+
// 上传图片和上传普通文件分别配置
4748
image: '/uploadImage', // 默认的上传图片接口
4849
imageSizeLimit: 1500, // 默认的图片大小限制, 单位KB
50+
51+
file: '/uploadFile', // 默认的上传文件的接口
52+
fileSizeLimit: 10240, // 默认的文件大小限制, 单位KB
4953
},
5054

5155
sidebar: { // 侧边栏相关配置

0 commit comments

Comments
 (0)