@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.
Files changed (142) hide show
  1. package/package.json +20 -8
  2. package/src/framework/GlobalData.ts +85 -0
  3. package/src/framework/biz/OrgTree.tsx +2 -7
  4. package/src/framework/biz/Perm.tsx +11 -0
  5. package/src/framework/biz/PermActions.tsx +18 -15
  6. package/src/framework/biz/index.tsx +34 -19
  7. package/src/framework/components/ContextMenu/index.tsx +105 -78
  8. package/src/framework/components/DownloadModal/index.tsx +15 -17
  9. package/src/framework/components/ErrorBoundary/index.tsx +2 -2
  10. package/src/framework/components/FormModal/index.tsx +76 -0
  11. package/src/framework/components/Gap/index.tsx +1 -1
  12. package/src/framework/components/NamedIcon/index.tsx +55 -9
  13. package/src/framework/components/Page/index.less +0 -1
  14. package/src/framework/components/Page/index.tsx +34 -27
  15. package/src/framework/components/PageLoading/index.less +15 -0
  16. package/src/framework/components/PageLoading/index.tsx +32 -16
  17. package/src/framework/components/ProTable/index.tsx +86 -15
  18. package/src/framework/components/index.ts +1 -0
  19. package/src/framework/config/index.ts +1 -0
  20. package/src/framework/config/themeConfig.ts +39 -0
  21. package/src/framework/fields/FieldBoolean/index.tsx +11 -2
  22. package/src/framework/fields/FieldDate/index.tsx +22 -7
  23. package/src/framework/fields/FieldDateRange/index.tsx +24 -6
  24. package/src/framework/fields/FieldDictSelect/index.tsx +1 -0
  25. package/src/framework/fields/FieldEditor/index.tsx +7 -1
  26. package/src/framework/fields/FieldNumberRange/index.tsx +14 -5
  27. package/src/framework/fields/FieldPercent/index.tsx +16 -5
  28. package/src/framework/fields/FieldRemoteSelect/index.tsx +4 -2
  29. package/src/framework/fields/FieldRemoteTreeSelect/index.tsx +2 -1
  30. package/src/framework/fields/FieldSysOrgTree/index.tsx +6 -1
  31. package/src/framework/fields/FieldSysOrgTreeSelect/index.tsx +6 -1
  32. package/src/framework/fields/FieldTable/index.tsx +35 -6
  33. package/src/framework/fields/FieldTableSelect/index.tsx +18 -2
  34. package/src/framework/fields/FieldUploadFile/index.tsx +69 -28
  35. package/src/framework/fields/index.ts +0 -1
  36. package/src/framework/index.ts +2 -0
  37. package/src/framework/router/Link.tsx +26 -0
  38. package/src/framework/router/PageFrame.tsx +31 -0
  39. package/src/framework/router/history.ts +50 -0
  40. package/src/framework/router/index.ts +4 -0
  41. package/src/framework/router/matcher.ts +58 -0
  42. package/src/framework/utils/{ArrayUtils.js → ArrayUtils.ts} +5 -5
  43. package/src/framework/utils/{DateUtils.js → DateUtils.ts} +19 -19
  44. package/src/framework/utils/{DeviceUtils.js → DeviceUtils.ts} +8 -1
  45. package/src/framework/utils/{EventBus.js → EventBus.ts} +13 -6
  46. package/src/framework/utils/MessageApi.ts +5 -0
  47. package/src/framework/utils/ModalApi.ts +5 -0
  48. package/src/framework/utils/ObjectUtils.ts +15 -0
  49. package/src/framework/utils/{StorageUtils.js → StorageUtils.ts} +7 -7
  50. package/src/framework/utils/{StringUtils.js → StringUtils.ts} +15 -15
  51. package/src/framework/utils/{TreeUtils.js → TreeUtils.ts} +19 -12
  52. package/src/framework/utils/{UrlUtils.js → UrlUtils.ts} +10 -10
  53. package/src/framework/utils/index.ts +2 -1
  54. package/src/framework/utils/system/{DictUtils.js → DictUtils.ts} +16 -4
  55. package/src/framework/utils/system/{HttpUtils.js → HttpUtils.ts} +25 -21
  56. package/src/framework/utils/system/PageUtils.ts +49 -0
  57. package/src/framework/utils/system/{PermUtils.js → PermUtils.ts} +5 -6
  58. package/src/framework/utils/system/index.ts +0 -2
  59. package/src/framework/views/ViewBoolean/index.tsx +6 -3
  60. package/src/framework/views/ViewFileButton/index.tsx +13 -8
  61. package/src/framework/views/ViewImage/index.tsx +19 -14
  62. package/src/framework/views/ViewRange/index.tsx +10 -7
  63. package/src/framework/views/ViewText/index.tsx +9 -11
  64. package/src/layouts/admin/AdminTabs.jsx +79 -0
  65. package/src/layouts/admin/AdminTabs.less +63 -0
  66. package/src/layouts/admin/HeaderRight.jsx +27 -13
  67. package/src/layouts/admin/TabLayout.jsx +124 -0
  68. package/src/layouts/admin/index.jsx +5 -6
  69. package/src/layouts/admin/index.less +1 -82
  70. package/src/layouts/index.tsx +148 -0
  71. package/src/main.jsx +8 -0
  72. package/src/pages/account/changePassword.jsx +14 -10
  73. package/src/pages/article/$code.jsx +2 -2
  74. package/src/pages/index.jsx +6 -4
  75. package/src/pages/{login.jsx → public/login.jsx} +7 -10
  76. package/src/pages/public/test.jsx +30 -0
  77. package/src/pages/{forceUpdatePwd.jsx → standalone/forceUpdatePwd.jsx} +3 -24
  78. package/src/pages/system/article/index.jsx +51 -56
  79. package/src/pages/system/dict/index.jsx +60 -91
  80. package/src/pages/system/file/index.jsx +23 -17
  81. package/src/pages/system/job/index.jsx +63 -72
  82. package/src/pages/system/log/index.jsx +11 -8
  83. package/src/pages/system/org/index.jsx +60 -80
  84. package/src/pages/system/role/index.jsx +50 -86
  85. package/src/pages/system/role/rolePerm.jsx +2 -2
  86. package/src/pages/system/user/index.jsx +120 -73
  87. package/src/pages/system/user/userPerm.jsx +2 -2
  88. package/vite-plugin/index.js +107 -0
  89. package/config/common-plugin.js +0 -145
  90. package/config/config.js +0 -5
  91. package/config/utils.js +0 -73
  92. package/src/.umi-production/appData.json +0 -2257
  93. package/src/.umi-production/core/EmptyRoute.tsx +0 -9
  94. package/src/.umi-production/core/defineApp.ts +0 -16
  95. package/src/.umi-production/core/helmet.ts +0 -10
  96. package/src/.umi-production/core/helmetContext.ts +0 -4
  97. package/src/.umi-production/core/history.ts +0 -72
  98. package/src/.umi-production/core/historyIntelli.ts +0 -132
  99. package/src/.umi-production/core/plugin.ts +0 -40
  100. package/src/.umi-production/core/pluginConfig.ts +0 -330
  101. package/src/.umi-production/core/pluginConfigJoi.d.ts +0 -7
  102. package/src/.umi-production/core/polyfill.ts +0 -220
  103. package/src/.umi-production/core/route.tsx +0 -47
  104. package/src/.umi-production/core/routeProps.js +0 -5
  105. package/src/.umi-production/core/routeProps.ts +0 -6
  106. package/src/.umi-production/core/terminal.ts +0 -37
  107. package/src/.umi-production/exports.ts +0 -17
  108. package/src/.umi-production/testBrowser.tsx +0 -90
  109. package/src/.umi-production/tsconfig.json +0 -43
  110. package/src/.umi-production/typings.d.ts +0 -136
  111. package/src/.umi-production/umi.ts +0 -83
  112. package/src/framework/GlobalData.d.ts +0 -10
  113. package/src/framework/GlobalData.js +0 -42
  114. package/src/framework/biz/HasPerm.tsx +0 -14
  115. package/src/framework/fields/FieldRemoteSelectMultiple/index.tsx +0 -29
  116. package/src/framework/utils/ArrayUtils.d.ts +0 -6
  117. package/src/framework/utils/DateUtils.d.ts +0 -20
  118. package/src/framework/utils/DeviceUtils.d.ts +0 -3
  119. package/src/framework/utils/EventBus.d.ts +0 -6
  120. package/src/framework/utils/MessageUtils.d.ts +0 -10
  121. package/src/framework/utils/MessageUtils.js +0 -43
  122. package/src/framework/utils/ObjectUtils.d.ts +0 -14
  123. package/src/framework/utils/ObjectUtils.js +0 -12
  124. package/src/framework/utils/StorageUtils.d.ts +0 -4
  125. package/src/framework/utils/StringUtils.d.ts +0 -17
  126. package/src/framework/utils/TreeUtils.d.ts +0 -13
  127. package/src/framework/utils/UrlUtils.d.ts +0 -6
  128. package/src/framework/utils/system/DictUtils.d.ts +0 -20
  129. package/src/framework/utils/system/FormRegistry.d.ts +0 -8
  130. package/src/framework/utils/system/FormRegistry.js +0 -31
  131. package/src/framework/utils/system/HttpUtils.d.ts +0 -14
  132. package/src/framework/utils/system/PageUtils.d.ts +0 -14
  133. package/src/framework/utils/system/PageUtils.js +0 -59
  134. package/src/framework/utils/system/PermUtils.d.ts +0 -5
  135. package/src/framework/utils/system/ThemeUtils.d.ts +0 -6
  136. package/src/framework/utils/system/ThemeUtils.js +0 -52
  137. package/src/layouts/PageRender.tsx +0 -29
  138. package/src/layouts/admin/TabPageRender.jsx +0 -210
  139. package/src/layouts/index.jsx +0 -120
  140. package/src/loading.jsx +0 -19
  141. package/src/pages/about.jsx +0 -16
  142. /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
