@jiangood/open-admin 3.0.4 → 3.0.6

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 CHANGED
@@ -48,5 +48,5 @@
48
48
  "test:e2e": "playwright test",
49
49
  "test:e2e:ui": "playwright test --ui"
50
50
  },
51
- "version": "3.0.4"
51
+ "version": "3.0.6"
52
52
  }
@@ -1,26 +1,143 @@
1
1
  import React from 'react';
2
- import { Space } from 'antd';
2
+ import { Button, Dropdown, Popconfirm, Space } from 'antd';
3
+ import { EllipsisOutlined } from '@ant-design/icons';
3
4
  import { PermUtils } from '../utils';
4
5
 
5
- interface PermActionsProps {
6
- gap?: boolean;
6
+ export interface PermAction {
7
+ /** 按钮文本 */
8
+ label: React.ReactNode;
9
+ /** 权限码,无权限时隐藏该操作(如 `sys-user:update`) */
10
+ perm?: string;
11
+ /** 点击回调(配合 confirm 时在确认后触发) */
12
+ onClick?: () => void;
13
+ /** 确认提示文案,设置后点击需二次确认 */
14
+ confirm?: React.ReactNode;
15
+ /** 危险操作样式(红色),常与 confirm 配合用于删除 */
16
+ danger?: boolean;
17
+ /** 是否禁用 */
18
+ disabled?: boolean;
19
+ /** 图标 */
20
+ icon?: React.ReactNode;
21
+ /** 按钮类型,仅非下拉模式生效 */
22
+ type?: 'primary' | 'dashed' | 'link' | 'text' | 'default';
23
+ }
24
+
25
+ export interface PermActionsProps {
26
+ /** 是否折叠为「第一个操作 + 更多下拉」,多余操作进入 `...` 菜单 */
27
+ more?: boolean;
28
+ /** 按钮尺寸 */
29
+ size?: 'small' | 'middle' | 'large';
30
+ /** 传统写法:包裹带 `perm` 属性的子元素,按 `perm` 过滤。不推荐,建议改用 `actions` 数据驱动 */
7
31
  children?: React.ReactNode;
32
+ /** 数据驱动模式:操作数组,按每个 action 的 perm 过滤 */
33
+ actions?: PermAction[];
8
34
  }
9
35
 
10
36
  export class PermActions extends React.Component<PermActionsProps> {
37
+ static defaultProps: Partial<PermActionsProps> = {
38
+ more: false,
39
+ size: 'small',
40
+ };
41
+
11
42
  render() {
12
- const { children, gap = true } = this.props;
43
+ const { children, more, size, actions } = this.props;
44
+
45
+ if (actions) {
46
+ return this.renderActions(actions, more, size);
47
+ }
13
48
 
14
49
  const checkPerm = (element) => {
15
50
  const _props = element?.props;
16
51
  return !_props?.perm || PermUtils.hasPermission(_props.perm);
17
52
  };
18
53
 
54
+ if (children != null) {
55
+ console.warn(
56
+ '[PermActions] children 写法已不推荐,请改用 actions 数据驱动模式:' +
57
+ '<PermActions actions={[{ label, perm, onClick }]} />'
58
+ );
59
+ }
60
+
19
61
  const nodes = React.Children.toArray(children).filter(
20
62
  (child) => child != null && checkPerm(child)
21
63
  );
22
64
 
23
- if (!gap) return <>{nodes}</>;
65
+ if (nodes.length === 0) return null;
24
66
  return <Space>{nodes}</Space>;
25
67
  }
68
+
69
+ private renderButton(action: PermAction, size: 'small' | 'middle' | 'large') {
70
+ const button = (
71
+ <Button
72
+ size={size}
73
+ type={action.type}
74
+ icon={action.icon}
75
+ danger={action.danger}
76
+ disabled={action.disabled}
77
+ onClick={action.confirm ? undefined : action.onClick}
78
+ >
79
+ {action.label}
80
+ </Button>
81
+ );
82
+ if (action.confirm) {
83
+ return (
84
+ <Popconfirm title={action.confirm} onConfirm={action.onClick}>
85
+ {button}
86
+ </Popconfirm>
87
+ );
88
+ }
89
+ return button;
90
+ }
91
+
92
+ private renderActions(
93
+ actions: PermAction[],
94
+ more?: boolean,
95
+ size: 'small' | 'middle' | 'large' = 'small'
96
+ ) {
97
+ const visible = actions.filter((a) => !a.perm || PermUtils.hasPermission(a.perm));
98
+ if (visible.length === 0) return null;
99
+
100
+ if (!more || visible.length <= 1) {
101
+ const buttons = visible.map((action, index) => (
102
+ <React.Fragment key={index}>{this.renderButton(action, size)}</React.Fragment>
103
+ ));
104
+ return <Space>{buttons}</Space>;
105
+ }
106
+
107
+ const [first, ...rest] = visible;
108
+
109
+ const items = rest.map((action, index) => {
110
+ const label = action.confirm ? (
111
+ <Popconfirm title={action.confirm} onConfirm={action.onClick}>
112
+ <span>{action.label}</span>
113
+ </Popconfirm>
114
+ ) : (
115
+ action.label
116
+ );
117
+ return {
118
+ key: `more-${index}`,
119
+ label,
120
+ icon: action.icon,
121
+ danger: action.danger,
122
+ disabled: action.disabled,
123
+ onClick: (info) => {
124
+ if (action.confirm) {
125
+ info.domEvent.preventDefault();
126
+ info.domEvent.stopPropagation();
127
+ } else {
128
+ action.onClick?.();
129
+ }
130
+ },
131
+ };
132
+ });
133
+
134
+ return (
135
+ <Space.Compact>
136
+ {this.renderButton(first, size)}
137
+ <Dropdown menu={{ items }} placement="bottomRight">
138
+ <Button size={size} icon={<EllipsisOutlined/>}/>
139
+ </Dropdown>
140
+ </Space.Compact>
141
+ );
142
+ }
26
143
  }
@@ -0,0 +1,58 @@
1
+ import React from "react";
2
+ import {TreeSelect} from "antd";
3
+ import {HttpUtils} from "../../utils";
4
+
5
+ interface OrgNode {
6
+ title: string;
7
+ key: string;
8
+ children?: OrgNode[];
9
+ }
10
+
11
+ const countUnits = (nodes: OrgNode[] = []): number =>
12
+ nodes.reduce((sum, n) => sum + 1 + countUnits(n.children), 0);
13
+
14
+ /**
15
+ * 组织机构切换器:切换当前组织机构(单位),默认隐藏,由 Layouts 的 orgSwitcher 插槽按需渲染。
16
+ */
17
+ export class OrgSwitcher extends React.Component<{}, {tree: OrgNode[]; currentOrgId: string | null}> {
18
+ state = {tree: [], currentOrgId: null};
19
+
20
+ componentDidMount() {
21
+ HttpUtils.get("admin/myOrgs").then(data => {
22
+ if (data) {
23
+ this.setState({tree: data.tree || [], currentOrgId: data.currentOrgId || null});
24
+ }
25
+ });
26
+ }
27
+
28
+ handleChange = (orgId: string) => {
29
+ HttpUtils.post("admin/switchOrg", {orgId}).then(() => {
30
+ setTimeout(() => location.reload(), 1000);
31
+ }).catch(() => {
32
+ });
33
+ };
34
+
35
+ render() {
36
+ const {tree, currentOrgId} = this.state;
37
+
38
+ if (countUnits(tree) === 0) {
39
+ return <span style={{marginRight: 8, color: "#999"}}>暂无组织机构</span>;
40
+ }
41
+
42
+ if (countUnits(tree) === 1) {
43
+ return <span style={{marginRight: 8}}>{tree[0].title}</span>;
44
+ }
45
+
46
+ return (
47
+ <TreeSelect
48
+ style={{width: 160, marginRight: 8}}
49
+ treeData={tree}
50
+ value={currentOrgId || undefined}
51
+ placeholder="选择组织机构"
52
+ onChange={this.handleChange}
53
+ treeDefaultExpandAll
54
+ showSearch
55
+ />
56
+ );
57
+ }
58
+ }
@@ -7,5 +7,6 @@ export * from './Page'
7
7
  export * from './ValueType'
8
8
  export * from './ContextMenu'
9
9
  export * from './DownloadModal'
10
+ export * from './OrgSwitcher'
10
11
  export { default as ErrorBoundary } from './ErrorBoundary'
11
12
  export { default as FormModal } from './FormModal'
@@ -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 uploadUrl = UrlUtils.contextPath('/admin/sysFile/upload');
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 的 id */
12
- export type SysUploadFile = UploadFile & { id?: string };
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
- /** 逗号分隔的文件 id */
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; previewFileId?: 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; previewFileId?: 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, // 都好分隔的文件id
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 id of arr) {
81
- const url = UrlUtils.contextPath('/admin/sysFile/preview/' + id);
82
- const file = {id, url, uid: id, name: id, status: 'done', fileName: id} as SysUploadFile;
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 fileIds: string[] = [];
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 {id, name} = ajaxResult.data;
98
- f.id = id;
99
- fileIds.push(id);
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
- fileIds.push(f.id as string);
109
+ objectNames.push(f.objectName as string);
105
110
  }
