Add create tool page
This commit is contained in:
@@ -48,6 +48,11 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script type="module" id="appSrc"></script>
|
<script type="module" id="appSrc"></script>
|
||||||
<div id="root"></div>
|
<div id="root">
|
||||||
|
<div
|
||||||
|
style="position:absolute;top: 0;left:0;width:100%;height:100%;display: flex;justify-content: center;align-items: center;">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -30,5 +30,9 @@ export const URL_SYS_TOOL_CATEGORY = `${URL_SYS_TOOL}/category`
|
|||||||
export const URL_SYS_TOOL_BASE = `${URL_SYS_TOOL}/base`
|
export const URL_SYS_TOOL_BASE = `${URL_SYS_TOOL}/base`
|
||||||
export const URL_SYS_TOOL_TEMPLATE = `${URL_SYS_TOOL}/template`
|
export const URL_SYS_TOOL_TEMPLATE = `${URL_SYS_TOOL}/template`
|
||||||
|
|
||||||
|
export const URL_TOOL = '/tool'
|
||||||
|
export const URL_TOOL_TEMPLATE = `${URL_TOOL}/template`
|
||||||
|
export const URL_TOOL_CATEGORY = `${URL_TOOL}/category`
|
||||||
|
|
||||||
export const URL_API_V1 = '/api/v1'
|
export const URL_API_V1 = '/api/v1'
|
||||||
export const URL_API_V1_AVATAR_RANDOM_BASE64 = `${URL_API_V1}/avatar/base64`
|
export const URL_API_V1_AVATAR_RANDOM_BASE64 = `${URL_API_V1}/avatar/base64`
|
||||||
|
|||||||
13
src/global.d.ts
vendored
13
src/global.d.ts
vendored
@@ -509,6 +509,7 @@ interface ToolTemplateVo {
|
|||||||
name: string
|
name: string
|
||||||
baseId: string
|
baseId: string
|
||||||
source: ToolDataVo
|
source: ToolDataVo
|
||||||
|
entryPoint: string
|
||||||
enable: boolean
|
enable: boolean
|
||||||
createTime: string
|
createTime: string
|
||||||
updateTime: string
|
updateTime: string
|
||||||
@@ -520,6 +521,7 @@ interface ToolTemplateAddEditParam {
|
|||||||
name?: string
|
name?: string
|
||||||
baseId?: string
|
baseId?: string
|
||||||
source?: string
|
source?: string
|
||||||
|
entryPoint?: string
|
||||||
enable?: boolean
|
enable?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,3 +543,14 @@ interface ToolVo {
|
|||||||
createTime: string
|
createTime: string
|
||||||
updateTime: string
|
updateTime: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ToolCreateParam {
|
||||||
|
name: string
|
||||||
|
toolId: string
|
||||||
|
description: string
|
||||||
|
ver: string
|
||||||
|
templateId: string
|
||||||
|
privately: boolean
|
||||||
|
keywords: string[]
|
||||||
|
categories: string[]
|
||||||
|
}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ const Template = () => {
|
|||||||
form.setFieldValue('id', undefined)
|
form.setFieldValue('id', undefined)
|
||||||
form.setFieldValue('name', newFormValues?.name)
|
form.setFieldValue('name', newFormValues?.name)
|
||||||
form.setFieldValue('baseId', newFormValues?.baseId)
|
form.setFieldValue('baseId', newFormValues?.baseId)
|
||||||
|
form.setFieldValue('entryPoint', newFormValues?.entryPoint)
|
||||||
form.setFieldValue('enable', newFormValues?.enable ?? true)
|
form.setFieldValue('enable', newFormValues?.enable ?? true)
|
||||||
if (!baseData || !baseData.length) {
|
if (!baseData || !baseData.length) {
|
||||||
getBaseData()
|
getBaseData()
|
||||||
@@ -98,6 +99,10 @@ const Template = () => {
|
|||||||
title: '基板',
|
title: '基板',
|
||||||
dataIndex: ['base', 'name']
|
dataIndex: ['base', 'name']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '入口',
|
||||||
|
dataIndex: 'entryPoint'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
dataIndex: 'createTime',
|
dataIndex: 'createTime',
|
||||||
@@ -211,6 +216,7 @@ const Template = () => {
|
|||||||
form.setFieldValue('id', value.id)
|
form.setFieldValue('id', value.id)
|
||||||
form.setFieldValue('name', value.name)
|
form.setFieldValue('name', value.name)
|
||||||
form.setFieldValue('baseId', value.base.id)
|
form.setFieldValue('baseId', value.base.id)
|
||||||
|
form.setFieldValue('entryPoint', value.entryPoint)
|
||||||
form.setFieldValue('enable', value.enable)
|
form.setFieldValue('enable', value.enable)
|
||||||
if (!baseData || !baseData.length) {
|
if (!baseData || !baseData.length) {
|
||||||
getBaseData()
|
getBaseData()
|
||||||
@@ -787,6 +793,7 @@ const Template = () => {
|
|||||||
setNewFormValues({
|
setNewFormValues({
|
||||||
name: formValues.name,
|
name: formValues.name,
|
||||||
baseId: formValues.baseId,
|
baseId: formValues.baseId,
|
||||||
|
entryPoint: formValues.entryPoint,
|
||||||
enable: formValues.enable
|
enable: formValues.enable
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -839,6 +846,9 @@ const Template = () => {
|
|||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
</AntdForm.Item>
|
</AntdForm.Item>
|
||||||
|
<AntdForm.Item name={'entryPoint'} label={'入口'} rules={[{ required: true }]}>
|
||||||
|
<AntdInput allowClear />
|
||||||
|
</AntdForm.Item>
|
||||||
<AntdForm.Item name={'enable'} label={'状态'}>
|
<AntdForm.Item name={'enable'} label={'状态'}>
|
||||||
<AntdSwitch checkedChildren={'启用'} unCheckedChildren={'禁用'} />
|
<AntdSwitch checkedChildren={'启用'} unCheckedChildren={'禁用'} />
|
||||||
</AntdForm.Item>
|
</AntdForm.Item>
|
||||||
@@ -857,6 +867,7 @@ const Template = () => {
|
|||||||
rowKey={(record) => record.id}
|
rowKey={(record) => record.id}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
|
scroll={{ x: true }}
|
||||||
expandable={{
|
expandable={{
|
||||||
expandedRowRender,
|
expandedRowRender,
|
||||||
onExpand: handleOnExpand
|
onExpand: handleOnExpand
|
||||||
|
|||||||
@@ -1,39 +1,151 @@
|
|||||||
import '@/assets/css/pages/tools/create.scss'
|
import '@/assets/css/pages/tools/create.scss'
|
||||||
import FlexBox from '@/components/common/FlexBox.tsx'
|
import {
|
||||||
import Card from '@/components/common/Card.tsx'
|
DATABASE_DUPLICATE_KEY,
|
||||||
import FitFullscreen from '@/components/common/FitFullscreen.tsx'
|
DATABASE_INSERT_SUCCESS,
|
||||||
import Preview from '@/components/Playground/Output/Preview'
|
DATABASE_SELECT_SUCCESS
|
||||||
import templates from '@/components/Playground/templates.ts'
|
} from '@/constants/common.constants'
|
||||||
import { useEffect } from 'react'
|
import {
|
||||||
import HideScrollbar from '@/components/common/HideScrollbar.tsx'
|
r_tool_category_get,
|
||||||
|
r_tool_create,
|
||||||
|
r_tool_template_get,
|
||||||
|
r_tool_template_get_one
|
||||||
|
} from '@/services/tool'
|
||||||
|
import { IImportMap } from '@/components/Playground/shared'
|
||||||
|
import { base64ToFiles, base64ToStr, IMPORT_MAP_FILE_NAME } from '@/components/Playground/files'
|
||||||
|
import compiler from '@/components/Playground/compiler'
|
||||||
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
|
import Card from '@/components/common/Card'
|
||||||
|
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||||
|
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||||
|
import Playground from '@/components/Playground'
|
||||||
|
|
||||||
const Create = () => {
|
const Create = () => {
|
||||||
const [form] = AntdForm.useForm<{
|
const [form] = AntdForm.useForm<ToolCreateParam>()
|
||||||
name: string
|
|
||||||
toolId: string
|
|
||||||
desc: string
|
|
||||||
version: string
|
|
||||||
template: string
|
|
||||||
private: boolean
|
|
||||||
keyword: string[]
|
|
||||||
category: string[]
|
|
||||||
}>()
|
|
||||||
const formValues = AntdForm.useWatch([], form)
|
const formValues = AntdForm.useWatch([], form)
|
||||||
const [template, setTemplate] = useState(templates['demo'])
|
const [templateData, setTemplateData] = useState<ToolTemplateVo[]>()
|
||||||
|
const [categoryData, setCategoryData] = useState<ToolCategoryVo[]>()
|
||||||
|
const [templateDetailData, setTemplateDetailData] = useState<Record<string, ToolTemplateVo>>({})
|
||||||
|
const [previewTemplate, setPreviewTemplate] = useState('')
|
||||||
|
const [loadingTemplate, setLoadingTemplate] = useState(false)
|
||||||
|
const [loadingCategory, setLoadingCategory] = useState(false)
|
||||||
|
const [creating, setCreating] = useState(false)
|
||||||
|
const [compiledCode, setCompiledCode] = useState('')
|
||||||
|
|
||||||
|
const handleOnFinish = (toolAddParam: ToolCreateParam) => {
|
||||||
|
setCreating(true)
|
||||||
|
|
||||||
|
void r_tool_create(toolAddParam)
|
||||||
|
.then((res) => {
|
||||||
|
const response = res.data
|
||||||
|
switch (response.code) {
|
||||||
|
case DATABASE_INSERT_SUCCESS:
|
||||||
|
void message.success(
|
||||||
|
`创建工具 ${response.data!.name}<${response.data!.toolId}>:${response.data!.ver} 成功`
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case DATABASE_DUPLICATE_KEY:
|
||||||
|
void message.warning('已存在相同 ID 相同版本的应用')
|
||||||
|
setCreating(false)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
void message.error('创建失败,请稍后重试')
|
||||||
|
setCreating(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setCreating(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnTemplateChange = (value: string) => {
|
||||||
|
setPreviewTemplate(value)
|
||||||
|
if (templateDetailData[value]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingTemplate(true)
|
||||||
|
void r_tool_template_get_one(value)
|
||||||
|
.then((res) => {
|
||||||
|
const response = res.data
|
||||||
|
switch (response.code) {
|
||||||
|
case DATABASE_SELECT_SUCCESS:
|
||||||
|
setTemplateDetailData({ ...templateDetailData, [value]: response.data! })
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
void message.error('获取模板信息失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoadingTemplate(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
formValues?.template && setTemplate(templates[formValues?.template])
|
const template = templateDetailData[previewTemplate]
|
||||||
}, [formValues?.template])
|
if (!template) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const baseDist = base64ToStr(template.base.dist.data!)
|
||||||
|
const files = base64ToFiles(template.source.data!)
|
||||||
|
const importMap = JSON.parse(files[IMPORT_MAP_FILE_NAME].value) as IImportMap
|
||||||
|
|
||||||
|
void compiler
|
||||||
|
.compile(files, importMap, template.entryPoint)
|
||||||
|
.then((result) => {
|
||||||
|
const output = result.outputFiles[0].text
|
||||||
|
setCompiledCode(`${output}\n${baseDist}`)
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
void message.error(`编译失败:${reason}`)
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
void message.error(`载入模板 ${templateDetailData[previewTemplate].name} 失败`)
|
||||||
|
}
|
||||||
|
}, [templateDetailData, previewTemplate])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const temp: string[] = []
|
const temp: string[] = []
|
||||||
formValues?.keyword.forEach((item) => {
|
formValues?.keywords.forEach((item) => {
|
||||||
if (item.length <= 10) {
|
if (item.length <= 10) {
|
||||||
temp.push(item)
|
temp.push(item)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
form.setFieldValue('keyword', temp)
|
form.setFieldValue('keyword', temp)
|
||||||
}, [form, formValues?.keyword])
|
}, [form, formValues?.keywords])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoadingTemplate(true)
|
||||||
|
setLoadingCategory(true)
|
||||||
|
void r_tool_template_get()
|
||||||
|
.then((res) => {
|
||||||
|
const response = res.data
|
||||||
|
switch (response.code) {
|
||||||
|
case DATABASE_SELECT_SUCCESS:
|
||||||
|
setTemplateData(response.data!)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
void message.error('获取模板列表失败,请稍后重试')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoadingTemplate(false)
|
||||||
|
})
|
||||||
|
void r_tool_category_get()
|
||||||
|
.then((res) => {
|
||||||
|
const response = res.data
|
||||||
|
switch (response.code) {
|
||||||
|
case DATABASE_SELECT_SUCCESS:
|
||||||
|
setCategoryData(response.data!)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
void message.error('获取类别列表失败,请稍后重试')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoadingCategory(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FitFullscreen data-component={'tools-create'}>
|
<FitFullscreen data-component={'tools-create'}>
|
||||||
@@ -45,7 +157,12 @@ const Create = () => {
|
|||||||
<Card className={'config'}>
|
<Card className={'config'}>
|
||||||
<HideScrollbar>
|
<HideScrollbar>
|
||||||
<div className={'config-content'}>
|
<div className={'config-content'}>
|
||||||
<AntdForm form={form} layout={'vertical'}>
|
<AntdForm
|
||||||
|
form={form}
|
||||||
|
layout={'vertical'}
|
||||||
|
onFinish={handleOnFinish}
|
||||||
|
disabled={creating}
|
||||||
|
>
|
||||||
<AntdForm.Item
|
<AntdForm.Item
|
||||||
label={'名称'}
|
label={'名称'}
|
||||||
name={'name'}
|
name={'name'}
|
||||||
@@ -75,7 +192,7 @@ const Create = () => {
|
|||||||
placeholder={'请输入工具 ID'}
|
placeholder={'请输入工具 ID'}
|
||||||
/>
|
/>
|
||||||
</AntdForm.Item>
|
</AntdForm.Item>
|
||||||
<AntdForm.Item label={'简介'} name={'desc'}>
|
<AntdForm.Item label={'简介'} name={'description'}>
|
||||||
<AntdInput.TextArea
|
<AntdInput.TextArea
|
||||||
autoSize={{ minRows: 6, maxRows: 6 }}
|
autoSize={{ minRows: 6, maxRows: 6 }}
|
||||||
maxLength={200}
|
maxLength={200}
|
||||||
@@ -85,7 +202,7 @@ const Create = () => {
|
|||||||
</AntdForm.Item>
|
</AntdForm.Item>
|
||||||
<AntdForm.Item
|
<AntdForm.Item
|
||||||
label={'版本'}
|
label={'版本'}
|
||||||
name={'version'}
|
name={'ver'}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true },
|
{ required: true },
|
||||||
{
|
{
|
||||||
@@ -102,21 +219,23 @@ const Create = () => {
|
|||||||
</AntdForm.Item>
|
</AntdForm.Item>
|
||||||
<AntdForm.Item
|
<AntdForm.Item
|
||||||
label={'模板'}
|
label={'模板'}
|
||||||
name={'template'}
|
name={'templateId'}
|
||||||
initialValue={'demo'}
|
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<AntdSelect>
|
<AntdSelect
|
||||||
{Object.keys(templates).map((item) => (
|
placeholder={'请选择模板'}
|
||||||
<AntdSelect.Option key={item}>
|
options={templateData?.map((value) => ({
|
||||||
{templates[item].name}
|
value: value.id,
|
||||||
</AntdSelect.Option>
|
label: value.name
|
||||||
))}
|
}))}
|
||||||
</AntdSelect>
|
loading={loadingTemplate}
|
||||||
|
disabled={loadingTemplate}
|
||||||
|
onChange={handleOnTemplateChange}
|
||||||
|
/>
|
||||||
</AntdForm.Item>
|
</AntdForm.Item>
|
||||||
<AntdForm.Item
|
<AntdForm.Item
|
||||||
label={'访问权限'}
|
label={'访问权限'}
|
||||||
name={'private'}
|
name={'privately'}
|
||||||
initialValue={false}
|
initialValue={false}
|
||||||
>
|
>
|
||||||
<AntdSwitch
|
<AntdSwitch
|
||||||
@@ -127,21 +246,39 @@ const Create = () => {
|
|||||||
<AntdForm.Item
|
<AntdForm.Item
|
||||||
label={'关键字'}
|
label={'关键字'}
|
||||||
tooltip={'工具搜索(每个不超过10个字符)'}
|
tooltip={'工具搜索(每个不超过10个字符)'}
|
||||||
name={'keyword'}
|
name={'keywords'}
|
||||||
rules={[{ required: true, message: '请输入关键字' }]}
|
rules={[{ required: true, message: '请输入关键字' }]}
|
||||||
>
|
>
|
||||||
<AntdSelect mode={'tags'} maxCount={20} />
|
<AntdSelect
|
||||||
|
placeholder={'请输入关键字'}
|
||||||
|
mode={'tags'}
|
||||||
|
maxCount={20}
|
||||||
|
/>
|
||||||
</AntdForm.Item>
|
</AntdForm.Item>
|
||||||
<AntdForm.Item
|
<AntdForm.Item
|
||||||
label={'类别'}
|
label={'类别'}
|
||||||
tooltip={'工具分类'}
|
tooltip={'工具分类'}
|
||||||
name={'category'}
|
name={'categories'}
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<AntdSelect mode={'multiple'} />
|
<AntdSelect
|
||||||
|
placeholder={'请选择类别'}
|
||||||
|
mode={'multiple'}
|
||||||
|
options={categoryData?.map((value) => ({
|
||||||
|
value: value.id,
|
||||||
|
label: value.name
|
||||||
|
}))}
|
||||||
|
loading={loadingCategory}
|
||||||
|
disabled={loadingCategory}
|
||||||
|
/>
|
||||||
</AntdForm.Item>
|
</AntdForm.Item>
|
||||||
<AntdForm.Item>
|
<AntdForm.Item>
|
||||||
<AntdButton className={'create-bt'} type={'primary'}>
|
<AntdButton
|
||||||
|
className={'create-bt'}
|
||||||
|
type={'primary'}
|
||||||
|
htmlType={'submit'}
|
||||||
|
loading={creating}
|
||||||
|
>
|
||||||
创建
|
创建
|
||||||
</AntdButton>
|
</AntdButton>
|
||||||
</AntdForm.Item>
|
</AntdForm.Item>
|
||||||
@@ -155,11 +292,12 @@ const Create = () => {
|
|||||||
<FlexBox>预览</FlexBox>
|
<FlexBox>预览</FlexBox>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className={'preview'}>
|
<Card className={'preview'}>
|
||||||
<Preview
|
{compiledCode && (
|
||||||
iframeKey={JSON.stringify(template.importMap)}
|
<Playground.Output.Preview.Render
|
||||||
files={template.files}
|
iframeKey={previewTemplate}
|
||||||
importMap={template.importMap}
|
compiledCode={compiledCode}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
|
|||||||
@@ -109,11 +109,18 @@ service.interceptors.response.use(
|
|||||||
return response
|
return response
|
||||||
},
|
},
|
||||||
async (error: AxiosError) => {
|
async (error: AxiosError) => {
|
||||||
|
if (
|
||||||
|
error.code === 'ETIMEDOUT' ||
|
||||||
|
(error.code === 'ECONNABORTED' && error.message.includes('timeout'))
|
||||||
|
) {
|
||||||
|
void message.error('请求超时,请稍后重试')
|
||||||
|
} else {
|
||||||
void message.error(
|
void message.error(
|
||||||
<>
|
<>
|
||||||
<strong>服务器出错</strong>,请稍后重试
|
<strong>服务器出错</strong>,请稍后重试
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
return await Promise.reject(error?.response?.data)
|
return await Promise.reject(error?.response?.data)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
11
src/services/tool.tsx
Normal file
11
src/services/tool.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import request from '@/services/index'
|
||||||
|
import { URL_TOOL, URL_TOOL_CATEGORY, URL_TOOL_TEMPLATE } from '@/constants/urls.constants'
|
||||||
|
|
||||||
|
export const r_tool_template_get = () => request.get<ToolTemplateVo[]>(URL_TOOL_TEMPLATE)
|
||||||
|
|
||||||
|
export const r_tool_template_get_one = (id: string) =>
|
||||||
|
request.get<ToolTemplateVo>(`${URL_TOOL_TEMPLATE}/${id}`)
|
||||||
|
|
||||||
|
export const r_tool_category_get = () => request.get<ToolCategoryVo[]>(URL_TOOL_CATEGORY)
|
||||||
|
|
||||||
|
export const r_tool_create = (param: ToolCreateParam) => request.post<ToolVo>(URL_TOOL, param)
|
||||||
Reference in New Issue
Block a user