@jiangood/open-admin 3.1.2 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -40,8 +40,7 @@
40
40
  "type": "module",
41
41
  "files": [
42
42
  "src/*",
43
- "vite-plugin/*",
44
- "!src/pages/test/*"
43
+ "vite-plugin/*"
45
44
  ],
46
45
  "main": "src/index.ts",
47
46
  "exports": {
@@ -59,7 +58,7 @@
59
58
  "test:e2e": "playwright test",
60
59
  "test:e2e:ui": "playwright test --ui"
61
60
  },
62
- "version": "3.1.2",
61
+ "version": "3.1.3",
63
62
  "overrides": {
64
63
  "zod-validation-error": "^4.0.2"
65
64
  }
@@ -27,14 +27,9 @@ class InnerFieldDeptTreeSelect extends React.Component<ComponentProps<typeof Fie
27
27
  return <FieldRemoteTreeSelect url="admin/sysOrg/dept-tree" {...this.props} />;
28
28
  }
29
29
  }
30
- class InnerFieldOrgTreeSelect extends React.Component<ComponentProps<typeof FieldRemoteTreeSelect>> {
31
- render() {
32
- return <FieldRemoteTreeSelect url="admin/sysOrg/dept-tree" {...this.props} />;
33
- }
34
- }
35
30
  class InnerFieldOrgTreeMultipleSelect extends React.Component<ComponentProps<typeof FieldRemoteTreeSelect>> {
36
31
  render() {
37
- return <FieldRemoteTreeSelect url="admin/sysOrg/dept-tree" multiple {...this.props} />;
32
+ return <InnerFieldDeptTreeSelect multiple {...this.props} />;
38
33
  }
39
34
  }
40
35
 
@@ -42,6 +37,7 @@ export const FieldUserSelect = InnerFieldUserSelect;
42
37
  export const FieldUserSelectMultiple = InnerFieldUserSelectMultiple;
43
38
  export const FieldUnitTreeSelect = InnerFieldUnitTreeSelect;
44
39
  export const FieldDeptTreeSelect = InnerFieldDeptTreeSelect;
45
- export const FieldOrgTreeSelect = InnerFieldOrgTreeSelect;
40
+ /** FieldDeptTreeSelect 的别名 */
41
+ export const FieldOrgTreeSelect = InnerFieldDeptTreeSelect;
46
42
  export const FieldOrgTreeMultipleSelect = InnerFieldOrgTreeMultipleSelect;
47
43
 
@@ -59,7 +59,25 @@
59
59
  display: flex;
60
60
  align-items: center;
61
61
  justify-content: space-between;
62
+ gap: 8px;
62
63
  margin: 8px;
64
+
65
+ .pro-table-toolbar-left {
66
+ display: flex;
67
+ align-items: center;
68
+ gap: 8px;
69
+ flex: 1;
70
+ min-width: 0;
71
+ }
72
+
73
+ .pro-table-toolbar-right {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 8px;
77
+ flex-shrink: 0;
78
+ /* 只提供右侧插槽时,space-between 退化为 flex-start,靠 auto 兜住右对齐 */
79
+ margin-left: auto;
80
+ }
63
81
  }
64
82
  }
65
83
 
@@ -23,6 +23,15 @@ export interface ProTableRequestResult<T = unknown> {
23
23
  };
24
24
  }
25
25
 
