@jiangood/open-admin 3.0.11 → 3.0.13

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.
@@ -1,67 +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
- for (let v of vs) {
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
- urlList.push(v);
31
- continue;
32
- }
33
-
34
- if (isObjectName) {
35
- urlList.push(UrlUtils.contextPath('/file/' + v));
36
- continue;
37
- }
38
-
39
- urlList.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;
40
60
  }
41
61
 
42
- const closeModal = () => this.setState({modalOpen: false});
43
-
44
- const imgs = urlList.map((url) => (
45
- <img
46
- style={{ display: 'inline-block' }}
47
- key={url}
48
- src={url}
49
- onClick={() => this.setState({modalOpen: true, previewUrl: url})}
50
- width={60}
51
- height={60}
52
- />
53
- ));
54
-
55
62
  return (
56
- <>
57
- {imgs}
58
- <Modal open={this.state.modalOpen} title="预览图片" width="70vw" footer={null}
59
- onCancel={closeModal}>
60
- <div style={{maxHeight:'70vh',overflow:'auto'}}>
61
- <img src={this.state.previewUrl} style={{maxWidth: '100%'}}/>
62
- </div>
63
- </Modal>
64
- </>
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>
65
75
  );
66
76
  }
67
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
 
@@ -91,6 +91,7 @@ export class Layouts extends React.Component<LayoutsProps> {
91
91
  GlobalData.setSiteArticles(data.siteArticles);
92
92
 
93
93
  if (data.needUpdatePwd) {
94
+ this.setState({loginChecked: true});
94
95
  history.push('/standalone/forceUpdatePwd');
95
96
  return;
96
97
  }
@@ -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,27 +1,25 @@
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,
6
6
  FieldBoolean,
7
7
  FieldDictSelect,
8
8
  FieldEditor,
9
- FieldUploadFile,
9
+ FieldUploadImage,
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="管理系统文章,如关于、帮助等页面"
@@ -136,7 +141,7 @@ export default class extends React.Component {
136
141
  </Form.Item>
137
142
 
138
143
  <Form.Item label='主图' name='mainImage'>
139
- <FieldUploadFile maxCount={1} cropImage accept='image/*'/>
144
+ <FieldUploadImage maxCount={1}/>
140
145
  </Form.Item>
141
146
 
142
147
  <Form.Item label='内容' name='content'>
@@ -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,14 +1,14 @@
1
1
  import React from 'react'
2
- import {Button, Form, Input, Modal} 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
- formValues: {}
11
+ formValues: {},
12
12
  }
13
13
 
14
14
  tableRef = React.createRef()
@@ -20,64 +20,105 @@ export default class extends React.Component {
20
20
  })
21
21
  }
22
22
 
23
+ handleBatchDelete = ids => {
24
+ HttpUtils.post('admin/sysFile/deleteBatch', ids).then(rs => {
25
+ this.tableRef.current.reload()
26
+ this.tableRef.current.clearSelection()
27
+ })
28
+ }
29
+
23
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
+ },
24
42
  {
25
43
  title: '原始名称',
26
44
  tooltip: '上传时候的文件名',
27
45
  dataIndex: 'originName',
28
46
  width: 200,
29
47
  },
30
- {
31
- title: '状态',
32
- dataIndex: 'status',
33
- width: 120,
34
- render(status) {
35
- return DictUtils.dictTag('fileStatus', status) || (status || '-')
36
- },
37
- },
38
48
  {
39
49
  title: '存储名称',
40
50
  dataIndex: 'objectName',
41
- tooltip: '文件唯一标识id'
51
+ tooltip: '文件唯一标识id',
52
+ width: 200,
42
53
  },
43
-
44
54
  {
45
55
  title: '文件大小',
46
56
  dataIndex: 'sizeInfo',
57
+ width: 100,
47
58
  },
48
-
49
59
  {
50
- title: 'mime',
60
+ title: '扩展名',
61
+ dataIndex: 'suffix',
62
+ width: 90,
63
+ },
64
+ {
65
+ title: 'MIME',
51
66
  dataIndex: 'mimeType',
67
+ width: 130,
52
68
  },
