@jiangood/open-admin 3.1.7 → 3.1.8
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/biz/PermActions.tsx +1 -1
- package/src/framework/components/DownloadModal/index.tsx +3 -3
- package/src/framework/components/FormModal/index.tsx +4 -4
- package/src/framework/components/NamedIcon/index.tsx +1 -1
- package/src/framework/components/Page/index.tsx +18 -4
- package/src/framework/components/PageLoading/index.tsx +2 -2
- package/src/framework/components/ProTable/index.less +44 -0
- package/src/framework/components/ProTable/index.tsx +54 -13
- package/src/framework/fields/BaseRemoteSelect/index.tsx +4 -4
- package/src/framework/fields/FieldDateRange/index.tsx +1 -1
- package/src/framework/fields/FieldDictSelect/index.tsx +1 -1
- package/src/framework/fields/FieldNumberRange/index.tsx +1 -1
- package/src/framework/fields/FieldUploadImage/index.tsx +4 -4
- package/src/framework/utils/UrlUtils.ts +1 -1
- package/src/layouts/admin/index.jsx +1 -1
- package/src/layouts/admin/index.less +25 -1
- package/src/pages/article/$code.jsx +2 -2
- package/src/pages/index.jsx +1 -1
- package/src/pages/system/article/index.jsx +13 -15
- package/src/pages/system/codegen/index.jsx +1 -3
- package/src/pages/system/dict/index.jsx +2 -2
- package/src/pages/system/file/index.jsx +3 -3
- package/src/pages/system/job/index.jsx +13 -13
- package/src/pages/system/log/index.jsx +2 -2
- package/src/pages/system/org/index.jsx +5 -5
- package/src/pages/system/role/index.jsx +12 -13
- package/src/pages/system/role/rolePerm.jsx +1 -1
- package/src/pages/system/user/index.jsx +7 -7
- package/src/pages/system/user/userPerm.jsx +2 -2
- package/src/pages/userCenter/permView.jsx +2 -2
package/package.json
CHANGED
|
@@ -101,7 +101,7 @@ export class PermActions extends React.Component<PermActionsProps> {
|
|
|
101
101
|
|
|
102
102
|
if (!more || visible.length <= 1) {
|
|
103
103
|
const buttons = visible.map((action, index) => (
|
|
104
|
-
<React.Fragment key={index}>{this.renderButton(action, size)}</React.Fragment>
|
|
104
|
+
<React.Fragment key={index}>{this.renderButton(action, size)}</React.Fragment>
|
|
105
105
|
));
|
|
106
106
|
return <Space>{buttons}</Space>;
|
|
107
107
|
}
|
|
@@ -40,8 +40,8 @@ export interface DownloadModalProps {
|
|
|
40
40
|
onFinish?: () => void;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export class DownloadModal extends React.Component<DownloadModalProps, ModalState> {
|
|
44
|
-
private abortController: AbortController | null = null;
|
|
43
|
+
export class DownloadModal extends React.Component<DownloadModalProps, ModalState> {
|
|
44
|
+
private abortController: AbortController | null = null;
|
|
45
45
|
private lastOptions: DownloadOptions | null = null;
|
|
46
46
|
private lastTime: number = 0;
|
|
47
47
|
private lastLoaded: number = 0;
|
|
@@ -61,7 +61,7 @@ export class DownloadModal extends React.Component<DownloadModalProps, ModalStat
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
download = (options: DownloadOptions) => {
|
|
64
|
+
download = (options: DownloadOptions) => {
|
|
65
65
|
this.startDownload(options);
|
|
66
66
|
};
|
|
67
67
|
|
|
@@ -12,9 +12,9 @@ export interface FormModalProps {
|
|
|
12
12
|
children?: React.ReactNode
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export default class FormModal extends React.Component<FormModalProps> {
|
|
15
|
+
export default class FormModal extends React.Component<FormModalProps> {
|
|
16
16
|
|
|
17
|
-
readonly state = {
|
|
17
|
+
readonly state = {
|
|
18
18
|
visible: false,
|
|
19
19
|
confirmLoading: false,
|
|
20
20
|
}
|
|
@@ -22,11 +22,11 @@ export default class FormModal extends React.Component<FormModalProps> { // NOSO
|
|
|
22
22
|
private pendingValues: Record<string, unknown> | null = null
|
|
23
23
|
private formRef = React.createRef<FormInstance>()
|
|
24
24
|
|
|
25
|
-
get formInstance() {
|
|
25
|
+
get formInstance() {
|
|
26
26
|
return this.formRef.current
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
open = (values?: Record<string, unknown>) => {
|
|
29
|
+
open = (values?: Record<string, unknown>) => {
|
|
30
30
|
this.pendingValues = values || {}
|
|
31
31
|
this.setState({visible: true})
|
|
32
32
|
}
|
|
@@ -12,7 +12,7 @@ const iconLoaders: Record<string, () => Promise<{ default: IconComponent }>> = {
|
|
|
12
12
|
for (const [path, loader] of Object.entries(
|
|
13
13
|
import.meta.glob('/node_modules/@ant-design/icons/es/icons/*.js')
|
|
14
14
|
)) {
|
|
15
|
-
const name = /([^/]+)\.js$/.exec(path)?.[1];
|
|
15
|
+
const name = /([^/]+)\.js$/.exec(path)?.[1];
|
|
16
16
|
if (name) {
|
|
17
17
|
iconLoaders[name] = loader as () => Promise<{ default: IconComponent }>;
|
|
18
18
|
}
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Typography } from "antd";
|
|
2
|
+
import { Space, Typography } from "antd";
|
|
3
3
|
import './index.less'
|
|
4
4
|
import {getToken} from "../../config";
|
|
5
|
-
import { PermActions } from "../../biz/PermActions";
|
|
5
|
+
import { PermActions, type PermAction } from "../../biz/PermActions";
|
|
6
|
+
import { PermUtils } from "../../utils";
|
|
7
|
+
|
|
8
|
+
/** 兼容旧写法:按子节点的 perm 属性过滤(新代码请用 PermAction[]) */
|
|
9
|
+
function renderActionNodes(nodes: React.ReactNode) {
|
|
10
|
+
const visible = React.Children.toArray(nodes).filter((node) => {
|
|
11
|
+
const perm = (node as React.ReactElement<{ perm?: string }>)?.props?.perm;
|
|
12
|
+
return !perm || PermUtils.hasPermission(perm);
|
|
13
|
+
});
|
|
14
|
+
if (visible.length === 0) return null;
|
|
15
|
+
return <Space>{visible}</Space>;
|
|
16
|
+
}
|
|
6
17
|
|
|
7
18
|
interface PageProps {
|
|
8
19
|
padding?: boolean;
|
|
@@ -10,7 +21,8 @@ interface PageProps {
|
|
|
10
21
|
debug?: boolean;
|
|
11
22
|
title?: React.ReactNode;
|
|
12
23
|
description?: React.ReactNode;
|
|
13
|
-
|
|
24
|
+
/** 右侧操作区:推荐传 PermAction[](数据驱动),兼容带 perm 属性的节点 */
|
|
25
|
+
actions?: PermAction[] | React.ReactNode;
|
|
14
26
|
children?: React.ReactNode;
|
|
15
27
|
}
|
|
16
28
|
|
|
@@ -44,7 +56,9 @@ export class Page extends React.Component<PageProps> {
|
|
|
44
56
|
{description && <div><Typography.Text type="secondary">{description}</Typography.Text></div>}
|
|
45
57
|
</div>
|
|
46
58
|
</div>
|
|
47
|
-
{actions &&
|
|
59
|
+
{actions != null && (Array.isArray(actions)
|
|
60
|
+
? <PermActions actions={actions}/>
|
|
61
|
+
: renderActionNodes(actions))}
|
|
48
62
|
</div>
|
|
49
63
|
</div>
|
|
50
64
|
)}
|
|
@@ -9,7 +9,7 @@ export interface PageLoadingProps {
|
|
|
9
9
|
messages?: string[];
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function PageLoading(props: PageLoadingProps) {
|
|
12
|
+
export function PageLoading(props: PageLoadingProps) {
|
|
13
13
|
const titles = props.messages || (props.message ? [props.message] : ['页面加载中...']);
|
|
14
14
|
const primaryColor = getToken().colorPrimary ?? "";
|
|
15
15
|
|
|
@@ -22,7 +22,7 @@ export function PageLoading(props: PageLoadingProps) { // NOSONAR: 仅读取 pro
|
|
|
22
22
|
<div className="oa-page-loading-messages">
|
|
23
23
|
{titles.map((msg, i) => (
|
|
24
24
|
<Alert
|
|
25
|
-
key={i}
|
|
25
|
+
key={i}
|
|
26
26
|
title={<span style={{color: primaryColor}}>{msg}</span>}
|
|
27
27
|
type="info"
|
|
28
28
|
showIcon
|
|
@@ -69,6 +69,11 @@
|
|
|
69
69
|
gap: 8px;
|
|
70
70
|
flex: 1;
|
|
71
71
|
min-width: 0;
|
|
72
|
+
|
|
73
|
+
/* 仅内联搜索时允许换行,存量工具栏布局与之前完全一致 */
|
|
74
|
+
&.has-inline-search {
|
|
75
|
+
flex-wrap: wrap;
|
|
76
|
+
}
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
.pro-table-toolbar-right {
|
|
@@ -81,6 +86,45 @@
|
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
|
89
|
+
/* searchInline:搜索控件内联在工具栏左侧,与操作按钮同一行 */
|
|
90
|
+
.pro-table-search-inline {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 8px;
|
|
94
|
+
flex-wrap: wrap;
|
|
95
|
+
margin: 0;
|
|
96
|
+
|
|
97
|
+
/* 定宽由 --oa-search-inline-width 控制,业务侧可覆盖;noStyle 的输入控件同样收敛 */
|
|
98
|
+
> .ant-form-item,
|
|
99
|
+
> .ant-input,
|
|
100
|
+
> .ant-input-affix-wrapper,
|
|
101
|
+
> .ant-picker,
|
|
102
|
+
> .ant-select {
|
|
103
|
+
width: var(--oa-search-inline-width, 260px);
|
|
104
|
+
max-width: 100%;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.ant-form-item {
|
|
108
|
+
margin: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* 视觉隐藏的提交按钮:保留回车提交能力,且不占位(不用 display:none,避免浏览器忽略默认提交按钮) */
|
|
112
|
+
.pro-table-search-submit {
|
|
113
|
+
position: absolute;
|
|
114
|
+
width: 1px;
|
|
115
|
+
height: 1px;
|
|
116
|
+
min-width: 0;
|
|
117
|
+
padding: 0;
|
|
118
|
+
margin: -1px;
|
|
119
|
+
border: 0;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
white-space: nowrap;
|
|
122
|
+
clip-path: inset(50%);
|
|
123
|
+
opacity: 0;
|
|
124
|
+
pointer-events: none;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
84
128
|
/* 桌面窄窗口下收敛搜索栏列数,避免标签+输入挤压/溢出 */
|
|
85
129
|
@media (max-width: 1400px) {
|
|
86
130
|
.filter-bar {
|
|
@@ -57,8 +57,14 @@ export interface ProTableProps<T = unknown> {
|
|
|
57
57
|
onChange?: (selectedRowKeys: React.Key[], selectedRows: T[]) => void;
|
|
58
58
|
};
|
|
59
59
|
defaultPageSize?: number;
|
|
60
|
-
/** 搜索栏每行列数,默认 4 */
|
|
60
|
+
/** 搜索栏每行列数,默认 4(searchInline 时忽略) */
|
|
61
61
|
searchFormCols?: number;
|
|
62
|
+
/**
|
|
63
|
+
* 搜索表单内联渲染到工具栏左侧,与操作按钮同一行(单条件页面省一行空间)。
|
|
64
|
+
* 内联模式不渲染「查询/重置」按钮:回车提交,条件清空后自动回到无筛选状态。
|
|
65
|
+
* 默认 false = 现有独立筛选卡
|
|
66
|
+
*/
|
|
67
|
+
searchInline?: boolean;
|
|
62
68
|
/** 搜索表单渲染函数,返回 Form.Item 列表 */
|
|
63
69
|
searchFormRender?: () => React.ReactNode;
|
|
64
70
|
scrollY?: number | string;
|
|
@@ -201,13 +207,15 @@ this.setState({loading: true})
|
|
|
201
207
|
// 有筛选条件时区分「无匹配结果」与「暂无数据」,避免业务人员误以为数据丢失
|
|
202
208
|
const hasFilter = Object.keys(this.state.params || {}).length > 0
|
|
203
209
|
|
|
210
|
+
const inlineSearch = this.renderInlineSearch()
|
|
204
211
|
|
|
205
212
|
return <div className={'oa-pro-table '} id={this.id}>
|
|
206
213
|
{this.renderForm()}
|
|
207
214
|
<div className="pro-table-wrapper">
|
|
208
|
-
{(toolBarRender || toolBarRightRender) && <div className="pro-table-toolbar">
|
|
209
|
-
{toolBarRender && <div className=
|
|
210
|
-
{
|
|
215
|
+
{(inlineSearch || toolBarRender || toolBarRightRender) && <div className="pro-table-toolbar">
|
|
216
|
+
{(inlineSearch || toolBarRender) && <div className={'pro-table-toolbar-left' + (inlineSearch ? ' has-inline-search' : '')}>
|
|
217
|
+
{inlineSearch}
|
|
218
|
+
{toolBarRender && this.getToolBarRenderNode(toolBarRender)}
|
|
211
219
|
</div>}
|
|
212
220
|
{toolBarRightRender && <div className="pro-table-toolbar-right">
|
|
213
221
|
{this.getToolBarRenderNode(toolBarRightRender)}
|
|
@@ -258,10 +266,9 @@ this.setState({loading: true})
|
|
|
258
266
|
|
|
259
267
|
|
|
260
268
|
renderForm = () => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const searchContent = this.props.searchFormRender?.()
|
|
269
|
+
// 内联模式:搜索表单改由工具栏渲染(见 renderInlineSearch)
|
|
270
|
+
if (this.props.searchInline) return
|
|
271
|
+
const searchContent = this.getSearchContent()
|
|
265
272
|
if (!searchContent) return
|
|
266
273
|
|
|
267
274
|
return (
|
|
@@ -269,10 +276,7 @@ this.setState({loading: true})
|
|
|
269
276
|
className="filter-bar"
|
|
270
277
|
style={{'--oa-filter-cols': this.props.searchFormCols ?? 4} as React.CSSProperties}
|
|
271
278
|
onFinish={(values) => this.onSearch(values)}
|
|
272
|
-
ref={
|
|
273
|
-
this.formRef.current = instance;
|
|
274
|
-
if (this.props.formRef) this.props.formRef.current = instance;
|
|
275
|
-
}}
|
|
279
|
+
ref={this.bindFormRef}
|
|
276
280
|
>
|
|
277
281
|
{searchContent}
|
|
278
282
|
<div className="filter-actions">
|
|
@@ -286,6 +290,43 @@ this.setState({loading: true})
|
|
|
286
290
|
)
|
|
287
291
|
};
|
|
288
292
|
|
|
293
|
+
/** 内联搜索:单条件页面把搜索控件放到工具栏左侧,回车即查询,复用 onSearch 的取数逻辑 */
|
|
294
|
+
renderInlineSearch = () => {
|
|
295
|
+
if (!this.props.searchInline) return
|
|
296
|
+
const searchContent = this.getSearchContent()
|
|
297
|
+
if (!searchContent) return
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<Form
|
|
301
|
+
className="pro-table-search-inline"
|
|
302
|
+
onFinish={(values) => this.onSearch(values)}
|
|
303
|
+
onValuesChange={(changedValues) => {
|
|
304
|
+
// 条件被清空(allowClear 等)后自动回到无筛选状态,否则用户还要再按一次回车
|
|
305
|
+
const cleared = Object.values(changedValues).some(v => v == null || v === '' || (Array.isArray(v) && v.length === 0))
|
|
306
|
+
if (cleared) this.formRef.current?.submit()
|
|
307
|
+
}}
|
|
308
|
+
ref={this.bindFormRef}
|
|
309
|
+
>
|
|
310
|
+
{searchContent}
|
|
311
|
+
{/* 视觉隐藏的提交按钮:多字段内联时也能回车提交 */}
|
|
312
|
+
<Button className="pro-table-search-submit" htmlType="submit" tabIndex={-1} aria-hidden/>
|
|
313
|
+
</Form>
|
|
314
|
+
)
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/** 校验已废弃的 children 用法,返回搜索表单内容(无搜索表单时返回空) */
|
|
318
|
+
getSearchContent = () => {
|
|
319
|
+
if (this.props.children) {
|
|
320
|
+
throw new Error('[ProTable] children 已废弃,请使用 searchFormRender prop 替代')
|
|
321
|
+
}
|
|
322
|
+
return this.props.searchFormRender?.()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
bindFormRef = (instance: FormInstance | null) => {
|
|
326
|
+
this.formRef.current = instance;
|
|
327
|
+
if (this.props.formRef) this.props.formRef.current = instance;
|
|
328
|
+
}
|
|
329
|
+
|
|
289
330
|
getToolBarRenderNode(toolBarRender: ProTableToolBarRender<T>) {
|
|
290
331
|
if (!toolBarRender) {
|
|
291
332
|
return
|
|
@@ -323,7 +364,7 @@ this.setState({loading: true})
|
|
|
323
364
|
this.setState({params: values, current: 1, sorter: {}}, this.loadData)
|
|
324
365
|
}
|
|
325
366
|
|
|
326
|
-
changeFormValues = (values: Record<string, unknown>) => {
|
|
367
|
+
changeFormValues = (values: Record<string, unknown>) => {
|
|
327
368
|
if (this.formRef.current) {
|
|
328
369
|
this.formRef.current.resetFields()
|
|
329
370
|
this.formRef.current.setFieldsValue(values)
|
|
@@ -21,8 +21,8 @@ export interface BaseRemoteSelectProps {
|
|
|
21
21
|
* 处理通用的数据加载、防抖搜索、竞态处理、错误提示逻辑。
|
|
22
22
|
* 子类只需覆写 getLoadParams() 和 render()。
|
|
23
23
|
*/
|
|
24
|
-
export class BaseRemoteSelect<P extends BaseRemoteSelectProps = BaseRemoteSelectProps> extends React.Component<P, BaseRemoteSelectState> {
|
|
25
|
-
private fetchIdRef: number = 0;
|
|
24
|
+
export class BaseRemoteSelect<P extends BaseRemoteSelectProps = BaseRemoteSelectProps> extends React.Component<P, BaseRemoteSelectState> {
|
|
25
|
+
private fetchIdRef: number = 0;
|
|
26
26
|
private loadDataDebounce: ReturnType<typeof debounce>;
|
|
27
27
|
|
|
28
28
|
static readonly defaultProps = {
|
|
@@ -117,14 +117,14 @@ export class BaseRemoteSelect<P extends BaseRemoteSelectProps = BaseRemoteSelect
|
|
|
117
117
|
|
|
118
118
|
// ========== Helper 方法 ==========
|
|
119
119
|
|
|
120
|
-
getShowSearch(): { filterOption: false; onSearch: (value: string) => void } {
|
|
120
|
+
getShowSearch(): { filterOption: false; onSearch: (value: string) => void } {
|
|
121
121
|
return {
|
|
122
122
|
filterOption: false,
|
|
123
123
|
onSearch: this.handleSearch,
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
getNotFoundContent(): React.ReactNode {
|
|
127
|
+
getNotFoundContent(): React.ReactNode {
|
|
128
128
|
return this.state.loading ? <Spin size="small" /> : '数据为空';
|
|
129
129
|
}
|
|
130
130
|
}
|
|
@@ -11,7 +11,7 @@ import type {FieldProps} from '../types';
|
|
|
11
11
|
const SP = StringUtils.ISO_SPLITTER;
|
|
12
12
|
|
|
13
13
|
/** 日期范围值,形如 "2024-01-01/2024-01-31"(起止以 / 分隔) */
|
|
14
|
-
export type FieldDateRangeValue = string;
|
|
14
|
+
export type FieldDateRangeValue = string;
|
|
15
15
|
|
|
16
16
|
export interface FieldDateRangeProps extends FieldProps<FieldDateRangeValue> {
|
|
17
17
|
/** 日期类型,如 YYYY-MM-DD、YYYY-MM、HH:mm:ss 等,默认 YYYY-MM-DD */
|
|
@@ -12,7 +12,7 @@ export class FieldDictSelect extends React.Component<FieldDictSelectProps> {
|
|
|
12
12
|
render() {
|
|
13
13
|
const {value, typeCode} = this.props;
|
|
14
14
|
const options = DictUtils.dictOptions(typeCode);
|
|
15
|
-
const strValue = value == null ? null : String(value);
|
|
15
|
+
const strValue = value == null ? null : String(value);
|
|
16
16
|
|
|
17
17
|
return <Select value={strValue}
|
|
18
18
|
onChange={this.props.onChange}
|
|
@@ -6,7 +6,7 @@ import type {FieldProps} from '../types';
|
|
|
6
6
|
const SP = StringUtils.ISO_SPLITTER;
|
|
7
7
|
|
|
8
8
|
/** 数字范围值,形如 "1/100"(起止以 / 分隔) */
|
|
9
|
-
export type FieldNumberRangeValue = string;
|
|
9
|
+
export type FieldNumberRangeValue = string;
|
|
10
10
|
|
|
11
11
|
export type RangePartValue = number | string | null;
|
|
12
12
|
|
|
@@ -91,7 +91,7 @@ function readDims(url: string): Promise<Dims> {
|
|
|
91
91
|
|
|
92
92
|
function compressToFile(source: Blob, options: {maxWidth?: number; maxHeight?: number; width?: number; height?: number; quality?: number}): Promise<File> {
|
|
93
93
|
return new Promise((resolve, reject) => {
|
|
94
|
-
new Compressor(source, {
|
|
94
|
+
new Compressor(source, {
|
|
95
95
|
maxWidth: options.maxWidth,
|
|
96
96
|
maxHeight: options.maxHeight,
|
|
97
97
|
width: options.width,
|
|
@@ -139,9 +139,9 @@ async function compressToTarget(source: Blob, maxWidth: number | undefined, targ
|
|
|
139
139
|
return best;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
export class FieldUploadImage extends React.Component<FieldUploadImageProps, FieldUploadImageState> {
|
|
142
|
+
export class FieldUploadImage extends React.Component<FieldUploadImageProps, FieldUploadImageState> {
|
|
143
143
|
|
|
144
|
-
state: FieldUploadImageState = {
|
|
144
|
+
state: FieldUploadImageState = {
|
|
145
145
|
// 传入的参数
|
|
146
146
|
maxCount: 1,
|
|
147
147
|
thumbWidth: 300,
|
|
@@ -170,7 +170,7 @@ export class FieldUploadImage extends React.Component<FieldUploadImageProps, Fie
|
|
|
170
170
|
if (props.value) this.state.objectNames = props.value.split(',');
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
componentDidUpdate(prevProps: FieldUploadImageProps, prevState: FieldUploadImageState) {
|
|
173
|
+
componentDidUpdate(prevProps: FieldUploadImageProps, prevState: FieldUploadImageState) {
|
|
174
174
|
const next: Partial<FieldUploadImageState> = {};
|
|
175
175
|
if (this.props.maxCount !== prevProps.maxCount) next.maxCount = this.props.maxCount;
|
|
176
176
|
if (this.props.thumbWidth !== prevProps.thumbWidth) next.thumbWidth = this.props.thumbWidth;
|
|
@@ -67,6 +67,6 @@ export class UrlUtils {
|
|
|
67
67
|
static contextPath(path: string): string {
|
|
68
68
|
const base = import.meta.env.VITE_SERVER_SERVLET_CONTEXT_PATH;
|
|
69
69
|
if (base === '/' || base === '') return path;
|
|
70
|
-
return base.replace(/\/+$/, '') + path;
|
|
70
|
+
return base.replace(/\/+$/, '') + path;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -112,7 +112,7 @@ export default class AdminLayout extends React.Component {
|
|
|
112
112
|
this.setState({menuLoading: false})
|
|
113
113
|
})
|
|
114
114
|
}
|
|
115
|
-
actionRef = React.createRef()
|
|
115
|
+
actionRef = React.createRef()
|
|
116
116
|
|
|
117
117
|
classifyMenus = (menuTree) => {
|
|
118
118
|
const topMenus = [];
|
|
@@ -95,7 +95,11 @@
|
|
|
95
95
|
display: flex;
|
|
96
96
|
align-items: center;
|
|
97
97
|
gap: 8px;
|
|
98
|
-
|
|
98
|
+
/* 与顶栏同高,保证 logo 基线与一级菜单对齐 */
|
|
99
|
+
height: 48px;
|
|
100
|
+
padding: 0 16px;
|
|
101
|
+
/* 底边线:与右侧顶栏/标签栏分隔线连成一线;色值由侧栏文字变量派生,避免硬编码 */
|
|
102
|
+
border-bottom: 1px solid color-mix(in srgb, var(--oa-sider-text, rgba(255, 255, 255, 0.85)) 14%, transparent);
|
|
99
103
|
|
|
100
104
|
.logo-img {
|
|
101
105
|
height: 36px;
|
|
@@ -115,10 +119,25 @@
|
|
|
115
119
|
margin: 0;
|
|
116
120
|
font-size: 14px;
|
|
117
121
|
line-height: 1;
|
|
122
|
+
/* 允许在 flex 中收缩,长站点名省略号截断而不溢出侧栏 */
|
|
123
|
+
min-width: 0;
|
|
124
|
+
overflow: hidden;
|
|
118
125
|
white-space: nowrap;
|
|
126
|
+
|
|
127
|
+
a {
|
|
128
|
+
display: block;
|
|
129
|
+
overflow: hidden;
|
|
130
|
+
text-overflow: ellipsis;
|
|
131
|
+
}
|
|
119
132
|
}
|
|
120
133
|
}
|
|
121
134
|
|
|
135
|
+
/* 折叠态:logo 居中,去掉左侧内边距 */
|
|
136
|
+
&.ant-layout-sider-collapsed .sider-header {
|
|
137
|
+
justify-content: center;
|
|
138
|
+
padding-inline: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
122
141
|
&.ant-layout-sider-collapsed .sider-header h3 {
|
|
123
142
|
display: none;
|
|
124
143
|
}
|
|
@@ -129,6 +148,11 @@
|
|
|
129
148
|
scrollbar-width: thin;
|
|
130
149
|
scrollbar-color: unset;
|
|
131
150
|
|
|
151
|
+
/* 菜单分隔线:加大上下间距,让不同菜单分组更清晰 */
|
|
152
|
+
.ant-menu-item-divider {
|
|
153
|
+
margin-block: 8px;
|
|
154
|
+
}
|
|
155
|
+
|
|
132
156
|
.ant-menu-submenu-selected {
|
|
133
157
|
&>div {
|
|
134
158
|
color: var(--oa-sider-text, rgba(255, 255, 255, 0.85));
|
|
@@ -26,13 +26,13 @@ export default class ArticlePage extends React.Component {
|
|
|
26
26
|
const {article, loading} = this.state
|
|
27
27
|
|
|
28
28
|
if (loading) {
|
|
29
|
-
return <Page
|
|
29
|
+
return <Page>
|
|
30
30
|
<div>加载中...</div>
|
|
31
31
|
</Page>
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
if (!article) {
|
|
35
|
-
return <Page
|
|
35
|
+
return <Page>
|
|
36
36
|
<div>文章不存在或已禁用</div>
|
|
37
37
|
</Page>
|
|
38
38
|
}
|
package/src/pages/index.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {PlusOutlined} from '@ant-design/icons'
|
|
2
|
-
import {
|
|
1
|
+
import {PlusOutlined, SearchOutlined} from '@ant-design/icons'
|
|
2
|
+
import {Form, Input, InputNumber} from 'antd'
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import {
|
|
5
5
|
DictUtils,
|
|
@@ -106,26 +106,24 @@ export default class ArticleListPage extends React.Component {
|
|
|
106
106
|
]
|
|
107
107
|
|
|
108
108
|
render() {
|
|
109
|
-
return <Page
|
|
110
|
-
title="文章管理"
|
|
111
|
-
description="管理系统文章,如关于、帮助等页面"
|
|
112
|
-
>
|
|
109
|
+
return <Page>
|
|
113
110
|
<ProTable
|
|
114
111
|
actionRef={this.tableRef}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
searchInline
|
|
113
|
+
toolBarRightRender={() => (
|
|
114
|
+
<PermActions actions={[
|
|
115
|
+
{label: '新增', type: 'primary', icon: <PlusOutlined/>, perm: 'article:create', onClick: this.handleAdd},
|
|
116
|
+
]}/>
|
|
119
117
|
)}
|
|
120
118
|
request={(params) => HttpClient.get('admin/article/page', params)}
|
|
121
119
|
columns={this.columns}
|
|
122
|
-
searchFormRender={() => (
|
|
120
|
+
searchFormRender={() => (
|
|
123
121
|
<>
|
|
124
|
-
<Form.Item
|
|
125
|
-
<Input/>
|
|
122
|
+
<Form.Item name='code' noStyle>
|
|
123
|
+
<Input prefix={<SearchOutlined/>} placeholder='文章编码' allowClear/>
|
|
126
124
|
</Form.Item>
|
|
127
|
-
<Form.Item
|
|
128
|
-
<Input/>
|
|
125
|
+
<Form.Item name='title' noStyle>
|
|
126
|
+
<Input placeholder='文章标题' allowClear/>
|
|
129
127
|
</Form.Item>
|
|
130
128
|
</>
|
|
131
129
|
)}
|
|
@@ -96,9 +96,7 @@ export default class CodegenPage extends React.Component {
|
|
|
96
96
|
|
|
97
97
|
render() {
|
|
98
98
|
const selected = this.selectedOption()
|
|
99
|
-
return <Page
|
|
100
|
-
description='扫描实体,生成 Repository / Service / Controller / 前端页面 / 菜单 代码并写入项目源码目录'
|
|
101
|
-
actions={
|
|
99
|
+
return <Page actions={
|
|
102
100
|
<Space>
|
|
103
101
|
<Perm code='sys-codegen:generate'>
|
|
104
102
|
<Button type='primary' loading={this.state.loading} onClick={this.handlePreview}>预览</Button>
|
|
@@ -164,7 +164,7 @@ export default class DictPage extends React.Component {
|
|
|
164
164
|
const {selectedType, selectedTypeCode} = this.state
|
|
165
165
|
const hasTypeSelected = selectedType != null && selectedTypeCode != null
|
|
166
166
|
|
|
167
|
-
return <Page
|
|
167
|
+
return <Page>
|
|
168
168
|
<Splitter>
|
|
169
169
|
<Splitter.Panel defaultSize={300} style={{paddingRight: 8}}>
|
|
170
170
|
<Card loading={this.state.treeLoading}
|
|
@@ -269,7 +269,7 @@ export default class DictPage extends React.Component {
|
|
|
269
269
|
allowClear
|
|
270
270
|
maxLength={20}
|
|
271
271
|
options={COLOR_OPTIONS}
|
|
272
|
-
filterOption={(input, option) =>
|
|
272
|
+
filterOption={(input, option) =>
|
|
273
273
|
(option.value + option.label).toLowerCase().includes(input.toLowerCase())
|
|
274
274
|
}
|
|
275
275
|
placeholder='如 SUCCESS、#ff0000'
|
|
@@ -32,7 +32,7 @@ export default class FilePage extends React.Component {
|
|
|
32
32
|
title: '缩略图',
|
|
33
33
|
dataIndex: 'objectName',
|
|
34
34
|
width: 80,
|
|
35
|
-
render: (objectName, record) => {
|
|
35
|
+
render: (objectName, record) => {
|
|
36
36
|
if (record.type !== 'image') {
|
|
37
37
|
return '-'
|
|
38
38
|
}
|
|
@@ -151,7 +151,7 @@ export default class FilePage extends React.Component {
|
|
|
151
151
|
<ProTable
|
|
152
152
|
actionRef={this.tableRef}
|
|
153
153
|
rowSelection={{}}
|
|
154
|
-
|
|
154
|
+
toolBarRightRender={(params, selection) => (
|
|
155
155
|
<Space>
|
|
156
156
|
<Button type='primary' icon={<CloudUploadOutlined/>} onClick={() => this.setState({formOpen: true})}>上传文件</Button>
|
|
157
157
|
<PermActions
|
|
@@ -171,7 +171,7 @@ export default class FilePage extends React.Component {
|
|
|
171
171
|
}}
|
|
172
172
|
|
|
173
173
|
columns={this.columns}
|
|
174
|
-
searchFormRender={() => (
|
|
174
|
+
searchFormRender={() => (
|
|
175
175
|
<>
|
|
176
176
|
<Form.Item label='文件名' name='originName'>
|
|
177
177
|
<Input/>
|
|
@@ -2,7 +2,6 @@ import React from 'react'
|
|
|
2
2
|
import {
|
|
3
3
|
Alert,
|
|
4
4
|
AutoComplete,
|
|
5
|
-
Button,
|
|
6
5
|
Divider,
|
|
7
6
|
Form,
|
|
8
7
|
Input,
|
|
@@ -12,7 +11,7 @@ import {
|
|
|
12
11
|
Switch,
|
|
13
12
|
Tag
|
|
14
13
|
} from 'antd'
|
|
15
|
-
import {PlusOutlined} from "@ant-design/icons";
|
|
14
|
+
import {PlusOutlined, SearchOutlined} from "@ant-design/icons";
|
|
16
15
|
import {FormModal, getMessageApi, HttpClient, Page, PermActions, ProTable, UrlUtils, ValueType} from "../../../framework";
|
|
17
16
|
|
|
18
17
|
|
|
@@ -200,21 +199,22 @@ export default class JobPage extends React.Component {
|
|
|
200
199
|
return <Page>
|
|
201
200
|
<ProTable
|
|
202
201
|
actionRef={this.tableRef}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
<
|
|
207
|
-
|
|
202
|
+
searchInline
|
|
203
|
+
toolBarRightRender={() => (
|
|
204
|
+
<PermActions actions={[
|
|
205
|
+
{label: '新增', type: 'primary', icon: <PlusOutlined/>, perm: 'job:create', onClick: () => this.handleAdd()},
|
|
206
|
+
{label: '查看状态', perm: 'job:read', onClick: this.showStatus},
|
|
207
|
+
]}/>
|
|
208
208
|
)}
|
|
209
209
|
request={(params) => HttpClient.get('admin/job/page', params)}
|
|
210
210
|
columns={this.columns}
|
|
211
|
-
searchFormRender={() => (
|
|
211
|
+
searchFormRender={() => (
|
|
212
212
|
<>
|
|
213
|
-
<Form.Item
|
|
214
|
-
<Input/>
|
|
213
|
+
<Form.Item name='name' noStyle>
|
|
214
|
+
<Input prefix={<SearchOutlined/>} placeholder='任务名称' allowClear/>
|
|
215
215
|
</Form.Item>
|
|
216
|
-
<Form.Item
|
|
217
|
-
<Input/>
|
|
216
|
+
<Form.Item name='jobClass' noStyle>
|
|
217
|
+
<Input placeholder='执行类' allowClear/>
|
|
218
218
|
</Form.Item>
|
|
219
219
|
</>
|
|
220
220
|
)}
|
|
@@ -348,7 +348,7 @@ export default class JobPage extends React.Component {
|
|
|
348
348
|
const option = this.state.jobClassOptions.find(o => o.value === changed.jobClass)
|
|
349
349
|
if (option) {
|
|
350
350
|
const {label} = option;
|
|
351
|
-
const match = label.match(/[((](.*?)[))]/);
|
|
351
|
+
const match = label.match(/[((](.*?)[))]/);
|
|
352
352
|
if (match) { // 取括号内中文描述设置为name
|
|
353
353
|
this.modalRef.current.formInstance.setFieldValue("name", match[1])
|
|
354
354
|
}
|
|
@@ -53,11 +53,11 @@ export default class LogPage extends React.Component {
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
render() {
|
|
56
|
-
return <Page
|
|
56
|
+
return <Page>
|
|
57
57
|
<ProTable
|
|
58
58
|
request={(params) => HttpClient.get('admin/sysLog/page', params)}
|
|
59
59
|
columns={this.columns}
|
|
60
|
-
searchFormRender={() => (
|
|
60
|
+
searchFormRender={() => (
|
|
61
61
|
<>
|
|
62
62
|
<Form.Item label='操作' name='operation'>
|
|
63
63
|
<Input/>
|
|
@@ -94,13 +94,13 @@ export default class OrgPage extends React.Component {
|
|
|
94
94
|
const params = this.state.params
|
|
95
95
|
const canSort = PermUtils.hasPermission('sys-org:update')
|
|
96
96
|
|
|
97
|
-
return <Page
|
|
98
|
-
<Button type='primary' perm='sys-org:create' icon={<PlusOutlined/>} onClick={this.handleAdd}>新增</Button>
|
|
99
|
-
}>
|
|
97
|
+
return <Page>
|
|
100
98
|
<Splitter>
|
|
101
99
|
<Splitter.Panel defaultSize={400} style={{paddingRight: 8}}>
|
|
102
100
|
|
|
103
|
-
|
|
101
|
+
|
|
102
|
+
<Card title="机构树" extra={<Button type='primary' perm='sys-org:create'
|
|
103
|
+
icon={<PlusOutlined/>} onClick={this.handleAdd}>新增</Button>}>
|
|
104
104
|
<div style={{display: 'flex', alignItems: 'center', gap: 4}}>
|
|
105
105
|
<Input.Search placeholder='搜索' value={params.searchText} onChange={e => {
|
|
106
106
|
params.searchText = e.target.value
|
|
@@ -140,7 +140,7 @@ export default class OrgPage extends React.Component {
|
|
|
140
140
|
onSelect={this.onSelect}
|
|
141
141
|
showIcon
|
|
142
142
|
blockNode
|
|
143
|
-
icon={item => <NamedIcon name={item.data.iconName}/>}
|
|
143
|
+
icon={item => <NamedIcon name={item.data.iconName}/>}
|
|
144
144
|
draggable={this.state.draggable}
|
|
145
145
|
onDrop={this.onDrop}
|
|
146
146
|
showLine
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {PlusOutlined} from '@ant-design/icons'
|
|
2
|
-
import {
|
|
1
|
+
import {PlusOutlined, SearchOutlined} from '@ant-design/icons'
|
|
2
|
+
import {Form, Input, InputNumber} from 'antd'
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import {FieldBoolean, FormModal, HttpClient, Page, PageUtils, PermActions, ProTable, UserPicker, ViewText} from "../../../framework";
|
|
5
5
|
|
|
@@ -133,25 +133,24 @@ export default class RolePage extends React.Component {
|
|
|
133
133
|
|
|
134
134
|
render() {
|
|
135
135
|
return <Page
|
|
136
|
-
title="角色管理"
|
|
137
|
-
description="管理系统角色,包括角色权限分配、用户设置等"
|
|
138
136
|
>
|
|
139
137
|
<ProTable
|
|
140
138
|
actionRef={this.tableRef}
|
|
141
|
-
|
|
142
|
-
<PermActions
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
toolBarRightRender={() => (
|
|
140
|
+
<PermActions actions={[
|
|
141
|
+
{label: '新增', type: 'primary', icon: <PlusOutlined/>, perm: 'sys-role:create', onClick: this.handleAdd},
|
|
142
|
+
]}/>
|
|
145
143
|
)}
|
|
146
144
|
request={(params) => HttpClient.get('admin/sysRole/page', params)}
|
|
147
145
|
columns={this.columns}
|
|
148
|
-
|
|
146
|
+
searchInline
|
|
147
|
+
searchFormRender={() => (
|
|
149
148
|
<>
|
|
150
|
-
<Form.Item
|
|
151
|
-
<Input/>
|
|
149
|
+
<Form.Item name='name' noStyle>
|
|
150
|
+
<Input prefix={<SearchOutlined/>} placeholder='角色名称' allowClear/>
|
|
152
151
|
</Form.Item>
|
|
153
|
-
<Form.Item
|
|
154
|
-
<Input/>
|
|
152
|
+
<Form.Item name='code' noStyle>
|
|
153
|
+
<Input placeholder='角色编码' allowClear/>
|
|
155
154
|
</Form.Item>
|
|
156
155
|
</>
|
|
157
156
|
)}
|
|
@@ -93,7 +93,7 @@ export default class RolePermPage extends React.Component {
|
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
render() {
|
|
96
|
-
return <Page
|
|
96
|
+
return <Page>
|
|
97
97
|
<Card title='角色权限设置' loading={this.state.loading}
|
|
98
98
|
variant={"borderless"}
|
|
99
99
|
extra={<Button type='primary' icon={<SaveOutlined/>} onClick={this.savePerms}>保存权限</Button>}>
|
|
@@ -152,9 +152,9 @@ export default class UserPage extends React.Component {
|
|
|
152
152
|
|
|
153
153
|
render() {
|
|
154
154
|
|
|
155
|
-
return <Page
|
|
155
|
+
return <Page >
|
|
156
156
|
<Splitter>
|
|
157
|
-
<Splitter.Panel defaultSize={
|
|
157
|
+
<Splitter.Panel defaultSize={340} style={{paddingRight: 8}}>
|
|
158
158
|
<Card size='small'>
|
|
159
159
|
<OrgTree onChange={this.onSelectOrg}/>
|
|
160
160
|
</Card>
|
|
@@ -163,17 +163,17 @@ export default class UserPage extends React.Component {
|
|
|
163
163
|
<ProTable
|
|
164
164
|
searchFormCols={3}
|
|
165
165
|
actionRef={this.tableRef}
|
|
166
|
-
|
|
167
|
-
<PermActions
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
toolBarRightRender={() => (
|
|
167
|
+
<PermActions actions={[
|
|
168
|
+
{label: '新增', type: 'primary', icon: <PlusOutlined/>, perm: 'sys-user:create', onClick: this.handleAdd},
|
|
169
|
+
]}/>
|
|
170
170
|
)}
|
|
171
171
|
request={(params) => {
|
|
172
172
|
params.orgId = this.state.currentOrgId
|
|
173
173
|
return HttpClient.get('admin/sysUser/page', params)
|
|
174
174
|
}}
|
|
175
175
|
columns={this.columns}
|
|
176
|
-
searchFormRender={() => (
|
|
176
|
+
searchFormRender={() => (
|
|
177
177
|
<>
|
|
178
178
|
<Form.Item label='姓名' name='name'>
|
|
179
179
|
<Input/>
|
|
@@ -15,9 +15,9 @@ export default class UserPerm extends React.Component {
|
|
|
15
15
|
dataPermType: null
|
|
16
16
|
},
|
|
17
17
|
}
|
|
18
|
-
formRef = React.createRef()
|
|
18
|
+
formRef = React.createRef()
|
|
19
19
|
|
|
20
|
-
show(item) {
|
|
20
|
+
show(item) {
|
|
21
21
|
this.setState({visible: true})
|
|
22
22
|
|
|
23
23
|
HttpClient.get('admin/sysUser/get-perm-info', {id: item.id}).then(rs => {
|
|
@@ -26,7 +26,7 @@ export default class PermView extends React.Component {
|
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
title: '状态', dataIndex: 'status', key: 'status', width: 140,
|
|
29
|
-
render: (status) => {
|
|
29
|
+
render: (status) => {
|
|
30
30
|
if (status === 'mine') {
|
|
31
31
|
return <Tag color='blue'>我的机构</Tag>;
|
|
32
32
|
}
|
|
@@ -48,7 +48,7 @@ export default class PermView extends React.Component {
|
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
title: '状态', dataIndex: 'status', key: 'status', width: 140,
|
|
51
|
-
render: (status) => {
|
|
51
|
+
render: (status) => {
|
|
52
52
|
if (status === 'all') {
|
|
53
53
|
return <Tag color='green'>已授权</Tag>;
|
|
54
54
|
}
|