26
+ /** 工具栏(含右侧插槽)渲染函数签名 */
27
+ export type ProTableToolBarRender<T = unknown> = (
28
+ params: Record<string, unknown>,
29
+ selection: {
30
+ selectedRows: T[];
31
+ selectedRowKeys: React.Key[];
32
+ }
33
+ ) => React.ReactNode;
34
+
26
35
  export interface ProTableProps<T = unknown> {
27
36
  /**
28
37
  * 数据请求(Promise 式),框架自动注入 page/size/sort 参数。
@@ -37,11 +46,10 @@ export interface ProTableProps<T = unknown> {
37
46
  actionRef?: React.RefObject<ProTableActionRef | undefined>;
38
47
  /** 获取搜索表单实例 */
39
48
  formRef?: React.RefObject<FormInstance | undefined>;
40
- /** 工具栏渲染,参数为当前搜索值与行选择状态 */
41
- toolBarRender?: (params: Record<string, unknown>, selection: {
42
- selectedRows: T[];
43
- selectedRowKeys: React.Key[];
44
- }) => React.ReactNode;
49
+ /** 工具栏渲染(左侧),参数为当前搜索值与行选择状态 */
50
+ toolBarRender?: ProTableToolBarRender<T>;
51
+ /** 工具栏右侧渲染,参数同 toolBarRender;用于导出/清理/刷新等右侧操作 */
52
+ toolBarRightRender?: ProTableToolBarRender<T>;
45
53
  rowKey?: string;
46
54
  /** 行选择:true 为默认 checkbox,对象可覆盖 type/onChange */
47
55
  rowSelection?: boolean | {
@@ -185,6 +193,7 @@ this.setState({loading: true})
185
193
  render() {
186
194
  const {
187
195
  toolBarRender,
196
+ toolBarRightRender,
188
197
  columns,
189
198
  rowSelection,
190
199
  rowKey = "id",
@@ -194,10 +203,13 @@ this.setState({loading: true})
194
203
  return <div className={'oa-pro-table '} id={this.id}>
195
204
  {this.renderForm()}
196
205
  <div className="pro-table-wrapper">
197
- {toolBarRender && <div className="pro-table-toolbar">
198
- <div className="pro-table-toolbar-left">
206
+ {(toolBarRender || toolBarRightRender) && <div className="pro-table-toolbar">
207
+ {toolBarRender && <div className="pro-table-toolbar-left">
199
208
  {this.getToolBarRenderNode(toolBarRender)}
200
- </div>
209
+ </div>}
210
+ {toolBarRightRender && <div className="pro-table-toolbar-right">
211
+ {this.getToolBarRenderNode(toolBarRightRender)}
212
+ </div>}
201
213
  </div>}
202
214
 
203
215
 
@@ -268,7 +280,7 @@ this.setState({loading: true})
268
280
  )
269
281
  };
270
282
 
271
- getToolBarRenderNode(toolBarRender: NonNullable<ProTableProps<T>['toolBarRender']>) {
283
+ getToolBarRenderNode(toolBarRender: ProTableToolBarRender<T>) {
272
284
  if (!toolBarRender) {
273
285
  return
274
286
  }
@@ -5,6 +5,7 @@ import React from "react";
5
5
  import dayjs from "dayjs";
6
6
  import {DatePicker, TimePicker} from "antd";
7
7
  import {DateUtils} from "../../utils";
8
+ import {DATE_PICKER_SPECS} from '../datePickerSpecs';
8
9
  import type {FieldProps} from '../types';
9
10
 
10
11
  export interface FieldDateProps extends FieldProps<string> {
@@ -28,67 +29,20 @@ export class FieldDate extends React.Component<FieldDateProps> {
28
29
  render() {
29
30
  const {type, value, onChange, ...rest} = this.props;
30
31
  const formattedType = DateUtils.convertTypeToFormat(type as string);
32
+ const spec = DATE_PICKER_SPECS[formattedType];
31
33
 
32
- switch (formattedType) {
33
- case 'YYYY':
34
- return <DatePicker
35
- value={this.strToDate(value, formattedType)}
36
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY'))}
37
- picker="year"
38
- {...rest}
39
- />;
40
- case 'YYYY-MM':
41
- return <DatePicker
42
- value={this.strToDate(value, 'YYYY-MM')}
43
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY-MM'))}
44
- picker="month"
45
- {...rest}
46
- />;
47
- case 'YYYY-QQ':
48
- return <DatePicker
49
- value={this.strToDate(value, 'YYYY-QQ')}
50
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY-QQ'))}
51
- picker="quarter"
52
- {...rest}
53
- />;
54
- case 'YYYY-MM-DD':
55
- return <DatePicker
56
- value={this.strToDate(value, 'YYYY-MM-DD')}
57
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY-MM-DD'))}
58
- {...rest}
59
- ></DatePicker>;
60
- case 'YYYY-MM-DD HH:mm':
61
- return <DatePicker
62
- value={this.strToDate(value, 'YYYY-MM-DD HH:mm')}
63
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY-MM-DD HH:mm'))}
64
- format='YYYY-MM-DD HH:mm'
65
- showTime
66
- {...rest}
67
- ></DatePicker>;
68
- case 'YYYY-MM-DD HH:mm:ss':
69
- return <DatePicker
70
- value={this.strToDate(value, 'YYYY-MM-DD HH:mm:ss')}
71
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY-MM-DD HH:mm:ss'))}
72
- showTime
73
- {...rest}
74
- ></DatePicker>;
75
- case 'HH:mm':
76
- return <TimePicker
77
- format='HH:mm'
78
- value={this.strToDate(value, 'HH:mm')}
79
- onChange={v => onChange?.(this.dateToStr(v, 'HH:mm'))}
80
- {...rest}
81
- ></TimePicker>;
82
- case 'HH:mm:ss':
83
- return <TimePicker
84
- value={this.strToDate(value, 'HH:mm:ss')}
85
- onChange={v => onChange?.(this.dateToStr(v, 'HH:mm:ss'))}
86
- {...rest}
87
- ></TimePicker>;
88
- default:
89
- return <div>未知组件 {formattedType}</div>;
34
+ if (!spec) {
35
+ return <div>未知组件 {formattedType}</div>;
90
36
  }
91
37
 
38
+ const Picker = spec.time ? TimePicker : DatePicker;
39
+ return <Picker
40
+ value={this.strToDate(value, formattedType)}
41
+ onChange={v => onChange?.(this.dateToStr(v, formattedType))}
42
+ {...spec}
43
+ {...rest}
44
+ />;
45
+
92
46
  }
93
47
 
94
48
  strToDate(value: string | number | dayjs.Dayjs | null | undefined, fmt: string): dayjs.Dayjs | null | undefined {
@@ -5,6 +5,7 @@ import React from "react";
5
5
  import dayjs from "dayjs";
6
6
  import {DatePicker, TimePicker} from "antd";
7
7
  import {DateUtils, StringUtils} from "../../utils";
8
+ import {DATE_PICKER_SPECS} from '../datePickerSpecs';
8
9
  import type {FieldProps} from '../types';
9
10
 
10
11
  const SP = StringUtils.ISO_SPLITTER;
@@ -33,67 +34,20 @@ export class FieldDateRange extends React.Component<FieldDateRangeProps> {
33
34
  render() {
34
35
  const {type, value, onChange, ...rest} = this.props;
35
36
  const formattedType = DateUtils.convertTypeToFormat(type as string);
37
+ const spec = DATE_PICKER_SPECS[formattedType];
36
38
 
37
- switch (formattedType) {
38
- case 'YYYY':
39
- return <DatePicker.RangePicker
40
- value={this.strToDate(value)}
41
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY'))}
42
- picker="year"
43
- {...rest}
44
- />;
45
- case 'YYYY-MM':
46
- return <DatePicker.RangePicker
47
- value={this.strToDate(value)}
48
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY-MM'))}
49
- picker="month"
50
- {...rest}
51
- />;
52
- case 'YYYY-QQ':
53
- return <DatePicker.RangePicker
54
- value={this.strToDate(value)}
55
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY-QQ'))}
56
- picker="quarter"
57
- {...rest}
58
- />;
59
- case 'YYYY-MM-DD':
60
- return <DatePicker.RangePicker
61
- value={this.strToDate(value)}
62
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY-MM-DD'))}
63
- {...rest}
64
- />;
65
- case 'YYYY-MM-DD HH:mm':
66
- return <DatePicker.RangePicker
67
- value={this.strToDate(value)}
68
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY-MM-DD HH:mm'))}
69
- format='YYYY-MM-DD HH:mm'
70
- showTime
71
- {...rest}
72
- />;
73
- case 'YYYY-MM-DD HH:mm:ss':
74
- return <DatePicker.RangePicker
75
- value={this.strToDate(value)}
76
- onChange={v => onChange?.(this.dateToStr(v, 'YYYY-MM-DD HH:mm:ss'))}
77
- showTime
78
- {...rest}
79
- />;
80
- case 'HH:mm':
81
- return <TimePicker.RangePicker
82
- format='HH:mm'
83
- value={this.strToDate(value)}
84
- onChange={v => onChange?.(this.dateToStr(v, 'HH:mm'))}
85
- {...rest}
86
- />;
87
- case 'HH:mm:ss':
88
- return <TimePicker.RangePicker
89
- value={this.strToDate(value)}
90
- onChange={v => onChange?.(this.dateToStr(v, 'HH:mm:ss'))}
91
- {...rest}
92
- />;
93
- default:
94
- return <div>未知组件 {formattedType}</div>;
39
+ if (!spec) {
40
+ return <div>未知组件 {formattedType}</div>;
95
41
  }
96
42
 
43
+ const Picker = spec.time ? TimePicker.RangePicker : DatePicker.RangePicker;
44
+ return <Picker
45
+ value={this.strToDate(value)}
46
+ onChange={v => onChange?.(this.dateToStr(v, formattedType))}
47
+ {...spec}
48
+ {...rest}
49
+ />;
50
+
97
51
  }
98
52
 
99
53
  strToDate(v: string | null | undefined): [dayjs.Dayjs, dayjs.Dayjs] | null {
@@ -62,7 +62,7 @@ export class FieldEditor extends React.Component<FieldEditorProps, {loading: boo
62
62
  plugins: [
63
63
  'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
64
64
  'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
65
- 'insertdatetime', 'media', 'table', 'code', 'help', 'wordcount',
65
+ 'insertdatetime', 'media', 'table', 'help', 'wordcount',
66
66
  'emoticons'
67
67
  ],
68
68
  image_description: false,
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import React from "react";
5
5
  import {FieldRemoteTree} from "../FieldRemoteTree";
6
+ import {orgTreeUrl} from '../orgTree';
6
7
  import type {FieldProps} from '../types';
7
8
 
8
9
  interface FieldSysOrgTreeProps extends FieldProps<string[]> {
@@ -16,10 +17,7 @@ export class FieldSysOrgTree extends React.Component<FieldSysOrgTreeProps> {
16
17
 
17
18
  render() {
18
19
  const {type, ...rest} = this.props;
19
- const url = type === 'dept'?
20
- '/admin/sysOrg/dept-tree':
21
- '/admin/sysOrg/unit-tree';
22
- return <FieldRemoteTree url={url} {...rest} />;
20
+ return <FieldRemoteTree url={orgTreeUrl(type)} {...rest} />;
23
21
  }
24
22
 
25
- }
23
+ }
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import {FieldRemoteTreeSelect} from "../FieldRemoteTreeSelect";
3
+ import {orgTreeUrl} from '../orgTree';
3
4
  import type {FieldProps} from '../types';
4
5
 
5
6
  interface FieldSysOrgTreeSelectProps extends FieldProps<string | string[]> {
@@ -14,13 +15,6 @@ export class FieldSysOrgTreeSelect extends React.Component<FieldSysOrgTreeSelect
14
15
 
15
16
  render() {
16
17
  const {type, ...rest} = this.props;
17
- let url = '/admin/sysOrg/dept-tree';
18
- if (type === 'unit') {
19
- url = '/admin/sysOrg/unit-tree';
20
- } else if (type === 'shop') {
21
- url = '/admin/sysOrg/tree?type=3';
22
- }
23
-
24
- return <FieldRemoteTreeSelect url={url} {...rest}/>;
18
+ return <FieldRemoteTreeSelect url={orgTreeUrl(type)} {...rest}/>;
25
19
  }
26
20
  }
@@ -0,0 +1,22 @@
1
+ export interface DatePickerSpec {
2
+ /** antd picker 属性 */
3
+ picker?: 'year' | 'month' | 'quarter';
4
+ /** antd showTime 属性 */
5
+ showTime?: boolean;
6
+ /** antd format 属性 */
7
+ format?: string;
8
+ /** 使用 TimePicker 而非 DatePicker */
9
+ time?: boolean;
10
+ }
11
+
12
+ /** 日期类型 → antd 选择器属性映射(FieldDate / FieldDateRange 共用) */
13
+ export const DATE_PICKER_SPECS: Record<string, DatePickerSpec> = {
14
+ 'YYYY': {picker: 'year'},
15
+ 'YYYY-MM': {picker: 'month'},
16
+ 'YYYY-QQ': {picker: 'quarter'},
17
+ 'YYYY-MM-DD': {},
18
+ 'YYYY-MM-DD HH:mm': {showTime: true, format: 'YYYY-MM-DD HH:mm'},
19
+ 'YYYY-MM-DD HH:mm:ss': {showTime: true},
20
+ 'HH:mm': {time: true, format: 'HH:mm'},
21
+ 'HH:mm:ss': {time: true},
22
+ }
@@ -0,0 +1,3 @@
1
+ export function orgTreeUrl(type: string): string {
2
+ return type === 'dept' ? '/admin/sysOrg/dept-tree' : '/admin/sysOrg/unit-tree';
3
+ }
@@ -0,0 +1,205 @@
1
+ import {Button, Collapse, Drawer, Form, Input, message, Modal, Select, Space, Switch, Tag, Typography} from 'antd'
2
+ import React from 'react'
3
+ import {HttpClient, Page, Perm} from '../../../framework'
4
+
5
+ /**
6
+ * 代码生成:动态扫描实体,按实体生成常见 CRUD 代码并写入项目源码目录。
7
+ */
8
+ export default class CodegenPage extends React.Component {
9
+
10
+ state = {
11
+ entities: [],
12
+ className: undefined,
13
+ module: '',
14
+ label: '',
15
+ parentMenu: 'sys',
16
+ overwrite: false,
17
+
18
+ files: [],
19
+ previewOpen: false,
20
+ confirmOpen: false,
21
+
22
+ result: null,
23
+ resultOpen: false,
24
+
25
+ loading: false,
26
+ }
27
+
28
+ componentDidMount() {
29
+ this.loadEntities()
30
+ }
31
+
32
+ loadEntities = () => {
33
+ HttpClient.get('admin/codegen/entity-options', null, {toastError: false}).then(rs => {
34
+ this.setState({entities: rs.data || []})
35
+ }).catch(e => {
36
+ console.error('[Codegen] 加载实体列表失败:', e)
37
+ })
38
+ }
39
+
40
+ handleEntityChange = (value, option) => {
41
+ const data = option?.data || {}
42
+ this.setState({className: value, module: data.module || '', label: data.label || ''})
43
+ }
44
+
45
+ buildReq = () => ({
46
+ className: this.state.className,
47
+ module: this.state.module,
48
+ label: this.state.label,
49
+ parentMenu: this.state.parentMenu,
50
+ overwrite: this.state.overwrite,
51
+ })
52
+
53
+ handlePreview = () => {
54
+ if (!this.state.className) {
55
+ message.warning('请先选择实体')
56
+ return
57
+ }
58
+ this.setState({loading: true})
59
+ HttpClient.post('admin/codegen/preview', this.buildReq(), null, {toastError: false}).then(rs => {
60
+ this.setState({files: rs.data || [], previewOpen: true})
61
+ }).catch(e => {
62
+ console.error('[Codegen] 预览失败:', e)
63
+ message.error(e?.message || '预览失败')
64
+ }).finally(() => this.setState({loading: false}))
65
+ }
66
+
67
+ handleGenerate = () => {
68
+ if (!this.state.className) {
69
+ message.warning('请先选择实体')
70
+ return
71
+ }
72
+ this.setState({confirmOpen: true})
73
+ }
74
+
75
+ doGenerate = () => {
76
+ this.setState({confirmOpen: false, loading: true})
77
+ HttpClient.post('admin/codegen/generate', this.buildReq(), null, {toastError: false}).then(rs => {
78
+ this.setState({result: rs.data, resultOpen: true, previewOpen: false})
79
+ }).catch(e => {
80
+ console.error('[Codegen] 生成失败:', e)
81
+ message.error(e?.message || '生成失败')
82
+ }).finally(() => this.setState({loading: false}))
83
+ }
84
+
85
+ copy = (text) => {
86
+ if (navigator.clipboard) {
87
+ navigator.clipboard.writeText(text)
88
+ .then(() => message.success('已复制'))
89
+ .catch(() => message.error('复制失败'))
90
+ } else {
91
+ message.warning('当前浏览器不支持剪贴板')
92
+ }
93
+ }
94
+
95
+ selectedOption = () => this.state.entities.find(e => e.value === this.state.className)
96
+
97
+ render() {
98
+ const selected = this.selectedOption()
99
+ return <Page title='代码生成' description='扫描实体,生成 Repository / Service / Controller / 前端页面 / 菜单 代码并写入项目源码目录'>
100
+ <Form layout='vertical' style={{maxWidth: 720}}>
101
+ <Form.Item label='实体' required>
102
+ <Select
103
+ showSearch
104
+ optionFilterProp='label'
105
+ placeholder='请选择实体'
106
+ options={this.state.entities}
107
+ value={this.state.className}
108
+ onChange={this.handleEntityChange}
109
+ />
110
+ </Form.Item>
111
+ {selected?.data?.frameworkEntity &&
112
+ <Typography.Text type='warning'>
113
+ 该实体属于框架内置包({selected.data.packageName}),生成的文件会写入同一包路径,请确认输出位置后再生成。
114
+ </Typography.Text>
115
+ }
116
+ <Form.Item label='模块名(请求前缀 / 前端目录)'>
117
+ <Input placeholder='如 customer'
118
+ value={this.state.module}
119
+ onChange={e => this.setState({module: e.target.value})}/>
120
+ </Form.Item>
121
+ <Form.Item label='中文名'>
122
+ <Input value={this.state.label}
123
+ onChange={e => this.setState({label: e.target.value})}/>
124
+ </Form.Item>
125
+ <Form.Item label='父菜单 id' tooltip='默认挂在系统管理(sys)下'>
126
+ <Input value={this.state.parentMenu}
127
+ onChange={e => this.setState({parentMenu: e.target.value})}/>
128
+ </Form.Item>
129
+ <Form.Item label='覆盖已存在文件'>
130
+ <Switch checked={this.state.overwrite}
131
+ onChange={checked => this.setState({overwrite: checked})}/>
132
+ </Form.Item>
133
+ <Space>
134
+ <Perm code='sys-codegen:generate'>
135
+ <Button type='primary' loading={this.state.loading} onClick={this.handlePreview}>预览</Button>
136
+ </Perm>
137
+ <Button onClick={this.loadEntities}>刷新实体</Button>
138
+ </Space>
139
+ </Form>
140
+
141
+ <Drawer title='生成预览' width={960}
142
+ open={this.state.previewOpen}
143
+ onClose={() => this.setState({previewOpen: false})}
144
+ extra={
145
+ <Perm code='sys-codegen:generate'>
146
+ <Button type='primary' loading={this.state.loading} onClick={this.handleGenerate}>生成并写入</Button>
147
+ </Perm>
148
+ }>
149
+ <Collapse
150
+ items={this.state.files.map((file, index) => ({
151
+ key: String(index),
152
+ label: (
153
+ <Space>
154
+ <span>{file.path}</span>
155
+ {file.exists
156
+ ? <Tag color='orange'>已存在</Tag>
157
+ : <Tag color='green'>新增</Tag>}
158
+ </Space>
159
+ ),
160
+ children: (
161
+ <>
162
+ <Button size='small' style={{marginBottom: 8}}
163
+ onClick={() => this.copy(file.content)}>复制</Button>
164
+ <pre style={{
165
+ maxHeight: 420,
166
+ overflow: 'auto',
167
+ background: '#f6f8fa',
168
+ padding: 12,
169
+ borderRadius: 4,
170
+ }}>{file.content}</pre>
171
+ </>
172
+ ),
173
+ }))}
174
+ />
175
+ </Drawer>
176
+
177
+ <Modal title='确认生成'
178
+ open={this.state.confirmOpen}
179
+ confirmLoading={this.state.loading}
180
+ onOk={this.doGenerate}
181
+ onCancel={() => this.setState({confirmOpen: false})}>
182
+ <p>将按以下配置生成代码到项目源码目录:</p>
183
+ <p>模块名:{this.state.module}</p>
184
+ <p>覆盖已存在文件:{this.state.overwrite ? '是' : '否'}</p>
185
+ </Modal>
186
+
187
+ <Modal title='生成结果'
188
+ open={this.state.resultOpen}
189
+ footer={null}
190
+ onCancel={() => this.setState({resultOpen: false})}>
191
+ <p>已写入 {this.state.result?.written?.length || 0} 个文件,跳过 {this.state.result?.skipped?.length || 0} 个。</p>
192
+ {this.state.result?.skipped?.length > 0 &&
193
+ <>
194
+ <Typography.Text type='warning'>以下文件已存在已跳过(可勾选“覆盖已存在文件”后重新生成):</Typography.Text>
195
+ <ul>
196
+ {this.state.result.skipped.map(path => <li key={path}>{path}</li>)}
197
+ </ul>
198
+ </>}
199
+ <ul>
200
+ {(this.state.result?.written || []).map(path => <li key={path}>{path}</li>)}
201
+ </ul>
202
+ </Modal>
203
+ </Page>
204
+ }
205
+ }
@@ -1,30 +0,0 @@
1
- import React from "react";
2
- import {Page} from "../../framework";
3
- import {Button, message, Modal, Space} from "antd";
4
-
5
- export default class TestPage extends React.Component {
6
-
7
- state = {
8
- modalOpen:false
9
- }
10
-
11
- render() {
12
- return <Page title="测试组件">
13
-
14
- <Space>
15
- <Button type={"primary"} onClick={()=>{message.info('你好,李白!')}} >message.info</Button>
16
-
17
- <Button danger onClick={()=>{Modal.info({content:'你好,李白!'})}} >modal.info</Button>
18
-
19
- <Button onClick={()=>{this.setState({modalOpen:true})}} >modal.组件</Button>
20
-
21
- </Space>
22
-
23
- <Modal title='modal组件' open={this.state.modalOpen} onCancel={()=>this.setState({modalOpen:false})}>
24
- 内容内容
25
- </Modal>
26
-
27
-
28
- </Page>
29
- }
30
- }