@jiangood/open-admin 3.0.4 → 3.0.5
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.
- package/package.json +1 -1
- package/src/framework/fields/FieldEditor/index.tsx +4 -1
- package/src/framework/fields/FieldUploadFile/index.tsx +25 -19
- package/src/framework/views/ViewFile/index.tsx +4 -4
- package/src/framework/views/ViewImage/index.tsx +3 -3
- package/src/pages/article/$code.jsx +6 -1
- package/src/pages/system/article/index.jsx +30 -2
- package/src/pages/system/file/index.jsx +4 -16
- package/src/pages/system/job/index.jsx +1 -1
package/package.json
CHANGED
|
@@ -6,6 +6,8 @@ import type {FieldProps} from '../types';
|
|
|
6
6
|
export interface FieldEditorProps extends FieldProps<string> {
|
|
7
7
|
/** 编辑器高度,默认 300 */
|
|
8
8
|
height?: number;
|
|
9
|
+
/** 图片上传可见性,默认 public(富文本图片需公开访问) */
|
|
10
|
+
visibility?: 'public' | 'private';
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -15,7 +17,8 @@ export interface FieldEditorProps extends FieldProps<string> {
|
|
|
15
17
|
*/
|
|
16
18
|
export class FieldEditor extends React.Component<FieldEditorProps> {
|
|
17
19
|
render() {
|
|
18
|
-
const
|
|
20
|
+
const visibility = this.props.visibility || 'public';
|
|
21
|
+
const uploadUrl = UrlUtils.contextPath('/admin/sysFile/upload') + '?visibility=' + visibility;
|
|
19
22
|
const jsUrl = UrlUtils.contextPath('/admin/tinymce/tinymce.min.js');
|
|
20
23
|
const {value, onChange, height} = this.props;
|
|
21
24
|
|
|
@@ -8,8 +8,8 @@ import {ViewFile} from "../../views";
|
|
|
8
8
|
import {ObjectUtils, UrlUtils} from "../../utils";
|
|
9
9
|
import type {FieldProps} from '../types';
|
|
10
10
|
|
|
11
|
-
/** 框架内使用的上传文件对象,额外携带 sysFile 的
|
|
12
|
-
export type SysUploadFile = UploadFile & {
|
|
11
|
+
/** 框架内使用的上传文件对象,额外携带 sysFile 的 objectName */
|
|
12
|
+
export type SysUploadFile = UploadFile & { objectName?: string };
|
|
13
13
|
|
|
14
14
|
export interface FieldUploadFileProps extends FieldProps<string> {
|
|
15
15
|
/** 最大上传数量,默认 1 */
|
|
@@ -22,6 +22,8 @@ export interface FieldUploadFileProps extends FieldProps<string> {
|
|
|
22
22
|
listType?: UploadProps['listType'];
|
|
23
23
|
/** 接受的文件类型,如 image/* */
|
|
24
24
|
accept?: string;
|
|
25
|
+
/** 文件可见性,默认 public */
|
|
26
|
+
visibility?: 'public' | 'private';
|
|
25
27
|
/** 文件列表变化回调(新增文件上传成功后才触发) */
|
|
26
28
|
onFileChange?: (fileList: SysUploadFile[]) => void;
|
|
27
29
|
}
|
|
@@ -30,9 +32,10 @@ interface FieldUploadFileState {
|
|
|
30
32
|
maxCount: number;
|
|
31
33
|
cropImage: boolean;
|
|
32
34
|
fileList: SysUploadFile[];
|
|
33
|
-
/** 逗号分隔的文件
|
|
35
|
+
/** 逗号分隔的文件 objectName */
|
|
34
36
|
value: string | null;
|
|
35
37
|
accept?: string;
|
|
38
|
+
visibility?: 'public' | 'private';
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
/**
|
|
@@ -40,16 +43,17 @@ interface FieldUploadFileState {
|
|
|
40
43
|
*
|
|
41
44
|
* 可参考 react-easy-crop
|
|
42
45
|
*/
|
|
43
|
-
export class FieldUploadFile extends React.Component<FieldUploadFileProps, FieldUploadFileState & { errorTitle?: string; errorContent?: string;
|
|
46
|
+
export class FieldUploadFile extends React.Component<FieldUploadFileProps, FieldUploadFileState & { errorTitle?: string; errorContent?: string; previewObjectName?: string }> {
|
|
44
47
|
|
|
45
|
-
state: FieldUploadFileState & { errorTitle?: string; errorContent?: string;
|
|
48
|
+
state: FieldUploadFileState & { errorTitle?: string; errorContent?: string; previewObjectName?: string } = {
|
|
46
49
|
// 传入的参数
|
|
47
50
|
maxCount: 1,
|
|
48
51
|
cropImage: false,
|
|
52
|
+
visibility: 'public',
|
|
49
53
|
|
|
50
54
|
// 内部参数
|
|
51
55
|
fileList: [],
|
|
52
|
-
value: null, //
|
|
56
|
+
value: null, // 逗号分隔的文件objectName
|
|
53
57
|
};
|
|
54
58
|
|
|
55
59
|
constructor(props: FieldUploadFileProps) {
|
|
@@ -62,6 +66,7 @@ export class FieldUploadFile extends React.Component<FieldUploadFileProps, Field
|
|
|
62
66
|
const next: Partial<FieldUploadFileState> = {};
|
|
63
67
|
if (this.props.maxCount !== prevProps.maxCount) next.maxCount = this.props.maxCount;
|
|
64
68
|
if (this.props.cropImage !== prevProps.cropImage) next.cropImage = this.props.cropImage;
|
|
69
|
+
if (this.props.visibility !== prevProps.visibility) next.visibility = this.props.visibility;
|
|
65
70
|
|
|
66
71
|
const prevValue = prevProps.value ?? null;
|
|
67
72
|
const curValue = this.props.value ?? null;
|
|
@@ -77,9 +82,9 @@ export class FieldUploadFile extends React.Component<FieldUploadFileProps, Field
|
|
|
77
82
|
const list: SysUploadFile[] = [];
|
|
78
83
|
if (value && value.length > 0) {
|
|
79
84
|
const arr = value.split(",");
|
|
80
|
-
for (const
|
|
81
|
-
const url = UrlUtils.contextPath('/
|
|
82
|
-
const file = {
|
|
85
|
+
for (const objectName of arr) {
|
|
86
|
+
const url = UrlUtils.contextPath('/file/' + objectName);
|
|
87
|
+
const file = {objectName, url, uid: objectName, name: objectName, status: 'done', fileName: objectName} as SysUploadFile;
|
|
83
88
|
list.push(file);
|
|
84
89
|
}
|
|
85
90
|
}
|
|
@@ -88,24 +93,24 @@ export class FieldUploadFile extends React.Component<FieldUploadFileProps, Field
|
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
convertComponentValueToOutput(fileList: SysUploadFile[]): string[] {
|
|
91
|
-
const
|
|
96
|
+
const objectNames: string[] = [];
|
|
92
97
|
for (const f of fileList) {
|
|
93
98
|
if (f.status === 'done') {
|
|
94
99
|
if (f.response) { // 新上传的
|
|
95
100
|
const ajaxResult = f.response;
|
|
96
101
|
if (ajaxResult.success) {
|
|
97
|
-
const {
|
|
98
|
-
f.
|
|
99
|
-
|
|
102
|
+
const {objectName, name} = ajaxResult.data;
|
|
103
|
+
f.objectName = objectName;
|
|
104
|
+
objectNames.push(objectName);
|
|
100
105
|
} else {
|
|
101
106
|
this.setState({errorTitle: '上传文件失败', errorContent: ajaxResult.message});
|
|
102
107
|
}
|
|
103
108
|
} else { // 老的
|
|
104
|
-
|
|
109
|
+
objectNames.push(f.objectName as string);
|
|
105
110
|
}
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
|
-
return
|
|
113
|
+
return objectNames;
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
handleChange = ({fileList, event, file}: UploadChangeParam<SysUploadFile>) => {
|
|
@@ -133,7 +138,7 @@ export class FieldUploadFile extends React.Component<FieldUploadFileProps, Field
|
|
|
133
138
|
};
|
|
134
139
|
|
|
135
140
|
handlePreview = (file) => {
|
|
136
|
-
this.setState({
|
|
141
|
+
this.setState({previewObjectName: file.objectName});
|
|
137
142
|
};
|
|
138
143
|
|
|
139
144
|
render() {
|
|
@@ -150,9 +155,9 @@ export class FieldUploadFile extends React.Component<FieldUploadFileProps, Field
|
|
|
150
155
|
onOk={() => this.setState({errorTitle: undefined})}>
|
|
151
156
|
{this.state.errorContent}
|
|
152
157
|
</Modal>
|
|
153
|
-
<Modal open={!!this.state.
|
|
154
|
-
onCancel={() => this.setState({
|
|
155
|
-
{this.state.
|
|
158
|
+
<Modal open={!!this.state.previewObjectName} title="文件预览" width="80vw" footer={null}
|
|
159
|
+
onCancel={() => this.setState({previewObjectName: undefined})}>
|
|
160
|
+
{this.state.previewObjectName && <ViewFile value={this.state.previewObjectName} height='70vh'/>}
|
|
156
161
|
</Modal>
|
|
157
162
|
</>
|
|
158
163
|
}
|
|
@@ -162,6 +167,7 @@ export class FieldUploadFile extends React.Component<FieldUploadFileProps, Field
|
|
|
162
167
|
|
|
163
168
|
return <Upload
|
|
164
169
|
action={UrlUtils.contextPath('/admin/sysFile/upload')}
|
|
170
|
+
data={{visibility: this.state.visibility}}
|
|
165
171
|
listType={this.props.listType || 'picture-card'}
|
|
166
172
|
fileList={fileList}
|
|
167
173
|
onChange={this.handleChange}
|
|
@@ -7,15 +7,15 @@ export class ViewFile extends React.Component {
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
render() {
|
|
10
|
-
const
|
|
10
|
+
const value = this.props.value
|
|
11
11
|
|
|
12
|
-
if (!
|
|
12
|
+
if (!value) {
|
|
13
13
|
return <Empty/>;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const arr =
|
|
16
|
+
const arr = value.split(',');
|
|
17
17
|
|
|
18
|
-
const urlList = arr.map(
|
|
18
|
+
const urlList = arr.map(objectName => UrlUtils.contextPath('/file/' + objectName));
|
|
19
19
|
const height = this.props.height;
|
|
20
20
|
|
|
21
21
|
if(urlList.length === 1){
|
|
@@ -23,16 +23,16 @@ export class ViewImage extends React.Component {
|
|
|
23
23
|
|
|
24
24
|
const urlList = [];
|
|
25
25
|
for (let v of vs) {
|
|
26
|
-
const isId = v.indexOf('/') === -1;
|
|
27
26
|
const isAbsUrl = v.startsWith('http');
|
|
28
27
|
const isDataUrl = v.startsWith('data:');
|
|
28
|
+
const isObjectName = v.startsWith('public/') || v.startsWith('private/');
|
|
29
29
|
if (isAbsUrl || isDataUrl) {
|
|
30
30
|
urlList.push(v);
|
|
31
31
|
continue;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
if (
|
|
35
|
-
urlList.push(UrlUtils.contextPath('/
|
|
34
|
+
if (isObjectName) {
|
|
35
|
+
urlList.push(UrlUtils.contextPath('/file/' + v));
|
|
36
36
|
continue;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {HttpUtils, Page} from "../../framework";
|
|
2
|
+
import {HttpUtils, Page, UrlUtils} from "../../framework";
|
|
3
3
|
|
|
4
4
|
export default class extends React.Component {
|
|
5
5
|
|
|
@@ -37,6 +37,11 @@ export default class extends React.Component {
|
|
|
37
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='主图'/>
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
40
45
|
<div dangerouslySetInnerHTML={{__html: article.content}}/>
|
|
41
46
|
</Page>
|
|
42
47
|
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import {PlusOutlined} from '@ant-design/icons'
|
|
2
|
-
import {Button, Form, Input, InputNumber, Popconfirm} from 'antd'
|
|
2
|
+
import {Button, Form, Input, InputNumber, Modal, Popconfirm} 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
10
|
FormModal,
|
|
10
11
|
HttpUtils,
|
|
11
12
|
Page,
|
|
12
13
|
PermActions,
|
|
13
|
-
ProTable
|
|
14
|
+
ProTable,
|
|
15
|
+
UrlUtils
|
|
14
16
|
} from "../../../framework";
|
|
15
17
|
|
|
16
18
|
const {TextArea} = Input;
|
|
@@ -19,6 +21,7 @@ export default class extends React.Component {
|
|
|
19
21
|
|
|
20
22
|
state = {
|
|
21
23
|
editing: false,
|
|
24
|
+
previewArticle: null,
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
modalRef = React.createRef()
|
|
@@ -34,6 +37,10 @@ export default class extends React.Component {
|
|
|
34
37
|
this.modalRef.current.open(record)
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
handlePreview = record => {
|
|
41
|
+
this.setState({previewArticle: record})
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
onFinish = async values => {
|
|
38
45
|
const isNew = !values.id;
|
|
39
46
|
const url = isNew ? 'admin/article/create' : 'admin/article/update';
|
|
@@ -80,6 +87,7 @@ export default class extends React.Component {
|
|
|
80
87
|
render: (_, record) => {
|
|
81
88
|
return (
|
|
82
89
|
<PermActions>
|
|
90
|
+
<Button size='small' onClick={() => this.handlePreview(record)}>预览</Button>
|
|
83
91
|
<Button size='small' perm='article:update' onClick={() => this.handleEdit(record)}>编辑</Button>
|
|
84
92
|
<Popconfirm perm='article:delete' title='确定删除?' onConfirm={() => this.handleDelete(record)}>
|
|
85
93
|
<Button size='small'>删除</Button>
|
|
@@ -91,6 +99,7 @@ export default class extends React.Component {
|
|
|
91
99
|
]
|
|
92
100
|
|
|
93
101
|
render() {
|
|
102
|
+
const {previewArticle} = this.state
|
|
94
103
|
return <Page
|
|
95
104
|
title="文章管理"
|
|
96
105
|
description="管理系统文章,如关于、帮助等页面"
|
|
@@ -126,6 +135,10 @@ export default class extends React.Component {
|
|
|
126
135
|
<Input/>
|
|
127
136
|
</Form.Item>
|
|
128
137
|
|
|
138
|
+
<Form.Item label='主图' name='mainImage'>
|
|
139
|
+
<FieldUploadFile maxCount={1} cropImage accept='image/*'/>
|
|
140
|
+
</Form.Item>
|
|
141
|
+
|
|
129
142
|
<Form.Item label='内容' name='content'>
|
|
130
143
|
<FieldEditor />
|
|
131
144
|
</Form.Item>
|
|
@@ -142,6 +155,21 @@ export default class extends React.Component {
|
|
|
142
155
|
<FieldBoolean/>
|
|
143
156
|
</Form.Item>
|
|
144
157
|
</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>
|
|
145
173
|
</Page>
|
|
146
174
|
}
|
|
147
175
|
}
|
|
@@ -15,7 +15,7 @@ export default class extends React.Component {
|
|
|
15
15
|
formRef = React.createRef()
|
|
16
16
|
|
|
17
17
|
handleDelete = row => {
|
|
18
|
-
HttpUtils.post('admin/sysFile/delete', row).then(rs => {
|
|
18
|
+
HttpUtils.post('admin/sysFile/delete', {value: row.objectName}).then(rs => {
|
|
19
19
|
this.tableRef.current.reload()
|
|
20
20
|
})
|
|
21
21
|
}
|
|
@@ -57,21 +57,9 @@ export default class extends React.Component {
|
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
title: '预览',
|
|
60
|
-
dataIndex: '
|
|
61
|
-
render(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (record.imageUrls?.length > 0) {
|
|
66
|
-
for (let item of record.imageUrls) {
|
|
67
|
-
const url = item.url;
|
|
68
|
-
const label = item.label;
|
|
69
|
-
nodes.push(<a href={(url)} target='_blank' style={{marginLeft: 8}}>{label}</a>)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return nodes;
|
|
74
|
-
|
|
60
|
+
dataIndex: 'objectName',
|
|
61
|
+
render(objectName) {
|
|
62
|
+
return <a href={UrlUtils.contextPath('/file/' + objectName)} target='_blank'>预览</a>
|
|
75
63
|
}
|
|
76
64
|
},
|
|
77
65
|
{
|
|
@@ -294,7 +294,7 @@ export default class extends React.Component {
|
|
|
294
294
|
title: '操作',
|
|
295
295
|
dataIndex: 'option',
|
|
296
296
|
render: (_, record) => {
|
|
297
|
-
const url = UrlUtils.contextPath('/admin/sys/log/' + record.id);
|
|
297
|
+
const url = UrlUtils.contextPath('/admin/sys/log/job/' + record.id);
|
|
298
298
|
return <a href={url} target='_blank'>日志</a>;
|
|
299
299
|
},
|
|
300
300
|
}
|