@jiangood/open-admin 2.5.9 → 3.0.1
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 +20 -8
- package/src/framework/GlobalData.ts +85 -0
- package/src/framework/biz/OrgTree.tsx +2 -7
- package/src/framework/biz/Perm.tsx +11 -0
- package/src/framework/biz/PermActions.tsx +18 -15
- package/src/framework/biz/index.tsx +34 -19
- package/src/framework/components/ContextMenu/index.tsx +105 -78
- package/src/framework/components/DownloadModal/index.tsx +15 -17
- package/src/framework/components/ErrorBoundary/index.tsx +2 -2
- package/src/framework/components/FormModal/index.tsx +76 -0
- package/src/framework/components/Gap/index.tsx +1 -1
- package/src/framework/components/NamedIcon/index.tsx +55 -9
- package/src/framework/components/Page/index.less +0 -1
- package/src/framework/components/Page/index.tsx +34 -27
- package/src/framework/components/PageLoading/index.less +15 -0
- package/src/framework/components/PageLoading/index.tsx +32 -16
- package/src/framework/components/ProTable/index.tsx +86 -15
- package/src/framework/components/index.ts +1 -0
- package/src/framework/config/index.ts +1 -0
- package/src/framework/config/themeConfig.ts +39 -0
- package/src/framework/fields/FieldBoolean/index.tsx +11 -2
- package/src/framework/fields/FieldDate/index.tsx +22 -7
- package/src/framework/fields/FieldDateRange/index.tsx +24 -6
- package/src/framework/fields/FieldDictSelect/index.tsx +1 -0
- package/src/framework/fields/FieldEditor/index.tsx +7 -1
- package/src/framework/fields/FieldNumberRange/index.tsx +14 -5
- package/src/framework/fields/FieldPercent/index.tsx +16 -5
- package/src/framework/fields/FieldRemoteSelect/index.tsx +4 -2
- package/src/framework/fields/FieldRemoteTreeSelect/index.tsx +2 -1
- package/src/framework/fields/FieldSysOrgTree/index.tsx +6 -1
- package/src/framework/fields/FieldSysOrgTreeSelect/index.tsx +6 -1
- package/src/framework/fields/FieldTable/index.tsx +35 -6
- package/src/framework/fields/FieldTableSelect/index.tsx +18 -2
- package/src/framework/fields/FieldUploadFile/index.tsx +69 -28
- package/src/framework/fields/index.ts +0 -1
- package/src/framework/index.ts +2 -0
- package/src/framework/router/Link.tsx +26 -0
- package/src/framework/router/PageFrame.tsx +31 -0
- package/src/framework/router/history.ts +50 -0
- package/src/framework/router/index.ts +4 -0
- package/src/framework/router/matcher.ts +58 -0
- package/src/framework/utils/{ArrayUtils.js → ArrayUtils.ts} +5 -5
- package/src/framework/utils/{DateUtils.js → DateUtils.ts} +19 -19
- package/src/framework/utils/{DeviceUtils.js → DeviceUtils.ts} +8 -1
- package/src/framework/utils/{EventBus.js → EventBus.ts} +13 -6
- package/src/framework/utils/MessageApi.ts +5 -0
- package/src/framework/utils/ModalApi.ts +5 -0
- package/src/framework/utils/ObjectUtils.ts +15 -0
- package/src/framework/utils/{StorageUtils.js → StorageUtils.ts} +7 -7
- package/src/framework/utils/{StringUtils.js → StringUtils.ts} +15 -15
- package/src/framework/utils/{TreeUtils.js → TreeUtils.ts} +19 -12
- package/src/framework/utils/{UrlUtils.js → UrlUtils.ts} +10 -10
- package/src/framework/utils/index.ts +2 -1
- package/src/framework/utils/system/{DictUtils.js → DictUtils.ts} +16 -4
- package/src/framework/utils/system/{HttpUtils.js → HttpUtils.ts} +25 -21
- package/src/framework/utils/system/PageUtils.ts +49 -0
- package/src/framework/utils/system/{PermUtils.js → PermUtils.ts} +5 -6
- package/src/framework/utils/system/index.ts +0 -2
- package/src/framework/views/ViewBoolean/index.tsx +6 -3
- package/src/framework/views/ViewFileButton/index.tsx +13 -8
- package/src/framework/views/ViewImage/index.tsx +19 -14
- package/src/framework/views/ViewRange/index.tsx +10 -7
- package/src/framework/views/ViewText/index.tsx +9 -11
- package/src/layouts/admin/AdminTabs.jsx +79 -0
- package/src/layouts/admin/AdminTabs.less +63 -0
- package/src/layouts/admin/HeaderRight.jsx +27 -13
- package/src/layouts/admin/TabLayout.jsx +124 -0
- package/src/layouts/admin/index.jsx +5 -6
- package/src/layouts/admin/index.less +1 -82
- package/src/layouts/index.tsx +148 -0
- package/src/main.jsx +8 -0
- package/src/pages/account/changePassword.jsx +14 -10
- package/src/pages/article/$code.jsx +2 -2
- package/src/pages/index.jsx +6 -4
- package/src/pages/{login.jsx → public/login.jsx} +7 -10
- package/src/pages/public/test.jsx +30 -0
- package/src/pages/{forceUpdatePwd.jsx → standalone/forceUpdatePwd.jsx} +3 -24
- package/src/pages/system/article/index.jsx +51 -56
- package/src/pages/system/dict/index.jsx +60 -91
- package/src/pages/system/file/index.jsx +23 -17
- package/src/pages/system/job/index.jsx +63 -72
- package/src/pages/system/log/index.jsx +11 -8
- package/src/pages/system/org/index.jsx +60 -80
- package/src/pages/system/role/index.jsx +50 -86
- package/src/pages/system/role/rolePerm.jsx +2 -2
- package/src/pages/system/user/index.jsx +120 -73
- package/src/pages/system/user/userPerm.jsx +2 -2
- package/vite-plugin/index.js +107 -0
- package/config/common-plugin.js +0 -145
- package/config/config.js +0 -5
- package/config/utils.js +0 -73
- package/src/.umi-production/appData.json +0 -2257
- package/src/.umi-production/core/EmptyRoute.tsx +0 -9
- package/src/.umi-production/core/defineApp.ts +0 -16
- package/src/.umi-production/core/helmet.ts +0 -10
- package/src/.umi-production/core/helmetContext.ts +0 -4
- package/src/.umi-production/core/history.ts +0 -72
- package/src/.umi-production/core/historyIntelli.ts +0 -132
- package/src/.umi-production/core/plugin.ts +0 -40
- package/src/.umi-production/core/pluginConfig.ts +0 -330
- package/src/.umi-production/core/pluginConfigJoi.d.ts +0 -7
- package/src/.umi-production/core/polyfill.ts +0 -220
- package/src/.umi-production/core/route.tsx +0 -47
- package/src/.umi-production/core/routeProps.js +0 -5
- package/src/.umi-production/core/routeProps.ts +0 -6
- package/src/.umi-production/core/terminal.ts +0 -37
- package/src/.umi-production/exports.ts +0 -17
- package/src/.umi-production/testBrowser.tsx +0 -90
- package/src/.umi-production/tsconfig.json +0 -43
- package/src/.umi-production/typings.d.ts +0 -136
- package/src/.umi-production/umi.ts +0 -83
- package/src/framework/GlobalData.d.ts +0 -10
- package/src/framework/GlobalData.js +0 -42
- package/src/framework/biz/HasPerm.tsx +0 -14
- package/src/framework/fields/FieldRemoteSelectMultiple/index.tsx +0 -29
- package/src/framework/utils/ArrayUtils.d.ts +0 -6
- package/src/framework/utils/DateUtils.d.ts +0 -20
- package/src/framework/utils/DeviceUtils.d.ts +0 -3
- package/src/framework/utils/EventBus.d.ts +0 -6
- package/src/framework/utils/MessageUtils.d.ts +0 -10
- package/src/framework/utils/MessageUtils.js +0 -43
- package/src/framework/utils/ObjectUtils.d.ts +0 -14
- package/src/framework/utils/ObjectUtils.js +0 -12
- package/src/framework/utils/StorageUtils.d.ts +0 -4
- package/src/framework/utils/StringUtils.d.ts +0 -17
- package/src/framework/utils/TreeUtils.d.ts +0 -13
- package/src/framework/utils/UrlUtils.d.ts +0 -6
- package/src/framework/utils/system/DictUtils.d.ts +0 -20
- package/src/framework/utils/system/FormRegistry.d.ts +0 -8
- package/src/framework/utils/system/FormRegistry.js +0 -31
- package/src/framework/utils/system/HttpUtils.d.ts +0 -14
- package/src/framework/utils/system/PageUtils.d.ts +0 -14
- package/src/framework/utils/system/PageUtils.js +0 -59
- package/src/framework/utils/system/PermUtils.d.ts +0 -5
- package/src/framework/utils/system/ThemeUtils.d.ts +0 -6
- package/src/framework/utils/system/ThemeUtils.js +0 -52
- package/src/layouts/PageRender.tsx +0 -29
- package/src/layouts/admin/TabPageRender.jsx +0 -210
- package/src/layouts/index.jsx +0 -120
- package/src/loading.jsx +0 -19
- package/src/pages/about.jsx +0 -16
- /package/src/pages/{login.less → public/login.less} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {PlusOutlined} from '@ant-design/icons';
|
|
2
|
-
import {Button, Card, Form, Input, Modal, Popconfirm, Select, Splitter} from 'antd';
|
|
2
|
+
import {Button, Card, Form, Input, Modal, Popconfirm, Select, Splitter, message} from 'antd';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import {
|
|
5
5
|
PermActions,
|
|
@@ -7,36 +7,41 @@ import {
|
|
|
7
7
|
FieldBoolean,
|
|
8
8
|
FieldRemoteSelect,
|
|
9
9
|
FieldSysOrgTreeSelect,
|
|
10
|
+
FormModal,
|
|
10
11
|
HttpUtils,
|
|
11
12
|
OrgTree,
|
|
12
13
|
Page,
|
|
13
14
|
ProTable,
|
|
15
|
+
ViewText,
|
|
14
16
|
} from "../../../framework";
|
|
15
17
|
import UserPerm from "./userPerm";
|
|
16
18
|
|
|
17
19
|
export default class extends React.Component {
|
|
18
20
|
|
|
19
21
|
state = {
|
|
20
|
-
showAddForm: false,
|
|
21
|
-
showEditForm: false,
|
|
22
|
-
formValues: {},
|
|
23
|
-
|
|
24
22
|
currentOrgId: null,
|
|
23
|
+
addResultModal: { open: false, account: '', password: '' },
|
|
24
|
+
resetPwdUser: null,
|
|
25
25
|
}
|
|
26
26
|
permRef = React.createRef();
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
modalRef = React.createRef()
|
|
29
29
|
tableRef = React.createRef()
|
|
30
|
+
resetPwdRef = React.createRef()
|
|
30
31
|
|
|
31
32
|
resetPwd(row) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
title: '重置密码成功',
|
|
35
|
-
content: rs
|
|
36
|
-
})
|
|
33
|
+
this.setState({resetPwdUser: row}, () => {
|
|
34
|
+
this.resetPwdRef.current.open()
|
|
37
35
|
})
|
|
38
36
|
}
|
|
39
37
|
|
|
38
|
+
onFinishResetPwd = async values => {
|
|
39
|
+
await HttpUtils.post('admin/sysUser/reset-pwd', {id: this.state.resetPwdUser.id, password: values.password})
|
|
40
|
+
message.success('重置密码成功')
|
|
41
|
+
this.setState({resetPwdUser: null})
|
|
42
|
+
this.tableRef.current?.reload()
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
handleDelete = r => {
|
|
41
46
|
HttpUtils.post('admin/sysUser/delete', {id: r.id}).then(rs => {
|
|
42
47
|
this.tableRef.current.reload();
|
|
@@ -48,12 +53,11 @@ export default class extends React.Component {
|
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
handleAdd = () => {
|
|
51
|
-
this.
|
|
56
|
+
this.modalRef.current.open({})
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
handleEdit = record => {
|
|
55
|
-
record
|
|
56
|
-
this.setState({formOpen: true, formValues: record})
|
|
60
|
+
this.modalRef.current.open({...record, deptId: record.deptId || record.unitId})
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
columns = [
|
|
@@ -125,9 +129,7 @@ export default class extends React.Component {
|
|
|
125
129
|
<Button size='small' perm='sys-user:grant-permission'
|
|
126
130
|
onClick={() => this.permRef.current.show(record)}> 授权 </Button>
|
|
127
131
|
|
|
128
|
-
<
|
|
129
|
-
<Button size='small'>重置密码</Button>
|
|
130
|
-
</Popconfirm>
|
|
132
|
+
<Button size='small' perm='sys-user:reset-password' onClick={() => this.resetPwd(record)}>重置密码</Button>
|
|
131
133
|
|
|
132
134
|
<Popconfirm perm='sys-user:delete' title={'是否确定删除用户'}
|
|
133
135
|
onConfirm={() => this.handleDelete(record)}>
|
|
@@ -138,18 +140,25 @@ export default class extends React.Component {
|
|
|
138
140
|
},
|
|
139
141
|
];
|
|
140
142
|
|
|
141
|
-
onFinish = values => {
|
|
143
|
+
onFinish = async values => {
|
|
142
144
|
const isNew = !values.id;
|
|
143
145
|
const url = isNew ? 'admin/sysUser/create' : 'admin/sysUser/update';
|
|
144
|
-
HttpUtils.post(url, values)
|
|
145
|
-
|
|
146
|
-
this.
|
|
147
|
-
|
|
146
|
+
const result = await HttpUtils.post(url, values)
|
|
147
|
+
if (result && result.password) {
|
|
148
|
+
this.setState({
|
|
149
|
+
addResultModal: {
|
|
150
|
+
open: true,
|
|
151
|
+
account: values.account,
|
|
152
|
+
password: result.password,
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
this.tableRef.current.reload()
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
render() {
|
|
151
160
|
|
|
152
|
-
return <Page title="用户管理" description="管理系统用户"
|
|
161
|
+
return <Page title="用户管理" description="管理系统用户">
|
|
153
162
|
<Splitter>
|
|
154
163
|
<Splitter.Panel defaultSize={350} style={{paddingRight: 8}}>
|
|
155
164
|
<Card size='small'>
|
|
@@ -158,75 +167,113 @@ export default class extends React.Component {
|
|
|
158
167
|
</Splitter.Panel>
|
|
159
168
|
<Splitter.Panel style={{paddingLeft: 8}}>
|
|
160
169
|
<ProTable
|
|
161
|
-
|
|
170
|
+
searchFormCols={3}
|
|
162
171
|
actionRef={this.tableRef}
|
|
172
|
+
toolBarRender={() => (
|
|
173
|
+
<PermActions>
|
|
174
|
+
<Button perm='sys-user:create' type='primary' icon={<PlusOutlined/>} onClick={this.handleAdd}>新增</Button>
|
|
175
|
+
</PermActions>
|
|
176
|
+
)}
|
|
163
177
|
request={(params) => {
|
|
164
178
|
params.orgId = this.state.currentOrgId
|
|
165
179
|
return HttpUtils.get('admin/sysUser/page', params)
|
|
166
180
|
}}
|
|
167
181
|
columns={this.columns}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
searchFormRender={() => (
|
|
183
|
+
<>
|
|
184
|
+
<Form.Item label='姓名' name='name'>
|
|
185
|
+
<Input/>
|
|
186
|
+
</Form.Item>
|
|
187
|
+
<Form.Item label='登录账号' name='account'>
|
|
188
|
+
<Input/>
|
|
189
|
+
</Form.Item>
|
|
190
|
+
<Form.Item label='手机号' name='phone'>
|
|
191
|
+
<Input/>
|
|
192
|
+
</Form.Item>
|
|
193
|
+
<Form.Item label='角色' name='roleId'>
|
|
194
|
+
<FieldRemoteSelect url='admin/sysRole/options' placeholder='请选择角色'/>
|
|
195
|
+
</Form.Item>
|
|
196
|
+
<Form.Item label='状态' name='enabled'>
|
|
197
|
+
<Select allowClear placeholder='全部' options={[{value: true, label: '启用'}, {value: false, label: '停用'}]}/>
|
|
198
|
+
</Form.Item>
|
|
199
|
+
</>
|
|
200
|
+
)}
|
|
201
|
+
/>
|
|
188
202
|
</Splitter.Panel>
|
|
189
203
|
</Splitter>
|
|
190
204
|
|
|
191
205
|
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
<FormModal ref={this.modalRef} title='系统用户' onFinish={this.onFinish}>
|
|
207
|
+
|
|
208
|
+
<Form.Item label='所属机构' name='deptId' rules={[{required: true}]}>
|
|
209
|
+
<FieldSysOrgTreeSelect/>
|
|
210
|
+
</Form.Item>
|
|
211
|
+
|
|
212
|
+
<Form.Item label='姓名' name='name' rules={[{required: true}]}>
|
|
213
|
+
<Input/>
|
|
214
|
+
</Form.Item>
|
|
215
|
+
<Form.Item label='账号' name='account' rules={[{required: true}]}>
|
|
216
|
+
<Input/>
|
|
217
|
+
</Form.Item>
|
|
198
218
|
|
|
199
|
-
<Form
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
219
|
+
<Form.Item label='电话' name='phone'>
|
|
220
|
+
<Input/>
|
|
221
|
+
</Form.Item>
|
|
222
|
+
<Form.Item label='邮箱' name='email'>
|
|
223
|
+
<Input/>
|
|
224
|
+
</Form.Item>
|
|
203
225
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
</Form.Item>
|
|
226
|
+
<Form.Item label='启用状态' name='enabled' rules={[{required: true}]}>
|
|
227
|
+
<FieldBoolean/>
|
|
207
228
|
|
|
208
|
-
|
|
209
|
-
<Input/>
|
|
210
|
-
</Form.Item>
|
|
211
|
-
<Form.Item label='账号' name='account' rules={[{required: true}]}>
|
|
212
|
-
<Input/>
|
|
213
|
-
</Form.Item>
|
|
229
|
+
</Form.Item>
|
|
214
230
|
|
|
215
231
|
|
|
216
|
-
<Form.Item label='电话' name='phone'>
|
|
217
|
-
<Input/>
|
|
218
|
-
</Form.Item>
|
|
219
|
-
<Form.Item label='邮箱' name='email'>
|
|
220
|
-
<Input/>
|
|
221
|
-
</Form.Item>
|
|
222
232
|
|
|
223
|
-
|
|
224
|
-
<FieldBoolean/>
|
|
225
|
-
</Form.Item>
|
|
233
|
+
</FormModal>
|
|
226
234
|
|
|
227
|
-
|
|
235
|
+
|
|
236
|
+
<Modal
|
|
237
|
+
title="添加用户成功"
|
|
238
|
+
open={this.state.addResultModal.open}
|
|
239
|
+
onOk={() => this.setState({addResultModal: {open: false, account: '', password: ''}})}
|
|
240
|
+
onCancel={() => this.setState({addResultModal: {open: false, account: '', password: ''}})}
|
|
241
|
+
width={420}
|
|
242
|
+
footer={
|
|
243
|
+
<div style={{display: 'flex', justifyContent: 'flex-end', gap: 8}}>
|
|
244
|
+
<Button onClick={() => {
|
|
245
|
+
navigator.clipboard.writeText(`账号:${this.state.addResultModal.account}\n密码:${this.state.addResultModal.password}`);
|
|
246
|
+
message.success('复制成功')
|
|
247
|
+
}}>复制</Button>
|
|
248
|
+
<Button type="primary" onClick={() => this.setState({addResultModal: {open: false, account: '', password: ''}})}>确定</Button>
|
|
249
|
+
</div>
|
|
250
|
+
}
|
|
251
|
+
>
|
|
252
|
+
<div>
|
|
253
|
+
<div style={{marginBottom: 8}}>
|
|
254
|
+
账号:{this.state.addResultModal.account}
|
|
255
|
+
</div>
|
|
256
|
+
<div>
|
|
257
|
+
密码:{this.state.addResultModal.password}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
228
260
|
</Modal>
|
|
229
261
|
|
|
262
|
+
<FormModal ref={this.resetPwdRef} title='重置密码' onFinish={this.onFinishResetPwd} width={420}>
|
|
263
|
+
{this.state.resetPwdUser && (
|
|
264
|
+
<>
|
|
265
|
+
<Form.Item label="账号">
|
|
266
|
+
<ViewText value={this.state.resetPwdUser.account}/>
|
|
267
|
+
</Form.Item>
|
|
268
|
+
<Form.Item label="姓名">
|
|
269
|
+
<ViewText value={this.state.resetPwdUser.name}/>
|
|
270
|
+
</Form.Item>
|
|
271
|
+
</>
|
|
272
|
+
)}
|
|
273
|
+
<Form.Item label="新密码" name="password" rules={[{required: true, message: '请输入新密码'}]}>
|
|
274
|
+
<Input.Password placeholder="请输入新密码"/>
|
|
275
|
+
</Form.Item>
|
|
276
|
+
</FormModal>
|
|
230
277
|
|
|
231
278
|
<UserPerm ref={this.permRef} onOk={() => this.tableRef.current.reload()}/>
|
|
232
279
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {Form, Modal} from 'antd';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {FieldDictSelect,
|
|
3
|
+
import {FieldDictSelect, FieldRemoteSelect, FieldSysOrgTree, HttpUtils} from "../../../framework";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
export default class UserPerm extends React.Component {
|
|
@@ -70,7 +70,7 @@ export default class UserPerm extends React.Component {
|
|
|
70
70
|
>
|
|
71
71
|
<Form.Item name='id' noStyle></Form.Item>
|
|
72
72
|
<Form.Item label='角色' name='roleIds' rules={[{required: true}]}>
|
|
73
|
-
<
|
|
73
|
+
<FieldRemoteSelect url='admin/sysRole/options' multiple />
|
|
74
74
|
</Form.Item>
|
|
75
75
|
<Form.Item label='数据权限' name='dataPermType' rules={[{required: true}]}>
|
|
76
76
|
<FieldDictSelect typeCode='dataPermType'/>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const VIRTUAL_ID = 'virtual:open-admin/routes';
|
|
5
|
+
const RESOLVED_ID = '\0open-admin:routes';
|
|
6
|
+
|
|
7
|
+
function walk(dir, out = []) {
|
|
8
|
+
if (!fs.existsSync(dir)) return out;
|
|
9
|
+
for (const item of fs.readdirSync(dir)) {
|
|
10
|
+
const full = path.join(dir, item);
|
|
11
|
+
if (fs.statSync(full).isDirectory()) {
|
|
12
|
+
walk(full, out);
|
|
13
|
+
} else {
|
|
14
|
+
out.push(full.split(path.sep).join('/'));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 页面文件扩展名:支持 .jsx / .tsx */
|
|
21
|
+
const PAGE_EXT_RE = /\.(jsx|tsx)$/;
|
|
22
|
+
const PAGE_PATH_RE = /src\/pages\/(.+)\.(jsx|tsx)$/;
|
|
23
|
+
|
|
24
|
+
/** 收集页面文件:业务项目 src/pages 优先,node_modules 中 @jiangood 框架包兜底 */
|
|
25
|
+
function collectPageFiles(root) {
|
|
26
|
+
const business = walk(path.resolve(root, 'src/pages')).filter(f => PAGE_EXT_RE.test(f));
|
|
27
|
+
const framework = [];
|
|
28
|
+
const scopeDir = path.resolve(root, 'node_modules/@jiangood');
|
|
29
|
+
if (fs.existsSync(scopeDir)) {
|
|
30
|
+
for (const pkg of fs.readdirSync(scopeDir)) {
|
|
31
|
+
framework.push(...walk(path.join(scopeDir, pkg, 'src/pages')).filter(f => PAGE_PATH_RE.test(f)));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return [...business, ...framework];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 页面文件 → 路由路径;大写开头(组件文件)返回 null */
|
|
38
|
+
function fileToRoutePath(file) {
|
|
39
|
+
const m = file.match(PAGE_PATH_RE);
|
|
40
|
+
if (!m) return null;
|
|
41
|
+
const mainName = m[1].split('/').pop();
|
|
42
|
+
if (mainName.charAt(0) !== mainName.charAt(0).toLowerCase()) return null;
|
|
43
|
+
let routePath = '/' + m[1].replaceAll('$', ':');
|
|
44
|
+
if (routePath.endsWith('/index')) routePath = routePath.substring(0, routePath.length - 6);
|
|
45
|
+
return routePath || '/';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 生成可被 Vite dev/build 解析的 import 路径 */
|
|
49
|
+
function toImportPath(root, file) {
|
|
50
|
+
const nm = file.indexOf('/node_modules/');
|
|
51
|
+
if (nm !== -1) {
|
|
52
|
+
// 依赖框架包:bare specifier,如 @jiangood/open-admin/src/pages/login.jsx
|
|
53
|
+
return file.substring(nm + '/node_modules/'.length);
|
|
54
|
+
}
|
|
55
|
+
// 项目内页面:root 相对,如 /src/pages/login.jsx
|
|
56
|
+
return '/' + path.relative(root, file).split(path.sep).join('/');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function generateRoutesModule(root) {
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
const imports = [];
|
|
62
|
+
const entries = [];
|
|
63
|
+
collectPageFiles(root).forEach((file, i) => {
|
|
64
|
+
const routePath = fileToRoutePath(file);
|
|
65
|
+
if (!routePath) return;
|
|
66
|
+
if (seen.has(routePath)) {
|
|
67
|
+
console.warn(`[open-admin] 路由 ${routePath} 被覆盖(来自 ${file})`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
seen.add(routePath);
|
|
71
|
+
imports.push(`import P${i} from ${JSON.stringify(toImportPath(root, file))};`);
|
|
72
|
+
entries.push(` {path: ${JSON.stringify(routePath)}, component: P${i}},`);
|
|
73
|
+
});
|
|
74
|
+
return `${imports.join('\n')}\n\nexport default [\n${entries.join('\n')}\n];\n`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default function openAdmin() {
|
|
78
|
+
let root;
|
|
79
|
+
return {
|
|
80
|
+
name: 'open-admin',
|
|
81
|
+
|
|
82
|
+
configResolved(resolved) {
|
|
83
|
+
root = resolved.root;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
resolveId(id) {
|
|
87
|
+
if (id === VIRTUAL_ID) return RESOLVED_ID;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
load(id) {
|
|
91
|
+
if (id === RESOLVED_ID) return generateRoutesModule(root);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
configureServer(server) {
|
|
95
|
+
const onPagesFileChange = (file) => {
|
|
96
|
+
if (!PAGE_PATH_RE.test(file.split(path.sep).join('/'))) return;
|
|
97
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED_ID);
|
|
98
|
+
if (mod) {
|
|
99
|
+
server.moduleGraph.invalidateModule(mod);
|
|
100
|
+
server.ws.send({type: 'full-reload'});
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
server.watcher.on('add', onPagesFileChange);
|
|
104
|
+
server.watcher.on('unlink', onPagesFileChange);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
package/config/common-plugin.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import Utils from './utils.js';
|
|
4
|
-
|
|
5
|
-
const pkgName = '@jiangood/open-admin';
|
|
6
|
-
|
|
7
|
-
function convertFileToRoute(file) {
|
|
8
|
-
const absPath = file;
|
|
9
|
-
const mainName = Utils.getFileMainName(file);
|
|
10
|
-
|
|
11
|
-
if (mainName.charAt(0) !== mainName.charAt(0).toLowerCase()) {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let routePath = file.substring(file.indexOf('pages') + 6, file.length - 4);
|
|
16
|
-
routePath = routePath.replaceAll("$", ":");
|
|
17
|
-
|
|
18
|
-
let parentId = "@@/global-layout";
|
|
19
|
-
|
|
20
|
-
if (routePath.endsWith("/index")) {
|
|
21
|
-
routePath = routePath.substring(0, routePath.length - 6);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
absPath,
|
|
26
|
-
id: routePath,
|
|
27
|
-
path: routePath,
|
|
28
|
-
file,
|
|
29
|
-
parentId
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default (api) => {
|
|
34
|
-
api.describe({
|
|
35
|
-
key: 'open-admin',
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Inject build-time defines and UmiJS defaults from environment variables
|
|
39
|
-
const servletContext = process.env.SERVLET_CONTEXT || '/change-this-servlet-context';
|
|
40
|
-
const isProd = process.env.NODE_ENV === 'production';
|
|
41
|
-
const serverPort = process.env.SERVER_PORT || '8080';
|
|
42
|
-
|
|
43
|
-
const theme = {};
|
|
44
|
-
if (process.env.THEME_PRIMARY_COLOR) theme["primary-color"] = process.env.THEME_PRIMARY_COLOR;
|
|
45
|
-
if (process.env.THEME_SUCCESS_COLOR) theme["success-color"] = process.env.THEME_SUCCESS_COLOR;
|
|
46
|
-
if (process.env.THEME_WARNING_COLOR) theme["warning-color"] = process.env.THEME_WARNING_COLOR;
|
|
47
|
-
if (process.env.THEME_ERROR_COLOR) theme["error-color"] = process.env.THEME_ERROR_COLOR;
|
|
48
|
-
if (process.env.THEME_BACKGROUND_COLOR) theme["background-color"] = process.env.THEME_BACKGROUND_COLOR;
|
|
49
|
-
|
|
50
|
-
api.modifyConfig((memo) => {
|
|
51
|
-
memo.define = memo.define || {};
|
|
52
|
-
|
|
53
|
-
// Define globals
|
|
54
|
-
memo.define.SERVLET_CONTEXT = servletContext;
|
|
55
|
-
if (Object.keys(theme).length > 0) {
|
|
56
|
-
memo.define.OPEN_ADMIN_THEME = theme;
|
|
57
|
-
}
|
|
58
|
-
if (process.env.PUBLIC_PAGES) {
|
|
59
|
-
memo.define.OPEN_ADMIN_PUBLIC_PAGES = process.env.PUBLIC_PAGES;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Build defaults (only if not explicitly set by project config.js)
|
|
63
|
-
if (memo.publicPath === undefined) {
|
|
64
|
-
memo.publicPath = isProd ? './' : '/';
|
|
65
|
-
}
|
|
66
|
-
if (memo.hash === undefined) memo.hash = true;
|
|
67
|
-
if (memo.history === undefined) memo.history = { type: 'hash' };
|
|
68
|
-
if (memo.mfsu === undefined) memo.mfsu = false;
|
|
69
|
-
if (memo.esbuildMinifyIIFE === undefined) memo.esbuildMinifyIIFE = true;
|
|
70
|
-
|
|
71
|
-
// Proxy dev server
|
|
72
|
-
if (memo.proxy === undefined) memo.proxy = {};
|
|
73
|
-
if (!memo.proxy[servletContext]) {
|
|
74
|
-
memo.proxy[servletContext] = {
|
|
75
|
-
target: `http://127.0.0.1:${serverPort}`,
|
|
76
|
-
changeOrigin: true,
|
|
77
|
-
ws: true, // WebSocket 代理支持(http-proxy-middleware)
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return memo;
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
api.logger.info(`SERVLET_CONTEXT=${servletContext}, proxy→http://127.0.0.1:${serverPort}`);
|
|
85
|
-
if (Object.keys(theme).length > 0) {
|
|
86
|
-
api.logger.info('OPEN_ADMIN_THEME:', theme);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const isFramework = api.pkg.name === pkgName;
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
let frameworkDirs = Utils.getDirs(api.paths.absNodeModulesPath + "/@jiangood");
|
|
93
|
-
frameworkDirs = frameworkDirs.reverse();
|
|
94
|
-
|
|
95
|
-
api.logger.info('依赖的框架:', frameworkDirs);
|
|
96
|
-
|
|
97
|
-
for (let frameworkDir of frameworkDirs) {
|
|
98
|
-
api.logger.info("正在解析文件夹", frameworkDir);
|
|
99
|
-
|
|
100
|
-
const routeFiles = Utils.findFilesSync(frameworkDir, /src\/pages\/.*\.jsx$/);
|
|
101
|
-
api.logger.info("找到的页面文件:", routeFiles);
|
|
102
|
-
|
|
103
|
-
api.modifyRoutes((routes) => {
|
|
104
|
-
for (let file of routeFiles) {
|
|
105
|
-
const route = convertFileToRoute(file);
|
|
106
|
-
if (route && !routes[route.id]) {
|
|
107
|
-
api.logger.info("加入路由:", route.id, "路径:", route.absPath);
|
|
108
|
-
routes[route.id] = route;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
api.logger.info("路由修改完成");
|
|
112
|
-
return routes;
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
} catch (error) {
|
|
116
|
-
api.logger.error('处理路由时出错:', error);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
let importFrom = path.join(api.paths.absSrcPath, 'framework');
|
|
121
|
-
if (!isFramework) {
|
|
122
|
-
importFrom = path.join(api.paths.absNodeModulesPath, pkgName);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
api.logger.info('formRegistryPath', importFrom);
|
|
126
|
-
api.addEntryImports(() => ({
|
|
127
|
-
source: importFrom,
|
|
128
|
-
specifier: '{FormRegistry}'
|
|
129
|
-
}));
|
|
130
|
-
|
|
131
|
-
Utils.findFilesSync(api.paths.absSrcPath, /forms\/.*\.jsx$/).forEach(file => {
|
|
132
|
-
const name = Utils.getFileMainName(file);
|
|
133
|
-
|
|
134
|
-
api.addEntryImports(() => ({
|
|
135
|
-
source: file,
|
|
136
|
-
specifier: name
|
|
137
|
-
}));
|
|
138
|
-
|
|
139
|
-
api.addEntryCodeAhead(() => `FormRegistry.register("${name}", ${name});`);
|
|
140
|
-
api.logger.info('新版本 formRegistry.register: ', name, file);
|
|
141
|
-
});
|
|
142
|
-
} catch (error) {
|
|
143
|
-
api.logger.error('处理表单时出错:', error);
|
|
144
|
-
}
|
|
145
|
-
};
|
package/config/config.js
DELETED
package/config/utils.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
|
|
4
|
-
class Utils {
|
|
5
|
-
/**
|
|
6
|
-
* 获取目录下的所有子目录
|
|
7
|
-
* @param dir
|
|
8
|
-
* @returns {string[]} 完整路径
|
|
9
|
-
*/
|
|
10
|
-
static getDirs(dir) {
|
|
11
|
-
if (!fs.existsSync(dir)) {
|
|
12
|
-
return [];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return fs.readdirSync(dir)
|
|
16
|
-
.map(item => {
|
|
17
|
-
const fullPath = path.join(dir, item);
|
|
18
|
-
if (fs.statSync(fullPath).isDirectory()) {
|
|
19
|
-
return path.join(dir, item);
|
|
20
|
-
}
|
|
21
|
-
})
|
|
22
|
-
.filter(Boolean);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 同步获取目录下所有文件
|
|
27
|
-
*/
|
|
28
|
-
static getAllFilesSync(dir) {
|
|
29
|
-
if (!fs.existsSync(dir)) throw new Error(`目录不存在: ${dir}`);
|
|
30
|
-
|
|
31
|
-
const results = [];
|
|
32
|
-
|
|
33
|
-
function traverse(currentDir) {
|
|
34
|
-
fs.readdirSync(currentDir).forEach(item => {
|
|
35
|
-
const fullPath = path.join(currentDir, item);
|
|
36
|
-
if (fs.statSync(fullPath).isDirectory()) {
|
|
37
|
-
traverse(fullPath);
|
|
38
|
-
} else {
|
|
39
|
-
results.push(fullPath.split(path.sep).join('/'));
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
traverse(dir);
|
|
45
|
-
return results;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 同步查找匹配模式的文件
|
|
50
|
-
*
|
|
51
|
-
* @return 文件列表 全路径
|
|
52
|
-
*/
|
|
53
|
-
static findFilesSync(dir, pattern) {
|
|
54
|
-
const files = this.getAllFilesSync(dir);
|
|
55
|
-
return files.filter(file => pattern.test(file));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 获取文件名(不含后缀)
|
|
60
|
-
* @param {string} filePath - 文件路径
|
|
61
|
-
* @returns {string} 文件名(不含后缀)
|
|
62
|
-
*/
|
|
63
|
-
static getFileMainName(filePath) {
|
|
64
|
-
const fileName = path.basename(filePath);
|
|
65
|
-
const lastDotIndex = fileName.lastIndexOf('.');
|
|
66
|
-
if (lastDotIndex > 0) {
|
|
67
|
-
return fileName.substring(0, lastDotIndex);
|
|
68
|
-
}
|
|
69
|
-
return fileName;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export default Utils;
|