Optimize ToolBase management page

This commit is contained in:
2024-01-21 23:43:10 +08:00
parent 2521b0c951
commit 3a973a3c93
2 changed files with 209 additions and 24 deletions

View File

@@ -1,8 +1,15 @@
@use '@/assets/css/constants' as constants;
[data-component=system-tools-base] { [data-component=system-tools-base] {
.root-content { .root-content {
padding: 20px; padding: 20px;
gap: 10px; gap: 10px;
height: 100%; height: 100%;
width: 100%; width: 100%;
.has-edited::after {
content: '*';
color: constants.$font-secondary-color;
}
} }
} }

View File

@@ -5,7 +5,7 @@ import HideScrollbar from '@/components/common/HideScrollbar.tsx'
import Card from '@/components/common/Card.tsx' import Card from '@/components/common/Card.tsx'
import CodeEditor from '@/components/Playground/CodeEditor' import CodeEditor from '@/components/Playground/CodeEditor'
import { useState } from 'react' import { useState } from 'react'
import { IFile, IFiles } from '@/components/Playground/shared.ts' import { IFile, IFiles, ITsconfig } from '@/components/Playground/shared.ts'
import { import {
r_sys_tool_base_add, r_sys_tool_base_add,
r_sys_tool_base_delete, r_sys_tool_base_delete,
@@ -23,26 +23,39 @@ import {
} from '@/constants/common.constants.ts' } from '@/constants/common.constants.ts'
import { utcToLocalTime } from '@/util/datetime.tsx' import { utcToLocalTime } from '@/util/datetime.tsx'
import Permission from '@/components/common/Permission.tsx' import Permission from '@/components/common/Permission.tsx'
import { base64ToFiles, filesToBase64, getFilesSize } from '@/components/Playground/files.ts' import {
base64ToFiles,
fileNameToLanguage,
filesToBase64,
getFilesSize,
TS_CONFIG_FILE_NAME
} from '@/components/Playground/files.ts'
const Base = () => { const Base = () => {
const [modal, contextHolder] = AntdModal.useModal() const [modal, contextHolder] = AntdModal.useModal()
const [form] = AntdForm.useForm<ToolBaseAddEditParam>() const [form] = AntdForm.useForm<ToolBaseAddEditParam>()
const formValues = AntdForm.useWatch([], form) const formValues = AntdForm.useWatch([], form)
const [addFileForm] = AntdForm.useForm<{ fileName: string }>()
const [newFormValues, setNewFormValues] = useState<ToolBaseAddEditParam>() const [newFormValues, setNewFormValues] = useState<ToolBaseAddEditParam>()
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [isDrawerOpen, setIsDrawerOpen] = useState(false) const [isDrawerOpen, setIsDrawerOpen] = useState(false)
const [isDrawerEdit, setIsDrawerEdit] = useState(false) const [isDrawerEdit, setIsDrawerEdit] = useState(false)
const [submittable, setSubmittable] = useState(false) const [submittable, setSubmittable] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false)
const [editingFiles, setEditingFiles] = useState<IFiles>() const [editingBaseId, setEditingBaseId] = useState<string>('')
const [editingFiles, setEditingFiles] = useState<Record<string, IFiles>>({})
const [editingFileName, setEditingFileName] = useState('') const [editingFileName, setEditingFileName] = useState('')
const [hasEdited, setHasEdited] = useState(false) const [hasEdited, setHasEdited] = useState<Record<string, boolean>>({})
const [baseData, setBaseData] = useState<ToolBaseVo[]>([]) const [baseData, setBaseData] = useState<ToolBaseVo[]>([])
const [baseDetailData, setBaseDetailData] = useState<Record<string, ToolBaseVo>>({}) const [baseDetailData, setBaseDetailData] = useState<Record<string, ToolBaseVo>>({})
const [baseDetailLoading, setBaseDetailLoading] = useState<Record<string, boolean>>({}) const [baseDetailLoading, setBaseDetailLoading] = useState<Record<string, boolean>>({})
const [tsconfig, setTsconfig] = useState<ITsconfig>()
const handleOnAddBtnClick = () => { const handleOnAddBtnClick = () => {
if (Object.keys(hasEdited).length) {
void message.warning('新增前请保存修改')
return
}
setIsDrawerEdit(false) setIsDrawerEdit(false)
setIsDrawerOpen(true) setIsDrawerOpen(true)
form.setFieldValue('id', undefined) form.setFieldValue('id', undefined)
@@ -51,7 +64,14 @@ const Base = () => {
} }
const baseColumns: _ColumnsType<ToolBaseVo> = [ const baseColumns: _ColumnsType<ToolBaseVo> = [
{ title: '名称', dataIndex: 'name' }, {
title: '名称',
render: (_, record) => (
<span className={hasEdited[record.id] ? 'has-edited' : undefined}>
{record.name}
</span>
)
},
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'createTime', dataIndex: 'createTime',
@@ -90,6 +110,16 @@ const Base = () => {
render: (_, record) => ( render: (_, record) => (
<> <>
<AntdSpace size={'middle'}> <AntdSpace size={'middle'}>
{hasEdited[record.id] && (
<Permission operationCode={'system:tool:modify:base'}>
<a
style={{ color: COLOR_PRODUCTION }}
onClick={handleOnSaveBtnClick(record)}
>
</a>
</Permission>
)}
<Permission operationCode={'system:tool:modify:base'}> <Permission operationCode={'system:tool:modify:base'}>
<a <a
style={{ color: COLOR_PRODUCTION }} style={{ color: COLOR_PRODUCTION }}
@@ -112,8 +142,42 @@ const Base = () => {
} }
] ]
const handleOnSaveBtnClick = (value: ToolBaseVo) => {
return () => {
if (isLoading) {
return
}
setIsLoading(true)
const source = filesToBase64(editingFiles[value.id])
void r_sys_tool_base_update({ id: value.id, source })
.then((res) => {
const response = res.data
switch (response.code) {
case DATABASE_UPDATE_SUCCESS:
void message.success('保存成功')
delete hasEdited[value.id]
setHasEdited({ ...hasEdited })
getBaseDetail(value)
break
default:
void message.error('出错了,请稍后重试')
}
})
.finally(() => {
setIsLoading(false)
})
}
}
const handleOnEditBtnClick = (value: ToolBaseVo) => { const handleOnEditBtnClick = (value: ToolBaseVo) => {
return () => { return () => {
if (Object.keys(hasEdited).length) {
void message.warning('编辑前请保存修改')
return
}
setIsDrawerEdit(true) setIsDrawerEdit(true)
setIsDrawerOpen(true) setIsDrawerOpen(true)
form.setFieldValue('id', value.id) form.setFieldValue('id', value.id)
@@ -140,6 +204,10 @@ const Base = () => {
const response = res.data const response = res.data
if (response.code === DATABASE_DELETE_SUCCESS) { if (response.code === DATABASE_DELETE_SUCCESS) {
void message.success('删除成功') void message.success('删除成功')
setHasEdited({})
setEditingFileName('')
setEditingFiles({})
setEditingBaseId('')
setTimeout(() => { setTimeout(() => {
getBase() getBase()
}) })
@@ -233,14 +301,14 @@ const Base = () => {
const handleOnExpand = (expanded: boolean, record: ToolBaseVo) => { const handleOnExpand = (expanded: boolean, record: ToolBaseVo) => {
if (!expanded) { if (!expanded) {
setEditingFiles(undefined) setEditingFileName('')
return return
} }
getBaseDetail(record) getBaseDetail(record)
} }
const getBaseDetail = (record: ToolBaseVo) => { const getBaseDetail = (record: ToolBaseVo) => {
if (baseDetailLoading[record.id]) { if (baseDetailLoading[record.id] || hasEdited[record.id]) {
return return
} }
setBaseDetailLoading({ ...baseDetailLoading, [record.id]: true }) setBaseDetailLoading({ ...baseDetailLoading, [record.id]: true })
@@ -270,6 +338,95 @@ const Base = () => {
sourceFileList = Object.values(sourceFiles) sourceFileList = Object.values(sourceFiles)
} }
const handleOnAddFile = () => {
if (Object.keys(hasEdited).length) {
void message.warning('新建文件前请先保存更改')
return
}
void modal.confirm({
title: '新建文件',
content: (
<AntdForm form={addFileForm}>
<AntdForm.Item
name={'fileName'}
label={'文件名'}
style={{ marginTop: 10 }}
rules={[
{ required: true },
{
pattern: /\.(jsx|tsx|js|ts|css|json)$/,
message: '仅支持 *.jsx, *.tsx, *.js, *.ts, *.css, *.json 文件'
},
({ getFieldValue }) => ({
validator() {
const newFileName = getFieldValue('fileName') as string
if (Object.keys(sourceFiles!).includes(newFileName)) {
return Promise.reject(new Error('文件已存在'))
}
return Promise.resolve()
}
})
]}
>
<AntdInput />
</AntdForm.Item>
</AntdForm>
),
onOk: () =>
addFileForm.validateFields().then(
() => {
return new Promise((resolve) => {
const newFileName = addFileForm.getFieldValue('fileName') as string
setBaseDetailLoading({ ...baseDetailLoading, [record.id]: true })
sourceFiles = {
...sourceFiles,
[newFileName]: {
name: newFileName,
language: fileNameToLanguage(newFileName),
value: ''
}
}
void r_sys_tool_base_update({
id: record.id,
source: filesToBase64(sourceFiles)
})
.then((res) => {
addFileForm.setFieldValue('fileName', '')
const response = res.data
switch (response.code) {
case DATABASE_UPDATE_SUCCESS:
void message.success('添加成功')
setTimeout(() => {
getBaseDetail(record)
})
resolve(true)
break
default:
void message.error('添加失败,请稍后重试')
resolve(true)
}
})
.finally(() => {
setBaseDetailLoading({
...baseDetailLoading,
[record.id]: false
})
})
})
},
() => {
return new Promise((_, reject) => {
reject('请输入文件名')
})
}
)
})
}
const detailColumns: _ColumnsType<IFile> = [ const detailColumns: _ColumnsType<IFile> = [
{ title: '文件名', dataIndex: 'name' }, { title: '文件名', dataIndex: 'name' },
{ {
@@ -286,18 +443,11 @@ const Base = () => {
{ {
title: ( title: (
<> <>
(
<br /> <a style={{ color: COLOR_PRODUCTION }} onClick={handleOnAddFile}>
{baseDetailVo ? utcToLocalTime(baseDetailVo.source.updateTime) : 'Unknown'}
</> </a>
), )
width: '15em',
align: 'center'
},
{
title: (
<>
(<a style={{ color: COLOR_PRODUCTION }}></a>)
</> </>
), ),
dataIndex: 'enable', dataIndex: 'enable',
@@ -307,7 +457,10 @@ const Base = () => {
<> <>
<AntdSpace size={'middle'}> <AntdSpace size={'middle'}>
<Permission operationCode={'system:tool:modify:category'}> <Permission operationCode={'system:tool:modify:category'}>
<a onClick={handleOnEditFile()} style={{ color: COLOR_PRODUCTION }}> <a
onClick={handleOnEditFile(record.name)}
style={{ color: COLOR_PRODUCTION }}
>
</a> </a>
</Permission> </Permission>
@@ -325,8 +478,17 @@ const Base = () => {
} }
] ]
const handleOnEditFile = () => { const handleOnEditFile = (fileName: string) => {
return () => {} return () => {
if (editingBaseId !== record.id) {
setTsconfig(undefined)
}
if (!hasEdited[record.id]) {
setEditingFiles({ ...editingFiles, [record.id]: sourceFiles! })
}
setEditingBaseId(record.id)
setEditingFileName(fileName)
}
} }
const handleOnDeleteFile = (fileName: string) => { const handleOnDeleteFile = (fileName: string) => {
@@ -390,6 +552,20 @@ const Base = () => {
) )
} }
const handleOnChangeFileContent = (_content: string, _fileName: string, files: IFiles) => {
setEditingFiles({ ...editingFiles, [editingBaseId]: files })
setHasEdited({ ...hasEdited, [editingBaseId]: true })
}
useEffect(() => {
try {
const tsconfigStr = editingFiles[editingBaseId][TS_CONFIG_FILE_NAME].value
setTsconfig(JSON.parse(tsconfigStr) as ITsconfig)
} catch (e) {
/* empty */
}
}, [editingFiles, editingBaseId])
useEffect(() => { useEffect(() => {
form.validateFields({ validateOnly: true }).then( form.validateFields({ validateOnly: true }).then(
() => { () => {
@@ -465,13 +641,15 @@ const Base = () => {
/> />
</Card> </Card>
</HideScrollbar> </HideScrollbar>
{editingFiles && ( {editingFileName && (
<Card> <Card>
<CodeEditor <CodeEditor
files={editingFiles} files={editingFiles[editingBaseId]}
selectedFileName={editingFileName} selectedFileName={editingFileName}
onSelectedFileChange={() => {}} onSelectedFileChange={() => {}}
onChangeFileContent={handleOnChangeFileContent}
showFileSelector={false} showFileSelector={false}
tsconfig={tsconfig}
/> />
</Card> </Card>
)} )}