@yuku123/z-frontend-common 0.2.9 → 1.0.0
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/LICENSE +25 -0
- package/README.md +55 -42
- package/dist/index.js +1 -0
- package/package.json +49 -44
- package/src/components/ErrorBoundary/index.jsx +24 -77
- package/src/components/Layout/index.jsx +141 -38
- package/src/components/LoginPage/index.jsx +47 -21
- package/src/components/StatusTag/index.jsx +14 -12
- package/src/index.js +32 -38
- package/src/services/request.js +74 -4
- package/dist/z-frontend-common.css +0 -1
- package/dist/z-frontend-common.es.js +0 -51275
- package/dist/z-frontend-common.umd.js +0 -459
- package/src/components/Ctc/Layout.jsx +0 -330
- package/src/components/Ctc/Layout.module.css +0 -145
- package/src/components/Ctc/agentTeam/index.tsx +0 -308
- package/src/components/Ctc/login/AuthPage.module.css +0 -26
- package/src/components/Ctc/login/AuthPage.tsx +0 -235
- package/src/components/Ctc/login/index.less +0 -49
- package/src/components/Ctc/login/index.tsx +0 -142
- package/src/components/Ctc/uc/UCLogin.tsx +0 -31
- package/src/components/Ctc/uc/UCPhoneLogin.tsx +0 -62
- package/src/components/Ctc/uc/UCRegister.tsx +0 -37
- package/src/components/Ctc/uc/UCResetPassword.tsx +0 -69
- package/src/components/Ctc/uc/index.tsx +0 -15
- package/src/components/Ctc/userPanel/index.tsx +0 -999
- package/src/components/Ctc/webide/index.tsx +0 -272
- package/src/components/LowCode/LowCodeModel.jsx +0 -962
- package/src/components/LowCode/LowCodePage.jsx +0 -31
- package/src/components/LowCode/LowCodeRuntime.jsx +0 -335
- package/src/components/LowCode/MaterializePage.jsx +0 -235
- package/src/components/LowCode/index.js +0 -1
- package/src/components/MockPlatform/CurlImportModal.jsx +0 -362
- package/src/components/MockPlatform/EndpointsTab.jsx +0 -509
- package/src/components/MockPlatform/EnvironmentsTab.jsx +0 -212
- package/src/components/MockPlatform/MockTemplateHelper.jsx +0 -200
- package/src/components/MockPlatform/OpenApiImportModal.jsx +0 -305
- package/src/components/MockPlatform/RecordingsTab.jsx +0 -397
- package/src/components/MockPlatform/RequestLogsTab.jsx +0 -239
- package/src/components/MockPlatform/ScenariosTab.jsx +0 -236
- package/src/components/MockPlatform/TestCasesTab.jsx +0 -462
- package/src/components/MockPlatform/index.jsx +0 -127
- package/src/components/Overview.jsx +0 -74
- package/src/dev.jsx +0 -28
- package/src/services/agentTeam.js +0 -7
- package/src/services/api.js +0 -84
- package/src/services/ctcAc.js +0 -7
- package/src/services/ctcAcDomain.js +0 -7
- package/src/services/ctcAuthorization.js +0 -7
- package/src/services/ctcSurl.js +0 -7
- package/src/services/ctcUser.js +0 -7
- package/src/services/job.js +0 -7
- package/src/services/metaApp.js +0 -7
- package/src/services/privateConfig.js +0 -7
- package/src/services/webide.js +0 -6
- package/src/services/workspace.js +0 -21
- package/src/utils/request.ts +0 -102
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import {useParams} from 'react-router-dom'
|
|
3
|
-
import LowCodeModel from './LowCodeModel'
|
|
4
|
-
import LowCodeRuntime from './LowCodeRuntime'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* LowCodePage 派发器 (z-lc Phase 1)
|
|
8
|
-
* <p>
|
|
9
|
-
* 单一入口 /form/:entity 拆分为两个真实页面:
|
|
10
|
-
* <ul>
|
|
11
|
-
* <li>/form/_model?appCode=xxx → LowCodeModel (实体/字段编辑器)</li>
|
|
12
|
-
* <li>/form/{entityCode}?appCode=xxx → LowCodeRuntime (通用 CRUD)</li>
|
|
13
|
-
* </ul>
|
|
14
|
-
*/
|
|
15
|
-
const LowCodePage = () => {
|
|
16
|
-
const {entity: entityCode} = useParams()
|
|
17
|
-
if (!entityCode) {
|
|
18
|
-
return (
|
|
19
|
-
<div style={{padding: 24}}>
|
|
20
|
-
缺少实体编码. 请访问 <code>/form/_model</code> 编辑模型,
|
|
21
|
-
或 <code>/form/{entityCode}</code> 运行时 CRUD.
|
|
22
|
-
</div>
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
if (entityCode === '_model') {
|
|
26
|
-
return <LowCodeModel/>
|
|
27
|
-
}
|
|
28
|
-
return <LowCodeRuntime/>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export default LowCodePage
|
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
import React, {useCallback, useEffect, useState} from 'react'
|
|
2
|
-
import {useParams, useSearchParams} from 'react-router-dom'
|
|
3
|
-
import {
|
|
4
|
-
Alert,
|
|
5
|
-
Button,
|
|
6
|
-
Card,
|
|
7
|
-
DatePicker,
|
|
8
|
-
Empty,
|
|
9
|
-
Form,
|
|
10
|
-
Input,
|
|
11
|
-
InputNumber,
|
|
12
|
-
message,
|
|
13
|
-
Modal,
|
|
14
|
-
Select,
|
|
15
|
-
Space,
|
|
16
|
-
Spin,
|
|
17
|
-
Switch,
|
|
18
|
-
Table,
|
|
19
|
-
Tag
|
|
20
|
-
} from 'antd'
|
|
21
|
-
import {DatabaseOutlined, DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined} from '@ant-design/icons'
|
|
22
|
-
import request from '../../utils/request'
|
|
23
|
-
|
|
24
|
-
const {Option} = Select
|
|
25
|
-
|
|
26
|
-
const FIELD_TYPE_INPUT = {
|
|
27
|
-
STRING: {component: Input, props: {}},
|
|
28
|
-
TEXT: {component: Input.TextArea, props: {rows: 3}},
|
|
29
|
-
INT: {component: InputNumber, props: {}},
|
|
30
|
-
LONG: {component: InputNumber, props: {}},
|
|
31
|
-
DECIMAL: {component: InputNumber, props: {step: 0.01}},
|
|
32
|
-
BOOLEAN: {component: Switch, props: {}},
|
|
33
|
-
DATE: {component: DatePicker, props: {format: 'YYYY-MM-DD'}},
|
|
34
|
-
DATETIME: {component: DatePicker, props: {format: 'YYYY-MM-DD HH:mm:ss', showTime: true}},
|
|
35
|
-
JSON: {component: Input.TextArea, props: {rows: 3, placeholder: 'JSON 字符串'}},
|
|
36
|
-
REF: {component: InputNumber, props: {}}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* LowCodeRuntime: 通用 CRUD 页面
|
|
41
|
-
* <p>
|
|
42
|
-
* 通过 GET /api/lc/app/{appCode}/schema 取 entity 定义
|
|
43
|
-
* 通过 POST /api/lc/runtime/{entityCode}/{list|get|create|update|delete} 走 CRUD
|
|
44
|
-
*/
|
|
45
|
-
export default function LowCodeRuntime() {
|
|
46
|
-
const {entity: entityCodeParam} = useParams()
|
|
47
|
-
const [searchParams] = useSearchParams()
|
|
48
|
-
const appCode = searchParams.get('appCode') || 'demo'
|
|
49
|
-
const tenantCode = searchParams.get('tenant') || localStorage.getItem('z_tenant') || 'default'
|
|
50
|
-
|
|
51
|
-
const [schema, setSchema] = useState([])
|
|
52
|
-
const [entity, setEntity] = useState(null)
|
|
53
|
-
const [rows, setRows] = useState([])
|
|
54
|
-
const [total, setTotal] = useState(0)
|
|
55
|
-
const [page, setPage] = useState(1)
|
|
56
|
-
const [size, setSize] = useState(10)
|
|
57
|
-
const [loading, setLoading] = useState(false)
|
|
58
|
-
const [modalOpen, setModalOpen] = useState(false)
|
|
59
|
-
const [editing, setEditing] = useState(null) // null = create, { id } = update
|
|
60
|
-
const [form] = Form.useForm()
|
|
61
|
-
const [dictCache, setDictCache] = useState({}) // { dictCode: [{ value, label }] }
|
|
62
|
-
|
|
63
|
-
const fetchSchema = useCallback(async () => {
|
|
64
|
-
try {
|
|
65
|
-
const list = await request.get(`/lc/app/${appCode}/schema`, {params: {tenantCode}})
|
|
66
|
-
setSchema(Array.isArray(list) ? list : [])
|
|
67
|
-
const e = (list || []).find(x => x.entityCode === entityCodeParam)
|
|
68
|
-
setEntity(e || null)
|
|
69
|
-
} catch (err) {
|
|
70
|
-
message.error('加载 schema 失败: ' + (err?.message || '未知错误'))
|
|
71
|
-
setSchema([])
|
|
72
|
-
setEntity(null)
|
|
73
|
-
}
|
|
74
|
-
}, [appCode, tenantCode, entityCodeParam])
|
|
75
|
-
|
|
76
|
-
const fetchDict = useCallback(async (dictCode) => {
|
|
77
|
-
if (!dictCode || dictCache[dictCode]) return dictCache[dictCode] || []
|
|
78
|
-
try {
|
|
79
|
-
// 走 z-meta 字典接口: /dict/items/get?dictCode=xxx
|
|
80
|
-
const arr = await request.get(`/dict/items/get`, {params: {dictCode}})
|
|
81
|
-
const list = Array.isArray(arr) ? arr : []
|
|
82
|
-
const normalized = list.map(it => ({
|
|
83
|
-
value: it.itemValue ?? it.itemCode,
|
|
84
|
-
label: it.itemName ?? it.itemLabel ?? it.itemCode
|
|
85
|
-
}))
|
|
86
|
-
setDictCache(prev => ({...prev, [dictCode]: normalized}))
|
|
87
|
-
return normalized
|
|
88
|
-
} catch (e) {
|
|
89
|
-
// 字典不存在也不报错 — 业务表允许 dict 字段没值
|
|
90
|
-
return []
|
|
91
|
-
}
|
|
92
|
-
}, [dictCache])
|
|
93
|
-
|
|
94
|
-
const fetchList = useCallback(async () => {
|
|
95
|
-
if (!entity) return
|
|
96
|
-
setLoading(true)
|
|
97
|
-
try {
|
|
98
|
-
const res = await request.post(`/lc/runtime/${entity.entityCode}/list`, {
|
|
99
|
-
tenantCode,
|
|
100
|
-
appCode,
|
|
101
|
-
entityCode: entity.entityCode,
|
|
102
|
-
page, size
|
|
103
|
-
})
|
|
104
|
-
setRows(res?.records || [])
|
|
105
|
-
setTotal(res?.total || 0)
|
|
106
|
-
} catch (err) {
|
|
107
|
-
message.error('查询失败: ' + (err?.message || '未知错误'))
|
|
108
|
-
setRows([])
|
|
109
|
-
setTotal(0)
|
|
110
|
-
} finally {
|
|
111
|
-
setLoading(false)
|
|
112
|
-
}
|
|
113
|
-
}, [entity, tenantCode, appCode, page, size])
|
|
114
|
-
|
|
115
|
-
useEffect(() => {
|
|
116
|
-
fetchSchema()
|
|
117
|
-
}, [fetchSchema])
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
fetchList()
|
|
120
|
-
}, [fetchList])
|
|
121
|
-
|
|
122
|
-
// 预取字典
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
if (!entity || !entity.fields) return
|
|
125
|
-
entity.fields.forEach(f => {
|
|
126
|
-
if (f.dictCode) fetchDict(f.dictCode)
|
|
127
|
-
})
|
|
128
|
-
}, [entity, fetchDict])
|
|
129
|
-
|
|
130
|
-
if (!entityCodeParam) {
|
|
131
|
-
return <Empty description="缺少实体编码 (路径变量 :entity)"/>
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!entity) {
|
|
135
|
-
return (
|
|
136
|
-
<Card title={<><DatabaseOutlined/> z-lc 运行时</>}>
|
|
137
|
-
<Alert
|
|
138
|
-
type="warning"
|
|
139
|
-
showIcon
|
|
140
|
-
title={`实体 ${entityCodeParam} 未定义`}
|
|
141
|
-
description={`请先在 模型编辑器 (路径 /form/_model?appCode=${appCode}) 中定义实体, 然后刷新此页.`}
|
|
142
|
-
/>
|
|
143
|
-
</Card>
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const fields = entity.fields || []
|
|
148
|
-
|
|
149
|
-
const openCreate = () => {
|
|
150
|
-
setEditing(null)
|
|
151
|
-
form.resetFields()
|
|
152
|
-
setModalOpen(true)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const openEdit = async (row) => {
|
|
156
|
-
setEditing(row)
|
|
157
|
-
try {
|
|
158
|
-
const full = await request.post(`/lc/runtime/${entity.entityCode}/get`, {
|
|
159
|
-
tenantCode, appCode, entityCode: entity.entityCode, id: row.id
|
|
160
|
-
})
|
|
161
|
-
form.resetFields()
|
|
162
|
-
form.setFieldsValue(full || row)
|
|
163
|
-
} catch (e) {
|
|
164
|
-
// 回退到当前行
|
|
165
|
-
form.resetFields()
|
|
166
|
-
form.setFieldsValue(row)
|
|
167
|
-
}
|
|
168
|
-
setModalOpen(true)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const handleDelete = (row) => {
|
|
172
|
-
Modal.confirm({
|
|
173
|
-
title: '确定删除?',
|
|
174
|
-
content: `id=${row.id}`,
|
|
175
|
-
okType: 'danger',
|
|
176
|
-
onOk: async () => {
|
|
177
|
-
try {
|
|
178
|
-
await request.post(`/lc/runtime/${entity.entityCode}/delete`, {
|
|
179
|
-
tenantCode, appCode, entityCode: entity.entityCode, id: row.id
|
|
180
|
-
})
|
|
181
|
-
message.success('删除成功')
|
|
182
|
-
fetchList()
|
|
183
|
-
} catch (e) {
|
|
184
|
-
message.error('删除失败: ' + (e?.message || '未知错误'))
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const handleSubmit = async () => {
|
|
191
|
-
try {
|
|
192
|
-
const values = await form.validateFields()
|
|
193
|
-
const body = {
|
|
194
|
-
tenantCode, appCode, entityCode: entity.entityCode, fieldValues: {...values}
|
|
195
|
-
}
|
|
196
|
-
if (editing) {
|
|
197
|
-
body.fieldValues.id = editing.id
|
|
198
|
-
await request.post(`/lc/runtime/${entity.entityCode}/update`, body)
|
|
199
|
-
message.success('更新成功')
|
|
200
|
-
} else {
|
|
201
|
-
await request.post(`/lc/runtime/${entity.entityCode}/create`, body)
|
|
202
|
-
message.success('创建成功')
|
|
203
|
-
}
|
|
204
|
-
setModalOpen(false)
|
|
205
|
-
fetchList()
|
|
206
|
-
} catch (e) {
|
|
207
|
-
if (e?.errorFields) {
|
|
208
|
-
message.error('请补全必填字段')
|
|
209
|
-
} else {
|
|
210
|
-
message.error('提交失败: ' + (e?.message || '未知错误'))
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const renderCell = (f, row) => {
|
|
216
|
-
const v = row[f.fieldCode]
|
|
217
|
-
const labelKey = f.fieldCode + '_label'
|
|
218
|
-
if (row[labelKey] != null) {
|
|
219
|
-
return <Tag>{row[labelKey]}</Tag>
|
|
220
|
-
}
|
|
221
|
-
if (v == null) return '-'
|
|
222
|
-
if (f.fieldType === 'BOOLEAN') return v ? '是' : '否'
|
|
223
|
-
if (f.fieldType === 'DATETIME' || f.fieldType === 'DATE') {
|
|
224
|
-
// JdbcTemplate 返回 java.sql.Timestamp / Date
|
|
225
|
-
try {
|
|
226
|
-
return new Date(v).toLocaleString()
|
|
227
|
-
} catch {
|
|
228
|
-
return String(v)
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return String(v)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const columns = [
|
|
235
|
-
{title: 'ID', dataIndex: 'id', key: 'id', width: 80},
|
|
236
|
-
...fields.map(f => ({
|
|
237
|
-
title: f.fieldName || f.fieldCode,
|
|
238
|
-
dataIndex: f.fieldCode,
|
|
239
|
-
key: f.fieldCode,
|
|
240
|
-
render: (_, row) => renderCell(f, row)
|
|
241
|
-
})),
|
|
242
|
-
{
|
|
243
|
-
title: '操作',
|
|
244
|
-
key: 'action',
|
|
245
|
-
width: 180,
|
|
246
|
-
fixed: 'right',
|
|
247
|
-
render: (_, row) => (
|
|
248
|
-
<Space>
|
|
249
|
-
<Button type="link" icon={<EditOutlined/>} onClick={() => openEdit(row)}>编辑</Button>
|
|
250
|
-
<Button type="link" danger icon={<DeleteOutlined/>} onClick={() => handleDelete(row)}>删除</Button>
|
|
251
|
-
</Space>
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
]
|
|
255
|
-
|
|
256
|
-
const renderFormItem = (f) => {
|
|
257
|
-
const type = (f.fieldType || 'STRING').toUpperCase()
|
|
258
|
-
if (f.dictCode) {
|
|
259
|
-
const opts = dictCache[f.dictCode] || []
|
|
260
|
-
return (
|
|
261
|
-
<Select placeholder={`选择 ${f.fieldName || f.fieldCode}`} allowClear showSearch
|
|
262
|
-
optionFilterProp="label">
|
|
263
|
-
{opts.map(o => <Option key={o.value} value={o.value} label={o.label}>{o.label}</Option>)}
|
|
264
|
-
</Select>
|
|
265
|
-
)
|
|
266
|
-
}
|
|
267
|
-
const conf = FIELD_TYPE_INPUT[type] || FIELD_TYPE_INPUT.STRING
|
|
268
|
-
const Cmp = conf.component
|
|
269
|
-
return <Cmp {...conf.props} placeholder={f.fieldName || f.fieldCode}/>
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return (
|
|
273
|
-
<Card
|
|
274
|
-
title={<><DatabaseOutlined/> {entity.entityName || entity.entityCode} (运行时 CRUD)</>}
|
|
275
|
-
extra={
|
|
276
|
-
<Space>
|
|
277
|
-
<Button icon={<ReloadOutlined/>} onClick={fetchList}>刷新</Button>
|
|
278
|
-
<Button type="primary" icon={<PlusOutlined/>} onClick={openCreate}>新增</Button>
|
|
279
|
-
</Space>
|
|
280
|
-
}
|
|
281
|
-
>
|
|
282
|
-
<Alert
|
|
283
|
-
type="info"
|
|
284
|
-
showIcon
|
|
285
|
-
title={`App=${appCode} · Tenant=${tenantCode} · 字段数=${fields.length}`}
|
|
286
|
-
style={{marginBottom: 16}}
|
|
287
|
-
/>
|
|
288
|
-
<Spin spinning={loading}>
|
|
289
|
-
<Table
|
|
290
|
-
rowKey="id"
|
|
291
|
-
columns={columns}
|
|
292
|
-
dataSource={rows}
|
|
293
|
-
pagination={{
|
|
294
|
-
current: page, pageSize: size, total,
|
|
295
|
-
showSizeChanger: true, showTotal: t => `共 ${t} 条`,
|
|
296
|
-
onChange: (p, s) => {
|
|
297
|
-
setPage(p);
|
|
298
|
-
setSize(s)
|
|
299
|
-
}
|
|
300
|
-
}}
|
|
301
|
-
scroll={{x: 'max-content'}}
|
|
302
|
-
size="small"
|
|
303
|
-
/>
|
|
304
|
-
</Spin>
|
|
305
|
-
|
|
306
|
-
<Modal
|
|
307
|
-
title={editing ? `编辑 #${editing.id}` : '新增记录'}
|
|
308
|
-
open={modalOpen}
|
|
309
|
-
onCancel={() => setModalOpen(false)}
|
|
310
|
-
onOk={handleSubmit}
|
|
311
|
-
width={680}
|
|
312
|
-
destroyOnHidden
|
|
313
|
-
>
|
|
314
|
-
<Form form={form} layout="vertical">
|
|
315
|
-
{fields.map(f => (
|
|
316
|
-
<Form.Item
|
|
317
|
-
key={f.fieldCode}
|
|
318
|
-
name={f.fieldCode}
|
|
319
|
-
label={
|
|
320
|
-
<span>
|
|
321
|
-
{f.fieldName || f.fieldCode}
|
|
322
|
-
{f.required && <span style={{color: 'red'}}> *</span>}
|
|
323
|
-
<Tag style={{marginLeft: 8}}>{f.fieldType}</Tag>
|
|
324
|
-
</span>
|
|
325
|
-
}
|
|
326
|
-
rules={f.required ? [{required: true, message: '必填'}] : []}
|
|
327
|
-
>
|
|
328
|
-
{renderFormItem(f)}
|
|
329
|
-
</Form.Item>
|
|
330
|
-
))}
|
|
331
|
-
</Form>
|
|
332
|
-
</Modal>
|
|
333
|
-
</Card>
|
|
334
|
-
)
|
|
335
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import {useEffect, useState} from 'react'
|
|
2
|
-
import {Alert, Badge, Button, Card, Col, Form, Input, message, Modal, Popconfirm, Row, Select, Space, Table, Tabs, Tag, Tooltip, Typography} from 'antd'
|
|
3
|
-
import {
|
|
4
|
-
CheckCircleOutlined,
|
|
5
|
-
CodeOutlined,
|
|
6
|
-
CopyOutlined,
|
|
7
|
-
DeleteOutlined,
|
|
8
|
-
DownloadOutlined,
|
|
9
|
-
ExportOutlined,
|
|
10
|
-
FileTextOutlined,
|
|
11
|
-
HistoryOutlined,
|
|
12
|
-
ReloadOutlined
|
|
13
|
-
} from '@ant-design/icons'
|
|
14
|
-
import request from '../../utils/request'
|
|
15
|
-
|
|
16
|
-
const {Text, Paragraph} = Typography
|
|
17
|
-
const {TextArea} = Input
|
|
18
|
-
|
|
19
|
-
const STATUS_COLORS = {
|
|
20
|
-
PENDING: 'default', GENERATING: 'processing', READY: 'success', FAILED: 'error',
|
|
21
|
-
}
|
|
22
|
-
const TYPE_COLORS = {
|
|
23
|
-
Entity: 'blue', Mapper: 'cyan', Service: 'green', ServiceImpl: 'lime',
|
|
24
|
-
Controller: 'orange', ReactPage: 'purple', File: 'default',
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* z-lc 代码物化页面 (FEATURE006 T1).
|
|
29
|
-
* <p>
|
|
30
|
-
* 路径: /form/_materialize?appCode=xxx
|
|
31
|
-
* <p>
|
|
32
|
-
* 功能:
|
|
33
|
-
* 1. 选择导出路径 + entity 范围 → 触发物化 (异步)
|
|
34
|
-
* 2. 列出该 app 的历史物化批次 + 状态
|
|
35
|
-
* 3. 选中某个 READY 批次 → 查看 diff (生成的文件清单)
|
|
36
|
-
* 4. 单个文件 → 代码预览 + 复制到剪贴板
|
|
37
|
-
*/
|
|
38
|
-
export default function MaterializePage() {
|
|
39
|
-
const params = new URLSearchParams(window.location.search)
|
|
40
|
-
const initialAppCode = params.get('appCode') || 'demo'
|
|
41
|
-
|
|
42
|
-
const [appCode] = useState(initialAppCode)
|
|
43
|
-
const [batches, setBatches] = useState([])
|
|
44
|
-
const [loading, setLoading] = useState(false)
|
|
45
|
-
const [triggerOpen, setTriggerOpen] = useState(false)
|
|
46
|
-
const [diffOpen, setDiffOpen] = useState(false)
|
|
47
|
-
const [diffFiles, setDiffFiles] = useState([])
|
|
48
|
-
const [selectedFile, setSelectedFile] = useState(null)
|
|
49
|
-
const [entities, setEntities] = useState([])
|
|
50
|
-
const [form] = Form.useForm()
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
loadBatches()
|
|
54
|
-
loadEntities()
|
|
55
|
-
}, [appCode])
|
|
56
|
-
|
|
57
|
-
const loadBatches = async () => {
|
|
58
|
-
setLoading(true)
|
|
59
|
-
try {
|
|
60
|
-
const r = await request.get(`/lc/app/${appCode}/materialize/list`, {params: {limit: 50}})
|
|
61
|
-
setBatches(r?.data || [])
|
|
62
|
-
} catch (e) {
|
|
63
|
-
message.warning('加载批次列表失败')
|
|
64
|
-
} finally {
|
|
65
|
-
setLoading(false)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const loadEntities = async () => {
|
|
70
|
-
try {
|
|
71
|
-
const r = await request.get(`/lc/app/${appCode}/schema`)
|
|
72
|
-
const list = r?.data?.entities || r?.data || []
|
|
73
|
-
setEntities(list)
|
|
74
|
-
} catch (e) {
|
|
75
|
-
// ignore
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const handleTrigger = async () => {
|
|
80
|
-
try {
|
|
81
|
-
const values = await form.validateFields()
|
|
82
|
-
const payload = {
|
|
83
|
-
appCode,
|
|
84
|
-
materializationPath: values.materializationPath,
|
|
85
|
-
entityCodes: values.entityCodes?.length ? values.entityCodes : null,
|
|
86
|
-
description: values.description,
|
|
87
|
-
triggerSource: 'USER',
|
|
88
|
-
}
|
|
89
|
-
await request.post(`/lc/app/${appCode}/materialize`, payload)
|
|
90
|
-
message.success('物化已触发,请稍后查看状态')
|
|
91
|
-
setTriggerOpen(false)
|
|
92
|
-
form.resetFields()
|
|
93
|
-
loadBatches()
|
|
94
|
-
} catch (e) {
|
|
95
|
-
if (!e?.errorFields) message.error('触发失败:' + (e?.message || '未知错误'))
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const handleShowDiff = async (batch) => {
|
|
100
|
-
try {
|
|
101
|
-
const r = await request.get(`/lc/app/${appCode}/materialize/diff`, {params: {id: batch.id}})
|
|
102
|
-
const files = r?.data || []
|
|
103
|
-
setDiffFiles(files)
|
|
104
|
-
setDiffOpen(true)
|
|
105
|
-
setSelectedFile(null)
|
|
106
|
-
} catch (e) {
|
|
107
|
-
message.error('加载文件清单失败')
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const handleCopy = (text) => {
|
|
112
|
-
try {
|
|
113
|
-
navigator.clipboard?.writeText(text)
|
|
114
|
-
message.success('已复制到剪贴板')
|
|
115
|
-
} catch {
|
|
116
|
-
message.warning('复制失败')
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const handleDownload = (file) => {
|
|
121
|
-
const blob = new Blob([file.content], {type: 'text/plain;charset=utf-8'})
|
|
122
|
-
const url = URL.createObjectURL(blob)
|
|
123
|
-
const a = document.createElement('a')
|
|
124
|
-
a.href = url
|
|
125
|
-
a.download = file.relativePath.split('/').pop()
|
|
126
|
-
a.click()
|
|
127
|
-
URL.revokeObjectURL(url)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const batchColumns = [
|
|
131
|
-
{title: '批次号', dataIndex: 'exportVersion', width: 180, render: v => <Text code>{v}</Text>},
|
|
132
|
-
{
|
|
133
|
-
title: '状态', dataIndex: 'status', width: 100,
|
|
134
|
-
render: v => <Badge status={STATUS_COLORS[v] === 'success' ? 'success' : STATUS_COLORS[v] === 'error' ? 'error' : 'processing'} text={v}/>
|
|
135
|
-
},
|
|
136
|
-
{title: '文件数', dataIndex: 'fileCount', width: 80, align: 'center', render: v => v || 0},
|
|
137
|
-
{title: '来源', dataIndex: 'triggerSource', width: 80, render: v => <Tag>{v}</Tag>},
|
|
138
|
-
{title: '路径', dataIndex: 'materializationPath', ellipsis: true, render: v => <Text type="secondary" style={{fontSize: 12}}>{v}</Text>},
|
|
139
|
-
{title: '创建时间', dataIndex: 'createTime', width: 170},
|
|
140
|
-
{
|
|
141
|
-
title: '操作', key: 'action', width: 200, fixed: 'right',
|
|
142
|
-
render: (_, r) => (
|
|
143
|
-
<Space size="small">
|
|
144
|
-
<Button size="small" type="link" icon={<FileTextOutlined/>}
|
|
145
|
-
onClick={() => handleShowDiff(r)}
|
|
146
|
-
disabled={r.status !== 'READY' && r.status !== 'GENERATING'}>
|
|
147
|
-
查看文件
|
|
148
|
-
</Button>
|
|
149
|
-
</Space>
|
|
150
|
-
)
|
|
151
|
-
},
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
const fileColumns = [
|
|
155
|
-
{title: '类型', dataIndex: 'type', width: 100, render: v => <Tag color={TYPE_COLORS[v] || 'default'}>{v}</Tag>},
|
|
156
|
-
{title: 'Entity', dataIndex: 'entityCode', width: 120, render: v => v ? <Text code>{v}</Text> : '-'},
|
|
157
|
-
{title: '相对路径', dataIndex: 'relativePath', ellipsis: true, render: v => <Text code style={{fontSize: 12}}>{v}</Text>},
|
|
158
|
-
{title: '大小', dataIndex: 'sizeBytes', width: 90, render: v => `${(v/1024).toFixed(1)}KB`},
|
|
159
|
-
{
|
|
160
|
-
title: '操作', key: 'action', width: 200, fixed: 'right',
|
|
161
|
-
render: (_, r) => (
|
|
162
|
-
<Space size="small">
|
|
163
|
-
<Button size="small" type="link" onClick={() => setSelectedFile(r)}>预览</Button>
|
|
164
|
-
<Button size="small" type="link" icon={<CopyOutlined/>} onClick={() => handleCopy(r.content)}>复制</Button>
|
|
165
|
-
<Button size="small" type="link" icon={<DownloadOutlined/>} onClick={() => handleDownload(r)}>下载</Button>
|
|
166
|
-
</Space>
|
|
167
|
-
)
|
|
168
|
-
},
|
|
169
|
-
]
|
|
170
|
-
|
|
171
|
-
return (
|
|
172
|
-
<div style={{padding: 16}}>
|
|
173
|
-
<Card>
|
|
174
|
-
<Alert type="info" showIcon icon={<CodeOutlined/>} style={{marginBottom: 16}}
|
|
175
|
-
message={`z-lc 代码物化 (FEATURE006 T1) — App: ${appCode}`}
|
|
176
|
-
description="将低代码定义的 entity/field 渲染为可编译的 Java (Entity/Mapper/Service/Controller) + React 页面代码。导出是异步的, 完成后可在此查看文件清单。"/>
|
|
177
|
-
|
|
178
|
-
<Space style={{marginBottom: 16}}>
|
|
179
|
-
<Button icon={<ReloadOutlined/>} onClick={loadBatches}>刷新</Button>
|
|
180
|
-
<Button type="primary" icon={<ExportOutlined/>} onClick={() => setTriggerOpen(true)}>导出代码</Button>
|
|
181
|
-
<Button icon={<HistoryOutlined/>} onClick={() => message.info(`共 ${batches.length} 个历史批次`)}>历史</Button>
|
|
182
|
-
</Space>
|
|
183
|
-
|
|
184
|
-
<Table dataSource={batches} columns={batchColumns} rowKey="id" loading={loading}
|
|
185
|
-
pagination={{pageSize: 10}} size="small" scroll={{x: 1000}}/>
|
|
186
|
-
</Card>
|
|
187
|
-
|
|
188
|
-
<Modal title="触发代码物化" open={triggerOpen} onCancel={() => setTriggerOpen(false)} onOk={handleTrigger}
|
|
189
|
-
okText="开始导出" width={600}>
|
|
190
|
-
<Form form={form} layout="vertical" style={{marginTop: 16}}
|
|
191
|
-
initialValues={{materializationPath: `app-${appCode}/v1`}}>
|
|
192
|
-
<Form.Item name="materializationPath" label="导出根路径 (相对沙箱默认根)"
|
|
193
|
-
rules={[{required: true, message: '请输入路径'}]}
|
|
194
|
-
extra="默认根: ${user.home}/z-lc-materialized. 如需写入绝对路径, 服务端需配置 z-lc.materialize.allow-absolute-path=true">
|
|
195
|
-
<Input placeholder={`app-${appCode}/v1`}/>
|
|
196
|
-
</Form.Item>
|
|
197
|
-
<Form.Item name="entityCodes" label="导出的 entity (留空 = 全量)">
|
|
198
|
-
<Select mode="multiple" placeholder="选择 entity" allowClear
|
|
199
|
-
options={entities.map(e => ({value: e.entityCode, label: `${e.entityCode} (${e.tableName})`}))}/>
|
|
200
|
-
</Form.Item>
|
|
201
|
-
<Form.Item name="description" label="备注">
|
|
202
|
-
<TextArea rows={2} placeholder="本次导出的业务说明"/>
|
|
203
|
-
</Form.Item>
|
|
204
|
-
</Form>
|
|
205
|
-
</Modal>
|
|
206
|
-
|
|
207
|
-
<Modal title={`物化文件清单 (${diffFiles.length} 个)`} open={diffOpen}
|
|
208
|
-
onCancel={() => { setDiffOpen(false); setSelectedFile(null) }}
|
|
209
|
-
footer={<Button onClick={() => { setDiffOpen(false); setSelectedFile(null) }}>关闭</Button>}
|
|
210
|
-
width={selectedFile ? 1100 : 800}>
|
|
211
|
-
{selectedFile ? (
|
|
212
|
-
<div>
|
|
213
|
-
<Space style={{marginBottom: 8}}>
|
|
214
|
-
<Button onClick={() => setSelectedFile(null)}>← 返回清单</Button>
|
|
215
|
-
<Text strong>{selectedFile.relativePath}</Text>
|
|
216
|
-
<Tag color={TYPE_COLORS[selectedFile.type] || 'default'}>{selectedFile.type}</Tag>
|
|
217
|
-
<Button size="small" icon={<CopyOutlined/>} onClick={() => handleCopy(selectedFile.content)}>复制</Button>
|
|
218
|
-
<Button size="small" icon={<DownloadOutlined/>} onClick={() => handleDownload(selectedFile)}>下载</Button>
|
|
219
|
-
</Space>
|
|
220
|
-
<pre style={{
|
|
221
|
-
background: '#1e1e1e', color: '#d4d4d4', padding: 16,
|
|
222
|
-
borderRadius: 8, maxHeight: 500, overflow: 'auto',
|
|
223
|
-
fontSize: 12, fontFamily: 'Menlo, Monaco, monospace',
|
|
224
|
-
}}>
|
|
225
|
-
{selectedFile.content}
|
|
226
|
-
</pre>
|
|
227
|
-
</div>
|
|
228
|
-
) : (
|
|
229
|
-
<Table dataSource={diffFiles} columns={fileColumns} rowKey="relativePath"
|
|
230
|
-
pagination={{pageSize: 20}} size="small" scroll={{x: 700}}/>
|
|
231
|
-
)}
|
|
232
|
-
</Modal>
|
|
233
|
-
</div>
|
|
234
|
-
)
|
|
235
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {default} from './LowCodePage'
|