Complete main UI #37

Merged
FatttSnake merged 192 commits from FatttSnake into dev 2024-02-23 16:31:17 +08:00
7 changed files with 240 additions and 51 deletions
Showing only changes of commit 5fb4cfe55d - Show all commits

View File

@@ -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>

View File

@@ -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
View File

@@ -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[]
}

View File

@@ -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

View File

@@ -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>

View File

@@ -109,11 +109,18 @@ service.interceptors.response.use(
return response return response
}, },
async (error: AxiosError) => { async (error: AxiosError) => {
void message.error( if (
<> error.code === 'ETIMEDOUT' ||
<strong></strong> (error.code === 'ECONNABORTED' && error.message.includes('timeout'))
</> ) {
) void message.error('请求超时,请稍后重试')
} else {
void message.error(
<>
<strong></strong>
</>
)
}
return await Promise.reject(error?.response?.data) return await Promise.reject(error?.response?.data)
} }
) )

11
src/services/tool.tsx Normal file
View 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)