106
111
  }
107
112
  }
108
- return fileIds;
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({previewFileId: file.id});
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.previewFileId} title="文件预览" width="80vw" footer={null}
154
- onCancel={() => this.setState({previewFileId: undefined})}>
155
- {this.state.previewFileId && <ViewFile value={this.state.previewFileId} height='70vh'/>}
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 fileId = this.props.value
10
+ const value = this.props.value
11
11
 
12
- if (!fileId) {
12
+ if (!value) {
13
13
  return <Empty/>;
14
14
  }
15
15
 
16
- const arr = fileId.split(',');
16
+ const arr = value.split(',');
17
17
 
18
- const urlList = arr.map(id => UrlUtils.contextPath('/admin/sysFile/preview/' + id));
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 (isId) {
35
- urlList.push(UrlUtils.contextPath('/admin/sysFile/preview/' + v));
34
+ if (isObjectName) {
35
+ urlList.push(UrlUtils.contextPath('/file/' + v));
36
36
  continue;
37
37
  }
38
38
 
@@ -38,7 +38,7 @@ export class HeaderRight extends React.Component {
38
38
  }
39
39
 
40
40
  account = () => {
41
- PageUtils.open('/account', '个人中心')
41
+ PageUtils.open('/userCenter', '个人中心')
42
42
  }
43
43
 
44
44
  openArticle = (code, title) => {
@@ -61,7 +61,7 @@ export class HeaderRight extends React.Component {
61
61
  }))
62
62
 
63
63
  const menuItems = [
64
- {key: 'account', label: '个人中心'},
64
+ {key: 'userCenter', label: '个人中心'},
65
65
  ...articleItems,
66
66
  {key: 'logout', label: '退出登录'},
67
67
  ]
@@ -76,7 +76,7 @@ export class HeaderRight extends React.Component {
76
76
 
77
77
  <Dropdown menu={{
78
78
  onClick: ({key}) => {
79
- if (key === 'account') {
79
+ if (key === 'userCenter') {
80
80
  this.account()
81
81
  } else if (key === 'logout') {
82
82
  this.logout();
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import {Layout, Menu, Skeleton, Watermark} from 'antd';
3
3
 
4
4
  import "./index.less"
5
- import {HttpUtils, NamedIcon, PageUtils, GlobalData, TreeUtils, history, Link, ARTICLE_HEADER_LEFT} from "../../framework";
5
+ import {HttpUtils, NamedIcon, PageUtils, GlobalData, TreeUtils, history, Link, ARTICLE_HEADER_LEFT, OrgSwitcher} from "../../framework";
6
6
 
7
7
  import { HeaderRight } from "./HeaderRight";
8
8
  import { TabLayout } from "./TabLayout";
@@ -204,6 +204,10 @@ return <Layout className='main-layout'>
204
204
  <div key={a.code} className='item' style={{cursor: 'pointer', padding: '0 12px', whiteSpace: 'nowrap'}}
205
205
  onClick={() => PageUtils.open('/article/' + a.code, a.title)}>{a.title}</div>
206
206
  ))}
207
+ {this.props.showOrgSwitcher?.({
208
+ activeTopMenu: this.getActiveTopMenu(),
209
+ loginInfo: this.props.loginInfo,
210
+ }) && <OrgSwitcher/>}
207
211
  {this.props.headerExtra?.({
208
212
  activeTopMenu: this.getActiveTopMenu(),
209
213
  loginInfo: this.props.loginInfo,
@@ -29,6 +29,7 @@ export interface HeaderExtraContext {
29
29
 
30
30
  interface LayoutsProps {
31
31
  headerExtra?: (context: HeaderExtraContext) => React.ReactNode;
32
+ showOrgSwitcher?: (context: HeaderExtraContext) => boolean;
32
33
  }
33
34
 
34
35
  export class Layouts extends React.Component<LayoutsProps> {
@@ -128,7 +129,7 @@ export class Layouts extends React.Component<LayoutsProps> {
128
129
  return (
129
130
  <ErrorBoundary minimal>
130
131
  <ConfigProvider {...configProps}>
131
- {showPageFrame ? <PageFrame url={pathname + search}/> : ready ? <AdminLayout headerExtra={this.props.headerExtra} loginInfo={GlobalData.getLoginInfo()}/> : (
132
+ {showPageFrame ? <PageFrame url={pathname + search}/> : ready ? <AdminLayout headerExtra={this.props.headerExtra} showOrgSwitcher={this.props.showOrgSwitcher} loginInfo={GlobalData.getLoginInfo()}/> : (
132
133
  <PageLoading messages={[
133
134
  !siteInfoLoaded && '加载站点信息...',
134
135
  !loginChecked && '检查登录中...',
@@ -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} 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';
@@ -78,19 +85,20 @@ export default class extends React.Component {
78
85
  title: '操作',
79
86
  dataIndex: 'option',
80
87
  render: (_, record) => {
81
- return (
82
- <PermActions>
83
- <Button size='small' perm='article:update' onClick={() => this.handleEdit(record)}>编辑</Button>
84
- <Popconfirm perm='article:delete' title='确定删除?' onConfirm={() => this.handleDelete(record)}>
85
- <Button size='small'>删除</Button>
86
- </Popconfirm>
87
- </PermActions>
88
- );
88
+ return <PermActions
89
+ more
90
+ actions={[
91
+ {label: '预览', onClick: () => this.handlePreview(record)},
92
+ {label: '编辑', perm: 'article:update', onClick: () => this.handleEdit(record)},
93
+ {label: '删除', perm: 'article:delete', confirm: '确定删除?', onClick: () => this.handleDelete(record)},
94
+ ]}
95
+ />;
89
96
  },
90
97
  },
91
98
  ]
92
99
 
93
100
  render() {
101
+ const {previewArticle} = this.state
94
102
  return <Page
95
103
  title="文章管理"
96
104
  description="管理系统文章,如关于、帮助等页面"
@@ -126,6 +134,10 @@ export default class extends React.Component {
126
134
  <Input/>
127
135
  </Form.Item>
128
136
 
137
+ <Form.Item label='主图' name='mainImage'>
138
+ <FieldUploadFile maxCount={1} cropImage accept='image/*'/>
139
+ </Form.Item>
140
+
129
141
  <Form.Item label='内容' name='content'>
130
142
  <FieldEditor />
131
143
  </Form.Item>
@@ -142,6 +154,21 @@ export default class extends React.Component {
142
154
  <FieldBoolean/>
143
155
  </Form.Item>
144
156
  </FormModal>
157
+
158
+ <Modal
159
+ open={!!previewArticle}
160
+ title={previewArticle?.title}
161
+ width={800}
162
+ footer={null}
163
+ onCancel={() => this.setState({previewArticle: null})}
164
+ >
165
+ {previewArticle?.mainImage && (
166
+ <div style={{marginBottom: 16}}>
167
+ <img src={UrlUtils.contextPath('/file/' + previewArticle.mainImage)} style={{maxWidth: '100%'}} alt='主图'/>
168
+ </div>
169
+ )}
170
+ <div dangerouslySetInnerHTML={{__html: previewArticle?.content || ''}}/>
171
+ </Modal>
145
172
  </Page>
146
173
  }
147
174
  }
@@ -1,10 +1,9 @@
1
1
  import {DeleteOutlined, EditOutlined, PlusOutlined} from '@ant-design/icons';
2
- import {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, Popconfirm, Splitter, Tree, Tag, TreeSelect, Typography} from 'antd';
3
3
  import React from 'react';
4
4
  import {
5
5
  PermActions,
6
6
  FieldBoolean,
7
- FieldDictSelect,
8
7
  FormModal,
9
8
  HttpUtils,
10
9
  Page,
@@ -12,6 +11,18 @@ import {
12
11
  ViewSwitch,
13
12
  } from "../../../framework";
14
13
 
14
+ const COLOR_OPTIONS = [
15
+ {value: 'SUCCESS', label: '成功'},
16
+ {value: 'PROCESSING', label: '处理中'},
17
+ {value: 'ERROR', label: '错误'},
18
+ {value: 'WARNING', label: '警告'},
19
+ {value: 'DEFAULT', label: '默认'},
20
+ {value: 'RED', label: '红色'},
21
+ {value: 'BLUE', label: '蓝色'},
22
+ {value: 'GREEN', label: '绿色'},
23
+ {value: 'GRAY', label: '灰色'},
24
+ ]
25
+
15
26
  export default class extends React.Component {
16
27
 
17
28
  state = {
@@ -134,16 +145,13 @@ export default class extends React.Component {
134
145
  {
135
146
  title: '操作', dataIndex: 'option',
136
147
  render: (_, record) => {
137
- return (
138
- <PermActions>
139
- <Button size='small' perm='sys-dict:update'
140
- onClick={() => this.handleItemEdit(record)}>编辑</Button>
141
- <Popconfirm perm='sys-dict:delete' title='是否确定删除字典项'
142
- onConfirm={() => this.handleItemDelete(record)}>
143
- <Button size='small'>删除</Button>
144
- </Popconfirm>
145
- </PermActions>
146
- );
148
+ return <PermActions
149
+ more
150
+ actions={[
151
+ {label: '编辑', perm: 'sys-dict:update', onClick: () => this.handleItemEdit(record)},
152
+ {label: '删除', perm: 'sys-dict:delete', confirm: '是否确定删除字典项', onClick: () => this.handleItemDelete(record)},
153
+ ]}
154
+ />;
147
155
  },
148
156
  },
149
157
  ]
@@ -249,7 +257,15 @@ export default class extends React.Component {
249
257
  <Input/>
250
258
  </Form.Item>
251
259
  <Form.Item label='颜色' name='color'>
252
- <FieldDictSelect typeCode='statusColor'/>
260
+ <AutoComplete
261
+ allowClear
262
+ maxLength={20}
263
+ options={COLOR_OPTIONS}
264
+ filterOption={(input, option) =>
265
+ (option.value + option.label).toLowerCase().includes(input.toLowerCase())
266
+ }
267
+ placeholder='如 SUCCESS、#ff0000'
268
+ />
253
269
  </Form.Item>
254
270
  <Form.Item label='序号' name='seq'>
255
271
  <InputNumber/>
@@ -1,8 +1,8 @@
1
1
  import React from 'react'
2
- import {Button, Form, Input, Modal, Popconfirm} from 'antd'
2
+ import {Button, Form, Input, Modal} from 'antd'
3
3
  import {CloudUploadOutlined} from "@ant-design/icons";
4
4
 
5
- import {PermActions, FieldDateRange, FieldDictSelect, FieldUploadFile, HttpUtils, Page, ProTable, UrlUtils} from "../../../framework";
5
+ import {PermActions, DictUtils, FieldDateRange, FieldDictSelect, FieldUploadFile, HttpUtils, Page, ProTable, UrlUtils} from "../../../framework";
6
6
 
7
7
  export default class extends React.Component {
8
8
 
@@ -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
  }
@@ -27,6 +27,14 @@ export default class extends React.Component {
27
27
  dataIndex: 'originName',
28
28
  width: 200,
29
29
  },
30
+ {
31
+ title: '状态',
32
+ dataIndex: 'status',
33
+ width: 120,
34
+ render(status) {
35
+ return DictUtils.dictTag('fileStatus', status) || (status || '-')
36
+ },
37
+ },
30
38
  {
31
39
  title: '存储名称',
32
40
  dataIndex: 'objectName',
@@ -57,33 +65,21 @@ export default class extends React.Component {
57
65
  },
58
66
  {
59
67
  title: '预览',
60
- dataIndex: 'id',
61
- render(id, record) {
62
- const nodes = [<a href={UrlUtils.contextPath('/admin/sysFile/preview/' + record.id)} target='_blank'>预览</a>]
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
-
68
+ dataIndex: 'objectName',
69
+ render(objectName) {
70
+ return <a href={UrlUtils.contextPath('/file/' + objectName)} target='_blank'>预览</a>
75
71
  }
76
72
  },
77
73
  {
78
74
  title: '操作',
79
75
  dataIndex: 'option',
80
76
  render: (_, record) => (
81
- <PermActions>
82
- <Popconfirm perm='sys-file:delete' title='是否确定删除文件信息'
83
- onConfirm={() => this.handleDelete(record)}>
84
- <a>删除</a>
85
- </Popconfirm>
86
- </PermActions>
77
+ <PermActions
78
+ more
79
+ actions={[
80
+ {label: '删除', perm: 'sys-file:delete', confirm: '是否确定删除文件信息', onClick: () => this.handleDelete(record)},
81
+ ]}
82
+ />
87
83
  ),
88
84
  },
89
85
  ]
@@ -110,6 +106,9 @@ export default class extends React.Component {
110
106
  <Form.Item label='对象名称' name='objectName'>
111
107
  <Input/>
112
108
  </Form.Item>
109
+ <Form.Item label='状态' name='status'>
110
+ <FieldDictSelect typeCode='fileStatus'/>
111
+ </Form.Item>
113
112
  <Form.Item label='类型' name='type'>
114
113
  <FieldDictSelect typeCode='materialType'/>
115
114
  </Form.Item>
@@ -8,9 +8,7 @@ import {
8
8
  Input,
9
9
  message,
10
10
  Modal,
11
- Popconfirm,
12
11
  Select,
13
- Space,
14
12
  Switch,
15
13
  Tag
16
14
  } from 'antd'
@@ -153,16 +151,15 @@ export default class extends React.Component {
153
151
  fixed: 'right',
154
152
  render: (_, record) => {
155
153
 
156
- return (
157
- <Space>
158
- <Button size='small' onClick={() => this.showExecuteRecord(record)}>执行记录</Button>
159
- <Button size='small' perm='job:trigger' onClick={() => this.handleTriggerJob(record)}>执行一次</Button>
160
- <Button size='small' perm='job:update' onClick={() => this.handleEdit(record)}> 编辑 </Button>
161
- <Popconfirm perm='job:delete' title='是否确定删除?' onConfirm={() => this.handleDelete(record)}>
162
- <Button size='small'>删除</Button>
163
- </Popconfirm>
164
- </Space>
165
- );
154
+ return <PermActions
155
+ more
156
+ actions={[
157
+ {label: '执行记录', onClick: () => this.showExecuteRecord(record)},
158
+ {label: '执行一次', perm: 'job:trigger', onClick: () => this.handleTriggerJob(record)},
159
+ {label: '编辑', perm: 'job:update', onClick: () => this.handleEdit(record)},
160
+ {label: '删除', perm: 'job:delete', confirm: '是否确定删除?', onClick: () => this.handleDelete(record)},
161
+ ]}
162
+ />;
166
163
  },
167
164
  },
168
165
 
@@ -294,7 +291,7 @@ export default class extends React.Component {
294
291
  title: '操作',
295
292
  dataIndex: 'option',
296
293
  render: (_, record) => {
297
- const url = UrlUtils.contextPath('/admin/sys/log/' + record.id);
294
+ const url = UrlUtils.contextPath('/admin/sys/log/job/' + record.id);
298
295
  return <a href={url} target='_blank'>日志</a>;
299
296
  },
300
297
  }
@@ -1,5 +1,5 @@
1
1
  import {PlusOutlined} from '@ant-design/icons'
2
- import {Button, Form, Input, InputNumber, Modal, Popconfirm, Select, Transfer, Tree} from 'antd'
2
+ import {Button, Form, Input, InputNumber, Modal, Select, Transfer, Tree} from 'antd'
3
3
  import React from 'react'
4
4
  import {FieldBoolean, FormModal, HttpUtils, Page, PageUtils, PermActions, ProTable, ViewText} from "../../../framework";
5
5
 
@@ -112,22 +112,15 @@ export default class extends React.Component {
112
112
  dataIndex: 'option',
113
113
  render: (_, record) => {
114
114
 
115
- return (
116
- <PermActions>
117
- <Button size='small' perm='sys-role:grant-permission'
118
- onClick={() => this.handleEditUser(record)}>用户设置</Button>
119
-
120
- <Button size='small' perm='sys-role:grant-permission'
121
- onClick={() => PageUtils.open('/system/role/rolePerm?id=' + record.id, '角色权限设置')}>权限设置</Button>
122
-
123
- <Button size='small' perm='sys-role:update'
124
- onClick={() => this.handleEdit(record)}>编辑</Button>
125
- <Popconfirm perm='sys-role:delete' title='是否确定删除系统角色'
126
- onConfirm={() => this.handleDelete(record)}>
127
- <Button size='small'>删除</Button>
128
- </Popconfirm>
129
- </PermActions>
130
- );
115
+ return <PermActions
116
+ more
117
+ actions={[
118
+ {label: '用户设置', perm: 'sys-role:grant-permission', onClick: () => this.handleEditUser(record)},
119
+ {label: '权限设置', perm: 'sys-role:grant-permission', onClick: () => PageUtils.open('/system/role/rolePerm?id=' + record.id, '角色权限设置')},
120
+ {label: '编辑', perm: 'sys-role:update', onClick: () => this.handleEdit(record)},
121
+ {label: '删除', perm: 'sys-role:delete', confirm: '是否确定删除系统角色', onClick: () => this.handleDelete(record)},
122
+ ]}
123
+ />;
131
124
  },
132
125
  },
133
126
  ]
@@ -1,5 +1,5 @@
1
1
  import {PlusOutlined} from '@ant-design/icons';
2
- import {Button, Card, Form, Input, Modal, Popconfirm, Select, Splitter, message} from 'antd';
2
+ import {Button, Card, Form, Input, Modal, Select, Splitter, message} from 'antd';
3
3
  import React from 'react';
4
4
  import {
5
5
  PermActions,
@@ -123,19 +123,15 @@ export default class extends React.Component {
123
123
  dataIndex: 'option',
124
124
  fixed: 'right',
125
125
  render: (_, record) => {
126
- return <PermActions>
127
- <Button size='small' perm='sys-user:update' onClick={() => this.handleEdit(record)}> 编辑 </Button>
128
-
129
- <Button size='small' perm='sys-user:grant-permission'
130
- onClick={() => this.permRef.current.show(record)}> 授权 </Button>
131
-
132
- <Button size='small' perm='sys-user:reset-password' onClick={() => this.resetPwd(record)}>重置密码</Button>
133
-
134
- <Popconfirm perm='sys-user:delete' title={'是否确定删除用户'}
135
- onConfirm={() => this.handleDelete(record)}>
136
- <Button size='small'>删除</Button>
137
- </Popconfirm>
138
- </PermActions>;
126
+ return <PermActions
127
+ more
128
+ actions={[
129
+ {label: '编辑', perm: 'sys-user:update', onClick: () => this.handleEdit(record)},
130
+ {label: '授权', perm: 'sys-user:grant-permission', onClick: () => this.permRef.current.show(record)},
131
+ {label: '重置密码', perm: 'sys-user:reset-password', onClick: () => this.resetPwd(record)},
132
+ {label: '删除', perm: 'sys-user:delete', confirm: '是否确定删除用户', onClick: () => this.handleDelete(record)},
133
+ ]}
134
+ />;
139
135
  },
140
136
  },
141
137
  ];
@@ -160,7 +156,7 @@ export default class extends React.Component {
160
156
 
161
157
  return <Page title="用户管理" description="管理系统用户">
162
158
  <Splitter>
163
- <Splitter.Panel defaultSize={350} style={{paddingRight: 8}}>
159
+ <Splitter.Panel defaultSize={240} style={{paddingRight: 8}}>
164
160
  <Card size='small'>
165
161
  <OrgTree onChange={this.onSelectOrg}/>
166
162
  </Card>
@@ -0,0 +1,99 @@
1
+ import React from "react";
2
+ import {Button, Card, Descriptions, Form, Input, Modal} from "antd";
3
+ import PermView from "./permView";
4
+ import {HttpUtils, history, Page} from "../../framework";
5
+
6
+ export default class extends React.Component {
7
+
8
+ formRef = React.createRef();
9
+
10
+ state = {
11
+ info: {},
12
+ changePwdOpen: false,
13
+ changePwdSuccess: false,
14
+ }
15
+
16
+ componentDidMount() {
17
+ HttpUtils.get('admin/userCenter/info').then(rs => {
18
+ this.setState({info: rs})
19
+ })
20
+ }
21
+
22
+ onPwdFinish = (values) => {
23
+ HttpUtils.post('admin/userCenter/update-pwd', values).then(() => {
24
+ this.setState({changePwdOpen: false, changePwdSuccess: true});
25
+ })
26
+ }
27
+
28
+ pwdValidator = (rule, value) => {
29
+ return new Promise((resolve, reject) => {
30
+ HttpUtils.get("admin/sysUser/pwd-strength", {password: value}, {showError: false}).then(response => {
31
+ const rs = response.data
32
+ if (!rs.success) {
33
+ reject(rs.message)
34
+ }
35
+ resolve()
36
+ })
37
+ })
38
+ }
39
+
40
+ render() {
41
+ const {info, changePwdOpen, changePwdSuccess} = this.state;
42
+ return <Page backgroundGray>
43
+
44
+ <Card title="个人信息"
45
+ style={{marginBottom: 16}}
46
+ extra={<Button onClick={() => this.setState({changePwdOpen: true})}>修改密码</Button>}>
47
+ <Descriptions column={2} size="middle">
48
+ <Descriptions.Item label="用户名称">{info.name}</Descriptions.Item>
49
+ <Descriptions.Item label="账号">{info.account}</Descriptions.Item>
50
+ <Descriptions.Item label="手机号码">{info.phone}</Descriptions.Item>
51
+ <Descriptions.Item label="用户邮箱">{info.email}</Descriptions.Item>
52
+ <Descriptions.Item label="所属单位">{info.unit}</Descriptions.Item>
53
+ <Descriptions.Item label="所属机构">{info.org}</Descriptions.Item>
54
+ <Descriptions.Item label="所属角色">{(info.roles || []).join('、') || '-'}</Descriptions.Item>
55
+ <Descriptions.Item label="创建日期">{info.createTime}</Descriptions.Item>
56
+ </Descriptions>
57
+ </Card>
58
+
59
+ <Card title="我的权限">
60
+ <PermView/>
61
+ </Card>
62
+
63
+ <Modal open={changePwdOpen} title="修改密码" okText="确定" cancelText="取消"
64
+ onCancel={() => this.setState({changePwdOpen: false})}
65
+ onOk={() => this.formRef?.submit()}>
66
+ <Form ref={this.formRef} onFinish={this.onPwdFinish} style={{maxWidth: 400}}>
67
+ <Form.Item name='oldPassword'
68
+ label='原密码'
69
+ rules={[{required: true, message: '请输入原密码'}]}
70
+ >
71
+ <Input.Password></Input.Password>
72
+ </Form.Item>
73
+ <Form.Item name='newPassword'
74
+ label='新密码'
75
+ extra={'请输入字母、数字、特殊字符'}
76
+ rules={[
77
+ {required: true, message: '请输入新密码'},
78
+ {
79
+ validator: this.pwdValidator
80
+ }
81
+ ]}
82
+ >
83
+ <Input.Password></Input.Password>
84
+ </Form.Item>
85
+ </Form>
86
+ </Modal>
87
+
88
+ <Modal open={changePwdSuccess} title="提示" okText="确定"
89
+ onCancel={() => this.setState({changePwdSuccess: false})}
90
+ onOk={() => {
91
+ this.setState({changePwdSuccess: false});
92
+ history.push('/public/login');
93
+ }}>
94
+ 修改密码成功
95
+ </Modal>
96
+
97
+ </Page>
98
+ }
99
+ }
@@ -0,0 +1,80 @@
1
+ import React from "react";
2
+ import {Empty, Table, Tag} from "antd";
3
+ import {HttpUtils, Gap} from "../../framework";
4
+
5
+ export default class PermView extends React.Component {
6
+
7
+ state = {
8
+ data: null,
9
+ }
10
+
11
+ componentDidMount() {
12
+ HttpUtils.get('admin/userCenter/perms').then(rs => {
13
+ this.setState({data: rs})
14
+ })
15
+ }
16
+
17
+ render() {
18
+ const {data} = this.state;
19
+ if (!data) {
20
+ return <Empty description='加载中...'/>;
21
+ }
22
+
23
+ const orgColumns = [
24
+ {
25
+ title: '机构名称', dataIndex: 'title', key: 'title',
26
+ },
27
+ {
28
+ title: '状态', dataIndex: 'status', key: 'status', width: 140,
29
+ render: (status) => {
30
+ if (status === 'mine') {
31
+ return <Tag color='blue'>我的机构</Tag>;
32
+ }
33
+ if (status === 'owned') {
34
+ return <Tag color='green'>已授权</Tag>;
35
+ }
36
+ return '-';
37
+ }
38
+ },
39
+ ];
40
+
41
+ const menuColumns = [
42
+ {
43
+ title: '菜单名称', dataIndex: 'title', key: 'title',
44
+ },
45
+ {
46
+ title: '权限名称', dataIndex: 'perms', key: 'perms',
47
+ render: (perms) => (perms || []).join('、') || '-',
48
+ },
49
+ {
50
+ title: '状态', dataIndex: 'status', key: 'status', width: 140,
51
+ render: (status) => {
52
+ if (status === 'all') {
53
+ return <Tag color='green'>已授权</Tag>;
54
+ }
55
+ if (status === 'partial') {
56
+ return <Tag color='orange'>部分授权</Tag>;
57
+ }
58
+ return '-';
59
+ }
60
+ },
61
+ ];
62
+
63
+ return <div>
64
+ <div style={{marginBottom: 16}}>
65
+ <span>数据权限:
66
+ <Tag color='processing'>{data.dataPermLabel || '-'}</Tag>
67
+ </span>
68
+ </div>
69
+ {data.orgRows && data.orgRows.length
70
+ ? <Table size='small' rowKey='key' columns={orgColumns} dataSource={data.orgRows}
71
+ pagination={false} expandable={{defaultExpandAllRows: true}}/>
72
+ : <Empty description='暂无机构数据'/>}
73
+ <Gap/>
74
+ {data.menuRows && data.menuRows.length
75
+ ? <Table size='small' rowKey='key' columns={menuColumns} dataSource={data.menuRows}
76
+ pagination={false} expandable={{defaultExpandAllRows: true}}/>
77
+ : <Empty description='暂无权限数据'/>}
78
+ </div>;
79
+ }
80
+ }
@@ -1,65 +0,0 @@
1
- import React from "react";
2
- import {Button, Form, Input, Modal} from "antd";
3
- import {HttpUtils, history} from "../../framework";
4
-
5
- export default class extends React.Component {
6
-
7
- state = {
8
- successModalOpen: false,
9
- };
10
-
11
- onFinish = (values) => {
12
- HttpUtils.post('admin/userCenter/update-pwd', values).then(() => {
13
- this.setState({successModalOpen: true});
14
- })
15
- }
16
-
17
- validator = (rule, value) => {
18
- return new Promise((resolve, reject) => {
19
- HttpUtils.get("admin/sysUser/pwd-strength", {password: value}, {showError: false}).then(response => {
20
- const rs = response.data
21
- if (!rs.success) {
22
- reject(rs.message)
23
- }
24
- resolve()
25
- })
26
- })
27
-
28
- }
29
-
30
- render() {
31
- return <div>
32
- <Modal open={this.state.successModalOpen} title="提示" okText="确定"
33
- onCancel={() => this.setState({successModalOpen: false})}
34
- onOk={() => {
35
- this.setState({successModalOpen: false});
36
- history.push('/public/login');
37
- }}>
38
- 修改密码成功
39
- </Modal>
40
-
41
- <Form onFinish={this.onFinish} style={{maxWidth: 400}}>
42
-
43
- <Form.Item name='newPassword'
44
- label='新密码'
45
- extra={'请输入字母、数字、特殊字符'}
46
- rules={[
47
- {required: true},
48
- {
49
- validator: this.validator
50
- }
51
- ]}
52
- >
53
- <Input.Password></Input.Password>
54
- </Form.Item>
55
-
56
- <Form.Item wrapperCol={{offset: 5}} style={{marginTop: 40}}>
57
- <Button type="primary" htmlType="submit">
58
- 确定
59
- </Button>
60
- </Form.Item>
61
- </Form>
62
-
63
- </div>
64
- }
65
- }
@@ -1,87 +0,0 @@
1
- import React from "react";
2
- import {Avatar, Card, Col, Row, Tabs} from "antd";
3
- import ChangePassword from "./changePassword";
4
- import {HttpUtils, Page} from "../../framework";
5
-
6
- export default class extends React.Component {
7
-
8
- state = {
9
- info: {}
10
- }
11
-
12
- componentDidMount() {
13
- HttpUtils.get('admin/userCenter/info').then(rs => {
14
- this.setState({info: rs})
15
- })
16
- }
17
-
18
- render() {
19
- const {info} = this.state;
20
- return <Page backgroundGray title="个人中心" description="查看和编辑个人资料">
21
-
22
- <Row gutter={[16, 16]}>
23
- <Col md={6} sm={24}>
24
- <Card title='个人信息'>
25
-
26
- <div className='flex-center' style={{marginBottom: 32}}>
27
- <Avatar size={128} title='点击修改头像'/>
28
- </div>
29
-
30
-
31
- <table className='oa-table'>
32
- <tbody>
33
- <tr>
34
- <td>用户名称</td>
35
- <td align='right'>{info.name}</td>
36
- </tr>
37
- <tr>
38
- <td>账号</td>
39
- <td align='right'>{info.account}</td>
40
- </tr>
41
- <tr>
42
- <td>手机号码</td>
43
- <td>{info.phone}</td>
44
- </tr>
45
- <tr>
46
- <td>用户邮箱</td>
47
- <td>{info.email}</td>
48
- </tr>
49
- <tr>
50
- <td>所属部门</td>
51
- <td>{info.unit} {info.dept}</td>
52
- </tr>
53
- <tr>
54
- <td>所属角色</td>
55
- <td>{info.roles}</td>
56
- </tr>
57
- <tr>
58
- <td>创建日期</td>
59
- <td>{info.createTime}</td>
60
- </tr>
61
- </tbody>
62
- </table>
63
- </Card>
64
- </Col>
65
- <Col md={18} sm={24}>
66
- <Card title='个人设置'>
67
- <Tabs
68
- items={[
69
- {
70
- label: '修改密码', key: 'pwd', children: <div>
71
- <ChangePassword/>
72
- </div>
73
- },
74
-
75
- ]}
76
- tabPlacement='left'
77
- >
78
- </Tabs>
79
- </Card>
80
- </Col>
81
- </Row>
82
-
83
-
84
- </Page>
85
-
86
- }
87
- }