- formRef = React.createRef()
28
+ modalRef = React.createRef()
29
29
  tableRef = React.createRef()
30
+ resetPwdRef = React.createRef()
30
31
 
31
32
  resetPwd(row) {
32
- HttpUtils.post('admin/sysUser/reset-pwd', {id: row.id}).then(rs => {
33
- Modal.success({
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.setState({formOpen: true, formValues: {}})
56
+ this.modalRef.current.open({})
52
57
  }
53
58
 
54
59
  handleEdit = record => {
55
- record.deptId = record.deptId || record.unitId
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
- <Popconfirm perm='sys-user:reset-password' title='确认重置密码?' onConfirm={() => this.resetPwd(record)}>
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).then(rs => {
145
- this.setState({formOpen: false})
146
- this.tableRef.current.reload()
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="管理系统用户" actions={<Button perm='sys-user:create' type="primary" icon={<PlusOutlined/>} onClick={this.handleAdd}>新增</Button>}>
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
- searchColumns={3}
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
- <Form.Item label='姓名' name='name'>
170
- <Input/>
171
- </Form.Item>
172
- <Form.Item label='登录账号' name='account'>
173
- <Input/>
174
- </Form.Item>
175
- <Form.Item label='手机号' name='phone'>
176
- <Input/>
177
- </Form.Item>
178
- <Form.Item label='角色' name='roleId'>
179
- <FieldRemoteSelect url='admin/sysRole/options' placeholder='请选择角色'/>
180
- </Form.Item>
181
- <Form.Item label='状态' name='enabled'>
182
- <Select allowClear placeholder='全部'>
183
- <Select.Option value={true}>启用</Select.Option>
184
- <Select.Option value={false}>停用</Select.Option>
185
- </Select>
186
- </Form.Item>
187
- </ProTable>
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
- <Modal title='系统用户'
193
- open={this.state.formOpen}
194
- onOk={() => this.formRef.current.submit()}
195
- onCancel={() => this.setState({formOpen: false})}
196
- destroyOnHidden
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 ref={this.formRef} labelCol={{flex: '100px'}}
200
- initialValues={this.state.formValues}
201
- onFinish={this.onFinish}>
202
- <Form.Item name='id' noStyle></Form.Item>
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
- <Form.Item label='所属机构' name='deptId' rules={[{required: true}]}>
205
- <FieldSysOrgTreeSelect/>
206
- </Form.Item>
226
+ <Form.Item label='启用状态' name='enabled' rules={[{required: true}]}>
227
+ <FieldBoolean/>
207
228
 
208
- <Form.Item label='姓名' name='name' rules={[{required: true}]}>
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
- <Form.Item label='启用状态' name='enabled' rules={[{required: true}]}>
224
- <FieldBoolean/>
225
- </Form.Item>
233
+ </FormModal>
226
234
 
227
- </Form>
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, FieldRemoteSelectMultiple, FieldSysOrgTree, HttpUtils} from "../../../framework";
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
- <FieldRemoteSelectMultiple url='admin/sysRole/options'/>
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
+ }
@@ -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
@@ -1,5 +0,0 @@
1
- import {defineConfig} from 'umi';
2
-
3
- export default defineConfig({
4
- plugins: ['./config/common-plugin'],
5
- });
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;