@jiangood/open-admin 3.0.12 → 3.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,6 +21,12 @@ export class HttpUtils {
21
21
  const url = config.url as string;
22
22
  config.url = url.startsWith('admin') ? '/' + url : url;
23
23
 
24
+ // FormData 请求交由浏览器自动生成带 boundary 的 multipart Content-Type,
25
+ // 否则手动指定 Content-Type 会缺失 boundary 导致后端解析失败
26
+ if (typeof FormData !== 'undefined' && config.data instanceof FormData) {
27
+ config.headers = {...config.headers, 'Content-Type': undefined};
28
+ }
29
+
24
30
  return new Promise<T>((resolve, reject) => {
25
31
  axiosInstance(config).then((response) => {
26
32
  const body = response.data;
@@ -1,71 +1,77 @@
1
1
  import React from 'react';
2
- import {Modal} from 'antd';
2
+ import {Image} from 'antd';
3
3
  import {UrlUtils} from '../../utils';
4
4
 
5
-
5
+ /**
6
+ * 图片展示组件
7
+ *
8
+ * 支持的属性:
9
+ * - value: 图片地址,支持多个(逗号分隔或数组)。objectName(public/、private/ 开头)自动拼 /file/ 前缀
10
+ * - size: 缩略图尺寸(正方形边长),默认 60
11
+ * - borderRadius: 圆角,默认 4
12
+ * - preview: 是否可点击放大预览,默认 true
13
+ * - previewTitle: 预览弹窗标题,默认「预览图片」
14
+ * - placeholder: value 为空时展示的内容,默认不渲染
15
+ * - style: 附加到 img 的样式
16
+ */
6
17
  export class ViewImage extends React.Component {
7
18
 
8
- state = {
9
- modalOpen: false,
10
- previewUrl: null,
19
+ static previewGroupProps = {
20
+ preview: {
21
+ scaleStep: 0.3,
22
+ },
23
+ };
24
+
25
+ resolveUrl = (v) => {
26
+ const isAbsUrl = v.startsWith('http');
27
+ const isDataUrl = v.startsWith('data:');
28
+ const isObjectName = v.startsWith('public/') || v.startsWith('private/');
29
+ if (isAbsUrl || isDataUrl) {
30
+ return {url: v, thumb: v};
31
+ }
32
+ if (isObjectName) {
33
+ return {
34
+ url: UrlUtils.contextPath('/file/' + v),
35
+ thumb: UrlUtils.contextPath('/file/' + v + '?thumb=1'),
36
+ };
37
+ }
38
+ return {url: UrlUtils.contextPath(v), thumb: UrlUtils.contextPath(v)};
11
39
  };
12
40
 
13
41
  render() {
14
- let vs = this.props.value
42
+ const {value, size = 60, borderRadius = 4, preview = true, previewTitle = '预览图片',
43
+ placeholder, style} = this.props;
15
44
 
16
- if (!vs) {
17
- return;
45
+ if (!value) {
46
+ return placeholder || null;
18
47
  }
19
48
 
49
+ let vs = value;
20
50
  if (typeof vs === 'string') {
21
51
  vs = vs.split(',');
22
52
  }
23
53
 
24
- const urlList = [];
25
- const thumbList = [];
26
- for (let v of vs) {
27
- const isAbsUrl = v.startsWith('http');
28
- const isDataUrl = v.startsWith('data:');
29
- const isObjectName = v.startsWith('public/') || v.startsWith('private/');
30
- if (isAbsUrl || isDataUrl) {
31
- urlList.push(v);
32
- thumbList.push(v);
33
- continue;
34
- }
35
-
36
- if (isObjectName) {
37
- urlList.push(UrlUtils.contextPath('/file/' + v));
38
- thumbList.push(UrlUtils.contextPath('/file/' + v + '?thumb=1'));
39
- continue;
40
- }
41
-
42
- urlList.push(UrlUtils.contextPath(v));
43
- thumbList.push(UrlUtils.contextPath(v));
54
+ const items = vs.map(this.resolveUrl);
55
+ const previewConfig = {
56
+ scaleStep: 0.3,
57
+ };
58
+ if (previewTitle) {
59
+ previewConfig.title = previewTitle;
44
60
  }
45
61
 
46
- const closeModal = () => this.setState({modalOpen: false});
47
-
48
- const imgs = thumbList.map((thumb, i) => (
49
- <img
50
- style={{ display: 'inline-block' }}
51
- key={urlList[i]}
52
- src={thumb}
53
- onClick={() => this.setState({modalOpen: true, previewUrl: urlList[i]})}
54
- width={60}
55
- height={60}
56
- />
57
- ));
58
-
59
62
  return (
60
- <>
61
- {imgs}
62
- <Modal open={this.state.modalOpen} title="预览图片" width="70vw" footer={null}
63
- onCancel={closeModal}>
64
- <div style={{maxHeight:'70vh',overflow:'auto'}}>
65
- <img src={this.state.previewUrl} style={{maxWidth: '100%'}}/>
66
- </div>
67
- </Modal>
68
- </>
63
+ <Image.PreviewGroup preview={previewConfig}>
64
+ {items.map((item, i) => (
65
+ <Image
66
+ key={item.url}
67
+ src={item.thumb}
68
+ width={size}
69
+ height={size}
70
+ style={{display: 'inline-block', objectFit: 'cover', borderRadius, cursor: preview ? 'pointer' : undefined, ...style}}
71
+ preview={preview ? {src: item.url} : false}
72
+ />
73
+ ))}
74
+ </Image.PreviewGroup>
69
75
  );
70
76
  }
71
77
  }
@@ -72,7 +72,7 @@
72
72
  padding: 12px 16px;
73
73
 
74
74
  .logo-img {
75
- height: 38px;
75
+ height: 36px;
76
76
  cursor: pointer;
77
77
  }
78
78
 
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import {HttpUtils, Page, UrlUtils} from "../../framework";
3
+ import "./article.less";
3
4
 
4
5
  export default class extends React.Component {
5
6
 
@@ -36,13 +37,26 @@ export default class extends React.Component {
36
37
  </Page>
37
38
  }
38
39
 
39
- return <Page title={article.title}>
40
- {article.mainImage && (
41
- <div style={{marginBottom: 16}}>
42
- <img src={UrlUtils.contextPath('/file/' + article.mainImage)} style={{maxWidth: '100%'}} alt='主图'/>
40
+ return <Page>
41
+ <div className="oa-article">
42
+ <div className="oa-article-title">
43
+ <h1>{article.title}</h1>
44
+ <div className="oa-article-meta">
45
+ {article.createUserLabel && <span>发布人:{article.createUserLabel}</span>}
46
+ {article.createTime && <span>发布时间:{article.createTime}</span>}
47
+ </div>
43
48
  </div>
44
- )}
45
- <div dangerouslySetInnerHTML={{__html: article.content}}/>
49
+ {article.mainImage && (
50
+ <div style={{marginBottom: 16, textAlign: 'center'}}>
51
+ <img
52
+ src={UrlUtils.contextPath('/file/' + article.mainImage)}
53
+ style={{maxWidth: '100%', maxHeight: 400, borderRadius: 4}}
54
+ alt='主图'
55
+ />
56
+ </div>
57
+ )}
58
+ <div dangerouslySetInnerHTML={{__html: article.content}}/>
59
+ </div>
46
60
  </Page>
47
61
  }
48
62
  }
@@ -0,0 +1,124 @@
1
+ .oa-article {
2
+ max-width: 800px;
3
+ margin: 0 auto;
4
+ background: #fff;
5
+ padding: 24px 32px;
6
+ border-radius: 4px;
7
+ font-size: 15px;
8
+ line-height: 1.8;
9
+ color: rgba(0, 0, 0, 0.88);
10
+ word-wrap: break-word;
11
+
12
+ .oa-article-title {
13
+ text-align: center;
14
+ padding-bottom: 16px;
15
+ margin-bottom: 16px;
16
+ border-bottom: 1px solid #eee;
17
+
18
+ h1 {
19
+ font-size: 26px;
20
+ font-weight: 600;
21
+ line-height: 1.4;
22
+ margin: 0 0 8px;
23
+ }
24
+
25
+ .oa-article-meta {
26
+ color: #999;
27
+ font-size: 13px;
28
+ display: flex;
29
+ justify-content: center;
30
+ gap: 16px;
31
+ }
32
+ }
33
+
34
+ p {
35
+ margin: 0 0 16px;
36
+ }
37
+
38
+ h1, h2, h3, h4, h5, h6 {
39
+ margin: 24px 0 12px;
40
+ font-weight: 600;
41
+ line-height: 1.4;
42
+ }
43
+
44
+ h1 { font-size: 24px; }
45
+ h2 { font-size: 21px; }
46
+ h3 { font-size: 18px; }
47
+ h4 { font-size: 16px; }
48
+ h5, h6 { font-size: 15px; }
49
+
50
+ ul, ol {
51
+ margin: 0 0 16px;
52
+ padding-left: 24px;
53
+ }
54
+
55
+ li {
56
+ margin-bottom: 4px;
57
+ }
58
+
59
+ img {
60
+ max-width: 100%;
61
+ height: auto;
62
+ display: block;
63
+ margin: 0 auto 16px;
64
+ }
65
+
66
+ a {
67
+ color: #1677ff;
68
+ }
69
+
70
+ blockquote {
71
+ margin: 0 0 16px;
72
+ padding: 8px 16px;
73
+ border-left: 4px solid #e8e8e8;
74
+ background: #fafafa;
75
+ color: #666;
76
+ }
77
+
78
+ pre {
79
+ background: #f6f8fa;
80
+ padding: 12px 16px;
81
+ border-radius: 4px;
82
+ overflow-x: auto;
83
+ margin: 0 0 16px;
84
+ }
85
+
86
+ code {
87
+ background: #f6f8fa;
88
+ padding: 2px 4px;
89
+ border-radius: 3px;
90
+ font-size: 90%;
91
+ }
92
+
93
+ pre code {
94
+ padding: 0;
95
+ background: none;
96
+ }
97
+
98
+ table {
99
+ border-collapse: collapse;
100
+ margin: 0 0 16px;
101
+ width: 100%;
102
+ }
103
+
104
+ th, td {
105
+ border: 1px solid #e0e0e0;
106
+ padding: 8px 12px;
107
+ text-align: left;
108
+ }
109
+
110
+ th {
111
+ background: #f5f5f5;
112
+ font-weight: 600;
113
+ }
114
+
115
+ hr {
116
+ border: none;
117
+ border-top: 1px solid #eee;
118
+ margin: 24px 0;
119
+ }
120
+
121
+ @media screen and (max-width: 600px) {
122
+ padding: 16px;
123
+ }
124
+ }
@@ -1,5 +1,5 @@
1
1
  import {PlusOutlined} from '@ant-design/icons'
2
- import {Button, Form, Input, InputNumber, Modal} from 'antd'
2
+ import {Button, Form, Input, InputNumber} from 'antd'
3
3
  import React from 'react'
4
4
  import {
5
5
  DictUtils,
@@ -10,18 +10,16 @@ import {
10
10
  FormModal,
11
11
  HttpUtils,
12
12
  Page,
13
+ PageUtils,
13
14
  PermActions,
14
15
  ProTable,
15
- UrlUtils
16
+ ViewImage
16
17
  } from "../../../framework";
17
18
 
18
- const {TextArea} = Input;
19
-
20
19
  export default class extends React.Component {
21
20
 
22
21
  state = {
23
22
  editing: false,
24
- previewArticle: null,
25
23
  }
26
24
 
27
25
  modalRef = React.createRef()
@@ -38,7 +36,7 @@ export default class extends React.Component {
38
36
  }
39
37
 
40
38
  handlePreview = record => {
41
- this.setState({previewArticle: record})
39
+ PageUtils.open('/article/' + record.code, record.title)
42
40
  }
43
41
 
44
42
  onFinish = async values => {
@@ -63,6 +61,14 @@ export default class extends React.Component {
63
61
  title: '标题',
64
62
  dataIndex: 'title',
65
63
  },
64
+ {
65
+ title: '主图',
66
+ dataIndex: 'mainImage',
67
+ width: 80,
68
+ render(v) {
69
+ return <ViewImage value={v} size={48}/>
70
+ },
71
+ },
66
72
  {
67
73
  title: '显示位置',
68
74
  dataIndex: 'position',
@@ -99,7 +105,6 @@ export default class extends React.Component {
99
105
  ]
100
106
 
101
107
  render() {
102
- const {previewArticle} = this.state
103
108
  return <Page
104
109
  title="文章管理"
105
110
  description="管理系统文章,如关于、帮助等页面"
@@ -155,21 +160,6 @@ export default class extends React.Component {
155
160
  <FieldBoolean/>
156
161
  </Form.Item>
157
162
  </FormModal>
158
-
159
- <Modal
160
- open={!!previewArticle}
161
- title={previewArticle?.title}
162
- width={800}
163
- footer={null}
164
- onCancel={() => this.setState({previewArticle: null})}
165
- >
166
- {previewArticle?.mainImage && (
167
- <div style={{marginBottom: 16}}>
168
- <img src={UrlUtils.contextPath('/file/' + previewArticle.mainImage)} style={{maxWidth: '100%'}} alt='主图'/>
169
- </div>
170
- )}
171
- <div dangerouslySetInnerHTML={{__html: previewArticle?.content || ''}}/>
172
- </Modal>
173
163
  </Page>
174
164
  }
175
165
  }
@@ -1,5 +1,5 @@
1
1
  import {DeleteOutlined, EditOutlined, PlusOutlined} from '@ant-design/icons';
2
- import {AutoComplete, Button, Card, Descriptions, Empty, Form, Input, InputNumber, Popconfirm, Splitter, Tree, Tag, TreeSelect, Typography} from 'antd';
2
+ import {AutoComplete, Button, Card, Descriptions, Empty, Form, Input, InputNumber, Splitter, Tree, Tag, TreeSelect, Typography} from 'antd';
3
3
  import React from 'react';
4
4
  import {
5
5
  PermActions,
@@ -168,7 +168,9 @@ export default class extends React.Component {
168
168
  size='small'
169
169
  title={<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
170
170
  <Typography.Text strong>字典类型</Typography.Text>
171
- <Button size='small' perm='sys-dict:create' icon={<PlusOutlined/>} onClick={this.handleTypeAdd}>新增类型</Button>
171
+ <PermActions size='small' actions={[
172
+ {label: '新增类型', perm: 'sys-dict:create', icon: <PlusOutlined/>, onClick: this.handleTypeAdd},
173
+ ]}/>
172
174
  </div>}
173
175
  >
174
176
  <Tree
@@ -188,14 +190,16 @@ export default class extends React.Component {
188
190
  <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8}}>
189
191
  <Typography.Text strong>类型信息</Typography.Text>
190
192
  {hasTypeSelected && (
191
- <span>
192
- <Button size='small' icon={<EditOutlined/>} perm='sys-dict:update'
193
- onClick={this.handleTypeEdit} style={{marginRight: 4}}>编辑</Button>
194
- <Popconfirm perm='sys-dict:delete' title='是否确定删除此类型及其所有子类型和字典项?'
195
- onConfirm={this.handleTypeDelete}>
196
- <Button size='small' icon={<DeleteOutlined/>}>删除</Button>
197
- </Popconfirm>
198
- </span>
193
+ <PermActions size='small' actions={[
194
+ {label: '编辑', perm: 'sys-dict:update', icon: <EditOutlined/>, onClick: this.handleTypeEdit},
195
+ {
196
+ label: '删除',
197
+ perm: 'sys-dict:delete',
198
+ icon: <DeleteOutlined/>,
199
+ confirm: '是否确定删除此类型及其所有子类型和字典项?',
200
+ onClick: this.handleTypeDelete,
201
+ },
202
+ ]}/>
199
203
  )}
200
204
  </div>
201
205
  {hasTypeSelected && <Descriptions size='small' column={3}>
@@ -1,19 +1,14 @@
1
1
  import React from 'react'
2
- import {Button, Empty, Form, Input, Modal, Pagination, Tabs} from 'antd'
2
+ import {Button, Form, Input, Modal, Space} from 'antd'
3
3
  import {CloudUploadOutlined} from "@ant-design/icons";
4
4
 
5
- import {PermActions, DictUtils, FieldDateRange, FieldDictSelect, FieldUploadFile, HttpUtils, Page, ProTable, UrlUtils} from "../../../framework";
5
+ import {PermActions, DictUtils, FieldDateRange, FieldDictSelect, FieldUploadFile, HttpUtils, Page, ProTable, UrlUtils, ViewImage} from "../../../framework";
6
6
 
7
7
  export default class extends React.Component {
8
8
 
9
9
  state = {
10
10
  formOpen: false,
11
11
  formValues: {},
12
- gridPage: 1,
13
- gridList: [],
14
- gridTotal: 0,
15
- gridLoading: false,
16
- previewUrl: undefined,
17
12
  }
18
13
 
19
14
  tableRef = React.createRef()
@@ -25,74 +20,105 @@ export default class extends React.Component {
25
20
  })
26
21
  }
27
22
 
28
- loadGrid = page => {
29
- const p = page || this.state.gridPage
30
- this.setState({gridLoading: true})
31
- HttpUtils.get('admin/sysFile/page', {page: p, size: 50, type: 'image'}).then(rs => {
32
- this.setState({gridList: rs.content || [], gridTotal: Number(rs.totalElements || 0), gridPage: p})
33
- }).finally(() => {
34
- this.setState({gridLoading: false})
23
+ handleBatchDelete = ids => {
24
+ HttpUtils.post('admin/sysFile/deleteBatch', ids).then(rs => {
25
+ this.tableRef.current.reload()
26
+ this.tableRef.current.clearSelection()
35
27
  })
36
28
  }
37
29
 
38
30
  columns = [
31
+ {
32
+ title: '缩略图',
33
+ dataIndex: 'objectName',
34
+ width: 80,
35
+ render: (objectName, record) => {
36
+ if (record.type !== 'image') {
37
+ return '-'
38
+ }
39
+ return <ViewImage value={objectName} size={48}/>
40
+ },
41
+ },
39
42
  {
40
43
  title: '原始名称',
41
44
  tooltip: '上传时候的文件名',
42
45
  dataIndex: 'originName',
43
46
  width: 200,
44
47
  },
45
- {
46
- title: '状态',
47
- dataIndex: 'status',
48
- width: 120,
49
- render(status) {
50
- return DictUtils.dictTag('fileStatus', status) || (status || '-')
51
- },
52
- },
53
48
  {
54
49
  title: '存储名称',
55
50
  dataIndex: 'objectName',
56
- tooltip: '文件唯一标识id'
51
+ tooltip: '文件唯一标识id',
52
+ width: 200,
57
53
  },
58
-
59
54
  {
60
55
  title: '文件大小',
61
56
  dataIndex: 'sizeInfo',
57
+ width: 100,
58
+ },
59
+ {
60
+ title: '扩展名',
61
+ dataIndex: 'suffix',
62
+ width: 90,
62
63
  },
63
-
64
64
  {
65
- title: 'mime',
65
+ title: 'MIME',
66
66
  dataIndex: 'mimeType',
67
+ width: 130,
67
68
  },
68
69
  {
69
- title: '扩展名',
70
- dataIndex: 'suffix',
70
+ title: '类型',
71
+ dataIndex: 'type',
72
+ width: 80,
73
+ render(v) {
74
+ return v || '-'
75
+ },
76
+ },
77
+ {
78
+ title: '状态',
79
+ dataIndex: 'status',
80
+ width: 100,
81
+ render(status) {
82
+ return DictUtils.dictTag('fileStatus', status) || (status || '-')
83
+ },
71
84
  },
72
-
73
85
  {
74
86
  title: '上传时间',
75
87
  dataIndex: 'createTime',
88
+ width: 160,
76
89
  },
77
90
  {
78
91
  title: '上传者',
79
92
  dataIndex: 'createUserLabel',
93
+ width: 100,
80
94
  },
81
95
  {
82
- title: '预览',
83
- dataIndex: 'objectName',
84
- render(objectName) {
85
- return <a href={UrlUtils.contextPath('/file/' + objectName)} target='_blank'>预览</a>
86
- }
96
+ title: '关联表',
97
+ dataIndex: 'joinTable',
98
+ width: 120,
99
+ render(v) {
100
+ return v || '-'
101
+ },
102
+ },
103
+ {
104
+ title: '关联ID',
105
+ dataIndex: 'joinId',
106
+ width: 160,
107
+ render(v) {
108
+ return v || '-'
109
+ },
87
110
  },
88
111
  {
89
112
  title: '操作',
90
113
  dataIndex: 'option',
114
+ fixed: 'right',
115
+ width: 120,
91
116
  render: (_, record) => (
92
117
  <PermActions
93
118
  more
94
119
  size="small"
95
120
  actions={[
121
+ {label: '预览', onClick: () => window.open(UrlUtils.contextPath('/file/' + record.objectName), '_blank')},
96
122
  {label: '删除', perm: 'sys-file:delete', confirm: '是否确定删除文件信息', onClick: () => this.handleDelete(record)},
97
123
  ]}
98
124
  />
@@ -120,12 +146,25 @@ export default class extends React.Component {
120
146
  </Modal>
121
147
  )
122
148
 
123
- renderTable = () => (
124
- <>
149
+ render() {
150
+ return <Page>
125
151
  <ProTable
126
152
  actionRef={this.tableRef}
127
- toolBarRender={() => (
128
- <Button type='primary' icon={<CloudUploadOutlined/>} onClick={() => this.setState({formOpen: true})}>上传文件</Button>
153
+ rowSelection={{}}
154
+ toolBarRender={(params, selection) => (
155
+ <Space>
156
+ <Button type='primary' icon={<CloudUploadOutlined/>} onClick={() => this.setState({formOpen: true})}>上传文件</Button>
157
+ <PermActions
158
+ actions={[{
159
+ label: `批量删除${selection.selectedRowKeys.length ? `(${selection.selectedRowKeys.length})` : ''}`,
160
+ perm: 'sys-file:delete',
161
+ danger: true,
162
+ disabled: !selection.selectedRowKeys.length,
163
+ confirm: `是否确定删除选中的 ${selection.selectedRowKeys.length} 个文件?`,
164
+ onClick: () => this.handleBatchDelete(selection.selectedRowKeys),
165
+ }]}
166
+ />
167
+ </Space>
129
168
  )}
130
169
  request={(params) => {
131
170
  return HttpUtils.get('admin/sysFile/page', params);
@@ -150,61 +189,6 @@ export default class extends React.Component {
150
189
  )}
151
190
  />
152
191
  {this.renderUploadModal()}
153
- </>
154
- )
155
-
156
- renderImageGrid = () => {
157
- const {gridList, gridTotal, gridPage, gridLoading, previewUrl} = this.state
158
- return (
159
- <div style={{minHeight: 200, position: 'relative'}}>
160
- {gridList.length === 0 && !gridLoading && <Empty style={{marginTop: 80}} description="暂无图片"/>}
161
- <div style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))', gap: 8}}>
162
- {gridList.map(item => (
163
- <div key={item.objectName}
164
- style={{borderRadius: 4, overflow: 'hidden', cursor: 'pointer', border: '1px solid #eee'}}
165
- onClick={() => this.setState({previewUrl: UrlUtils.contextPath('/file/' + item.objectName)})}>
166
- <img
167
- src={UrlUtils.contextPath('/file/' + item.objectName + '?thumb=1')}
168
- style={{width: '100%', height: 120, objectFit: 'cover', display: 'block'}}
169
- alt={item.originName || item.objectName}
170
- />
171
- </div>
172
- ))}
173
- </div>
174
- {gridTotal > 0 && (
175
- <div style={{display: 'flex', justifyContent: 'flex-end', marginTop: 16}}>
176
- <Pagination
177
- current={gridPage}
178
- pageSize={50}
179
- total={gridTotal}
180
- showTotal={(total) => `共 ${total} 张`}
181
- onChange={(page) => this.loadGrid(page)}
182
- />
183
- </div>
184
- )}
185
-
186
- <Modal open={!!previewUrl} title="图片预览" width="70vw" footer={null}
187
- onCancel={() => this.setState({previewUrl: undefined})}>
188
- {previewUrl && <img src={previewUrl} style={{maxWidth: '100%'}} alt="预览"/>}
189
- </Modal>
190
- </div>
191
- )
192
- }
193
-
194
- render() {
195
- return <Page title="文件管理" description="管理系统上传文件">
196
- <Tabs
197
- defaultActiveKey="table"
198
- onChange={(key) => {
199
- if (key === 'image' && this.state.gridList.length === 0) {
200
- this.loadGrid(1)
201
- }
202
- }}
203
- items={[
204
- {key: 'table', label: '全部', children: this.renderTable()},
205
- {key: 'image', label: '图片', children: this.renderImageGrid()},
206
- ]}
207
- />
208
192
  </Page>
209
193
  }
210
194
  }