53
69
  {
54
- title: '扩展名',
55
- 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
+ },
56
84
  },
57
-
58
85
  {
59
86
  title: '上传时间',
60
87
  dataIndex: 'createTime',
88
+ width: 160,
61
89
  },
62
90
  {
63
91
  title: '上传者',
64
92
  dataIndex: 'createUserLabel',
93
+ width: 100,
65
94
  },
66
95
  {
67
- title: '预览',
68
- dataIndex: 'objectName',
69
- render(objectName) {
70
- return <a href={UrlUtils.contextPath('/file/' + objectName)} target='_blank'>预览</a>
71
- }
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
+ },
72
110
  },
73
111
  {
74
112
  title: '操作',
75
113
  dataIndex: 'option',
114
+ fixed: 'right',
115
+ width: 120,
76
116
  render: (_, record) => (
77
117
  <PermActions
78
118
  more
79
119
  size="small"
80
120
  actions={[
121
+ {label: '预览', onClick: () => window.open(UrlUtils.contextPath('/file/' + record.objectName), '_blank')},
81
122
  {label: '删除', perm: 'sys-file:delete', confirm: '是否确定删除文件信息', onClick: () => this.handleDelete(record)},
82
123
  ]}
83
124
  />
@@ -85,14 +126,45 @@ export default class extends React.Component {
85
126
  },
86
127
  ]
87
128
 
129
+ renderUploadModal = () => (
130
+ <Modal open={this.state.formOpen} title='上传文件'
131
+ width={800}
132
+ onCancel={() => {
133
+ this.setState({formOpen: false})
134
+ this.tableRef.current.reload()
135
+ }}
136
+ footer={null}
137
+ destroyOnHidden
138
+ >
139
+ <Form ref={this.formRef}
140
+ initialValues={this.state.formValues}
141
+ >
142
+ <Form.Item name='文件'>
143
+ <FieldUploadFile accept="*/*"/>
144
+ </Form.Item>
145
+ </Form>
146
+ </Modal>
147
+ )
148
+
88
149
  render() {
89
- return <Page title="文件管理" description="管理系统上传文件">
150
+ return <Page>
90
151
  <ProTable
91
152
  actionRef={this.tableRef}
92
- toolBarRender={() => (
93
- <PermActions>
94
- <Button type='primary' perm='sys-file:upload' icon={<CloudUploadOutlined/>} onClick={() => this.setState({formOpen: true})}>上传文件</Button>
95
- </PermActions>
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>
96
168
  )}
97
169
  request={(params) => {
98
170
  return HttpUtils.get('admin/sysFile/page', params);
@@ -110,39 +182,13 @@ export default class extends React.Component {
110
182
  <Form.Item label='状态' name='status'>
111
183
  <FieldDictSelect typeCode='fileStatus'/>
112
184
  </Form.Item>
113
- <Form.Item label='类型' name='type'>
114
- <FieldDictSelect typeCode='materialType'/>
115
- </Form.Item>
116
185
  <Form.Item label='上传时间' name='dateRange'>
117
186
  <FieldDateRange/>
118
187
  </Form.Item>
119
188
  </>
120
189
  )}
121
190
  />
122
-
123
- <Modal open={this.state.formOpen} title='上传文件'
124
- width={800}
125
- onCancel={() => {
126
- this.setState({formOpen: false})
127
- this.tableRef.current.reload()
128
- }}
129
- footer={null}
130
- destroyOnHidden
131
- >
132
- <Form ref={this.formRef}
133
- initialValues={this.state.formValues}
134
- >
135
- <Form.Item name='文件'>
136
- <FieldUploadFile accept="*/*"/>
137
- </Form.Item>
138
-
139
-
140
- </Form>
141
- </Modal>
142
-
191
+ {this.renderUploadModal()}
143
192
  </Page>
144
193
  }
145
194
  }
146
-
147
-
148
-