Files
oxygen-ui/src/pages/System/Tools/Template.tsx

1093 lines
44 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Icon from '@ant-design/icons'
import '@/assets/css/pages/system/tools/template.scss'
import {
COLOR_PRODUCTION,
DATABASE_DELETE_SUCCESS,
DATABASE_DUPLICATE_KEY,
DATABASE_INSERT_SUCCESS,
DATABASE_SELECT_SUCCESS,
DATABASE_UPDATE_SUCCESS
} from '@/constants/common.constants'
import { utcToLocalTime } from '@/util/datetime'
import { hasPermission } from '@/util/auth'
import {
r_sys_tool_template_update,
r_sys_tool_template_delete,
r_sys_tool_template_add,
r_sys_tool_template_get,
r_sys_tool_template_get_one,
r_sys_tool_base_get_list
} from '@/services/system'
import { IFile, IFiles, ITsconfig } from '@/components/Playground/shared'
import {
base64ToFiles,
fileNameToLanguage,
filesToBase64,
getFilesSize,
TS_CONFIG_FILE_NAME
} from '@/components/Playground/files'
import FitFullscreen from '@/components/common/FitFullscreen'
import FlexBox from '@/components/common/FlexBox'
import HideScrollbar from '@/components/common/HideScrollbar'
import Card from '@/components/common/Card'
import Permission from '@/components/common/Permission'
import Playground from '@/components/Playground'
const Template = () => {
const blocker = useBlocker(
({ currentLocation, nextLocation }) =>
currentLocation.pathname !== nextLocation.pathname && Object.keys(hasEdited).length > 0
)
const [modal, contextHolder] = AntdModal.useModal()
const [tableParams, setTableParams] = useState<TableParam>({
pagination: {
current: 1,
pageSize: 20,
position: ['bottomCenter'],
showTotal: (total, range) =>
`${
range[0] === range[1] ? `${range[0]}` : `${range[0]}~${range[1]}`
} 项 共 ${total}`
}
})
const [form] = AntdForm.useForm<ToolTemplateAddEditParam>()
const formValues = AntdForm.useWatch([], form)
const [addFileForm] = AntdForm.useForm<{ fileName: string }>()
const [renameFileForm] = AntdForm.useForm<{ fileName: string }>()
const [newFormValues, setNewFormValues] = useState<ToolTemplateAddEditParam>()
const [isLoading, setIsLoading] = useState(false)
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
const [isDrawerEdit, setIsDrawerEdit] = useState(false)
const [baseData, setBaseData] = useState<ToolBaseVo[]>([])
const [isLoadingBaseData, setIsLoadingBaseData] = useState(false)
const [isSubmittable, setIsSubmittable] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const [editingTemplateId, setEditingTemplateId] = useState<string>('')
const [editingFiles, setEditingFiles] = useState<Record<string, IFiles>>({})
const [editingFileName, setEditingFileName] = useState('')
const [hasEdited, setHasEdited] = useState<Record<string, boolean>>({})
const [templateData, setTemplateData] = useState<ToolTemplateVo[]>([])
const [templateDetailData, setTemplateDetailData] = useState<Record<string, ToolTemplateVo>>({})
const [templateDetailLoading, setTemplateDetailLoading] = useState<Record<string, boolean>>({})
const [tsconfig, setTsconfig] = useState<ITsconfig>()
useBeforeUnload(
useCallback(
(event) => {
if (Object.keys(hasEdited).length) {
event.preventDefault()
event.returnValue = ''
}
},
[hasEdited]
),
{ capture: true }
)
const handleOnTableChange = (
pagination: _TablePaginationConfig,
filters: Record<string, _FilterValue | null>,
sorter: _SorterResult<ToolTemplateVo> | _SorterResult<ToolTemplateVo>[]
) => {
pagination = { ...tableParams.pagination, ...pagination }
if (Array.isArray(sorter)) {
setTableParams({
pagination,
filters,
sortField: sorter.map((value) => value.field).join(',')
})
} else {
setTableParams({
pagination,
filters,
sortField: sorter.field,
sortOrder: sorter.order
})
}
if (pagination.pageSize !== tableParams.pagination?.pageSize) {
setBaseData([])
}
}
const handleOnAddBtnClick = () => {
setIsDrawerEdit(false)
setIsDrawerOpen(true)
form.setFieldValue('id', undefined)
form.setFieldValue('name', newFormValues?.name)
form.setFieldValue('baseId', newFormValues?.baseId)
form.setFieldValue('entryPoint', newFormValues?.entryPoint)
form.setFieldValue('enable', newFormValues?.enable ?? true)
if (!baseData || !baseData.length) {
getBaseData()
}
}
const templateColumns: _ColumnsType<ToolTemplateVo> = [
{
title: '名称',
render: (_, record) => (
<span className={hasEdited[record.id] ? 'has-edited' : undefined}>
{record.name}
</span>
)
},
{
title: '平台',
dataIndex: 'platform',
render: (value: string) => `${value.slice(0, 1)}${value.slice(1).toLowerCase()}`,
filters: [
{ text: 'Web', value: 'WEB' },
{ text: 'Desktop', value: 'DESKTOP' },
{ text: 'Android', value: 'ANDROID' }
]
},
{
title: '基板',
dataIndex: ['base', 'name']
},
{
title: '入口',
dataIndex: 'entryPoint'
},
{
title: '创建时间',
dataIndex: 'createTime',
width: '7em',
align: 'center',
render: (value: string) => utcToLocalTime(value)
},
{
title: '修改时间',
dataIndex: 'updateTime',
width: '7em',
align: 'center',
render: (value: string) => utcToLocalTime(value)
},
{
title: '状态',
dataIndex: 'enable',
width: '5em',
align: 'center',
render: (value) =>
value ? <AntdTag color={'success'}></AntdTag> : <AntdTag></AntdTag>
},
{
title: (
<>
{!Object.keys(hasEdited).length && (
<Permission operationCode={['system:tool:add:template']}>
{' '}
(
<a style={{ color: COLOR_PRODUCTION }} onClick={handleOnAddBtnClick}>
</a>
)
</Permission>
)}
</>
),
width: '8em',
align: 'center',
render: (_, record) => (
<>
<AntdSpace size={'middle'}>
{hasEdited[record.id] && (
<Permission operationCode={['system:tool:modify:template']}>
<a
style={{ color: COLOR_PRODUCTION }}
onClick={handleOnSaveBtnClick(record)}
>
</a>
</Permission>
)}
{!Object.keys(hasEdited).length && (
<Permission operationCode={['system:tool:modify:template']}>
<a
style={{ color: COLOR_PRODUCTION }}
onClick={handleOnEditBtnClick(record)}
>
</a>
</Permission>
)}
<Permission operationCode={['system:tool:delete:template']}>
<a
style={{ color: COLOR_PRODUCTION }}
onClick={handleOnDeleteBtnClick(record)}
>
</a>
</Permission>
</AntdSpace>
</>
)
}
]
const handleOnSaveBtnClick = (value: ToolTemplateVo) => {
return () => {
if (isLoading) {
return
}
setIsLoading(true)
const source = filesToBase64(editingFiles[value.id])
void r_sys_tool_template_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 })
getTemplateDetail(value)
break
default:
void message.error('出错了,请稍后重试')
}
})
.finally(() => {
setIsLoading(false)
})
}
}
const handleOnEditBtnClick = (value: ToolTemplateVo) => {
return () => {
setIsDrawerEdit(true)
setIsDrawerOpen(true)
form.setFieldValue('id', value.id)
form.setFieldValue('name', value.name)
form.setFieldValue('baseId', value.base.id)
form.setFieldValue('entryPoint', value.entryPoint)
form.setFieldValue('enable', value.enable)
if (!baseData || !baseData.length) {
getBaseData()
}
void form.validateFields()
}
}
const handleOnDeleteBtnClick = (value: ToolTemplateVo) => {
return () => {
modal
.confirm({
centered: true,
maskClosable: true,
title: '确定删除',
content: `确定删除模板 ${value.name} 吗?`
})
.then(
(confirmed) => {
if (confirmed) {
setIsLoading(true)
void r_sys_tool_template_delete(value.id)
.then((res) => {
const response = res.data
if (response.code === DATABASE_DELETE_SUCCESS) {
void message.success('删除成功')
setHasEdited({})
setEditingFileName('')
setEditingFiles({})
setEditingTemplateId('')
setTimeout(() => {
getTemplate()
})
} else {
void message.error('删除失败,请稍后重试')
}
})
.finally(() => {
setIsLoading(false)
})
}
},
() => {}
)
}
}
const handleOnSubmit = () => {
if (isSubmitting) {
return
}
setIsSubmitting(true)
if (isDrawerEdit) {
void r_sys_tool_template_update(formValues)
.then((res) => {
const response = res.data
switch (response.code) {
case DATABASE_UPDATE_SUCCESS:
setIsDrawerOpen(false)
void message.success('更新成功')
getTemplate()
break
case DATABASE_DUPLICATE_KEY:
void message.error('已存在相同名称的模板')
break
default:
void message.error('更新失败,请稍后重试')
}
})
.finally(() => {
setIsSubmitting(false)
})
} else {
void r_sys_tool_template_add({
...formValues,
baseId: formValues.baseId
? (formValues.baseId as unknown as string[])[1]
: undefined
})
.then((res) => {
const response = res.data
switch (response.code) {
case DATABASE_INSERT_SUCCESS:
setIsDrawerOpen(false)
void message.success('添加成功')
setNewFormValues(undefined)
getTemplate()
break
case DATABASE_DUPLICATE_KEY:
void message.error('已存在相同名称的模板')
break
default:
void message.error('添加失败,请稍后重试')
}
})
.finally(() => {
setIsSubmitting(false)
})
}
}
const handleOnCloseBtnClick = () => {
setEditingFileName('')
}
const handleOnDrawerClose = () => {
setIsDrawerOpen(false)
}
const getTemplate = () => {
if (isLoading) {
return
}
setIsLoading(true)
void r_sys_tool_template_get({
currentPage: tableParams.pagination?.current,
pageSize: tableParams.pagination?.pageSize,
sortField:
tableParams.sortField && tableParams.sortOrder
? (tableParams.sortField as string)
: undefined,
sortOrder:
tableParams.sortField && tableParams.sortOrder ? tableParams.sortOrder : undefined,
...tableParams.filters
})
.then((res) => {
const response = res.data
if (response.code === DATABASE_SELECT_SUCCESS) {
setTemplateData(response.data!.records)
setTableParams({
...tableParams,
pagination: {
...tableParams.pagination,
total: response.data!.total
}
})
} else {
void message.error('获取失败,请稍后重试')
}
})
.finally(() => {
setIsLoading(false)
})
}
const handleOnExpand = (expanded: boolean, record: ToolTemplateVo) => {
if (!expanded) {
return
}
getTemplateDetail(record)
}
const getTemplateDetail = (record: ToolTemplateVo) => {
if (templateDetailLoading[record.id] || hasEdited[record.id]) {
return
}
setTemplateDetailLoading({ ...templateDetailLoading, [record.id]: true })
void r_sys_tool_template_get_one(record.id)
.then((res) => {
const response = res.data
switch (response.code) {
case DATABASE_SELECT_SUCCESS:
setTemplateDetailData({
...templateDetailData,
[record.id]: response.data!
})
setTemplateData(
templateData.map((value) =>
value.id === response.data!.id
? {
...response.data!,
source: { id: response.data!.source.id }
}
: value
)
)
break
default:
void message.error(`获取模板 ${record.name} 文件内容失败,请稍后重试`)
}
})
.finally(() => {
setTemplateDetailLoading({ ...templateDetailLoading, [record.id]: false })
})
}
const expandedRowRender = (record: ToolTemplateVo) => {
const templateDetailVo = templateDetailData[record.id]
let sourceFiles: IFiles | undefined = undefined
let sourceFileList: IFile[] = []
if (templateDetailVo) {
sourceFiles = base64ToFiles(templateDetailVo.source.data!)
sourceFileList = Object.values(sourceFiles)
}
const handleOnAddFile = () => {
void modal.confirm({
centered: true,
maskClosable: true,
title: '新建文件',
footer: (_, { OkBtn, CancelBtn }) => (
<>
<OkBtn />
<CancelBtn />
</>
),
content: (
<AntdForm
form={addFileForm}
ref={() => {
setTimeout(() => {
addFileForm?.getFieldInstance('fileName').focus()
}, 50)
}}
>
<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!)
.map((item) => item.toLowerCase())
.includes(newFileName.toLowerCase())
) {
return Promise.reject(new Error('文件已存在'))
}
return Promise.resolve()
}
})
]}
>
<AntdInput placeholder={'请输入文件名'} />
</AntdForm.Item>
</AntdForm>
),
onOk: () =>
addFileForm.validateFields().then(
() => {
return new Promise<void>((resolve) => {
const newFileName = addFileForm.getFieldValue('fileName') as string
setTemplateDetailLoading({
...templateDetailLoading,
[record.id]: true
})
sourceFiles = {
...sourceFiles,
[newFileName]: {
name: newFileName,
language: fileNameToLanguage(newFileName),
value: ''
}
}
void r_sys_tool_template_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(() => {
getTemplateDetail(record)
})
resolve()
break
default:
void message.error('添加失败,请稍后重试')
resolve()
}
})
.finally(() => {
setTemplateDetailLoading({
...templateDetailLoading,
[record.id]: false
})
})
})
},
() => {
return new Promise((_, reject) => {
reject('请输入文件名')
})
}
)
})
}
const detailColumns: _ColumnsType<IFile> = [
{ title: '文件名', dataIndex: 'name' },
{
title: (
<>
<br />
{sourceFiles ? getFilesSize(sourceFiles) : 'Unknown'}
</>
),
width: '10em',
align: 'center'
},
{
title: (
<>
{!Object.keys(hasEdited).length && (
<Permission operationCode={['system:tool:modify:template']}>
{' '}
(
<a style={{ color: COLOR_PRODUCTION }} onClick={handleOnAddFile}>
</a>
)
</Permission>
)}
</>
),
width: '12em',
align: 'center',
render: (_, record) => (
<>
<AntdSpace size={'middle'}>
<Permission
operationCode={[
'system:tool:query:template',
'system:tool:modify:template'
]}
>
<a
onClick={handleOnEditFile(record.name)}
style={{ color: COLOR_PRODUCTION }}
>
{hasPermission('system:tool:modify:template') ? '编辑' : '查看'}
</a>
</Permission>
{!Object.keys(hasEdited).length && (
<Permission operationCode={['system:tool:modify:template']}>
<a
onClick={handleOnRenameFile(record.name)}
style={{ color: COLOR_PRODUCTION }}
>
</a>
</Permission>
)}
{!Object.keys(hasEdited).length && (
<Permission operationCode={['system:tool:delete:template']}>
<a
onClick={handleOnDeleteFile(record.name)}
style={{ color: COLOR_PRODUCTION }}
>
</a>
</Permission>
)}
</AntdSpace>
</>
)
}
]
const handleOnEditFile = (fileName: string) => {
return () => {
if (editingTemplateId !== record.id) {
setTsconfig(undefined)
}
if (!hasEdited[record.id]) {
setEditingFiles({ ...editingFiles, [record.id]: sourceFiles! })
}
setEditingTemplateId(record.id)
setEditingFileName(fileName)
}
}
const handleOnRenameFile = (fileName: string) => {
return () => {
renameFileForm.setFieldValue('fileName', fileName)
void modal.confirm({
centered: true,
maskClosable: true,
title: '重命名文件',
footer: (_, { OkBtn, CancelBtn }) => (
<>
<OkBtn />
<CancelBtn />
</>
),
content: (
<AntdForm
form={renameFileForm}
ref={() => {
setTimeout(() => {
renameFileForm?.getFieldInstance('fileName').focus()
}, 50)
}}
>
<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!)
.map((item) => item.toLowerCase())
.includes(newFileName?.toLowerCase()) &&
newFileName.toLowerCase() !== fileName.toLowerCase()
) {
return Promise.reject(new Error('文件已存在'))
}
return Promise.resolve()
}
})
]}
>
<AntdInput placeholder={'请输入新文件名'} />
</AntdForm.Item>
</AntdForm>
),
onOk: () =>
renameFileForm.validateFields().then(
() => {
return new Promise<void>((resolve) => {
const newFileName = renameFileForm.getFieldValue(
'fileName'
) as string
const temp = sourceFiles![fileName].value
delete sourceFiles![fileName]
sourceFiles = {
...sourceFiles,
[newFileName]: {
name: newFileName,
language: fileNameToLanguage(newFileName),
value: temp
}
}
void r_sys_tool_template_update({
id: record.id,
source: filesToBase64(sourceFiles)
})
.then((res) => {
const response = res.data
switch (response.code) {
case DATABASE_UPDATE_SUCCESS:
void message.success('重命名成功')
if (
editingTemplateId === record.id &&
editingFileName === fileName
) {
setEditingFileName('')
}
setTimeout(() => {
getTemplateDetail(record)
})
break
default:
void message.error('重命名失败,请稍后重试')
}
})
.finally(() => {
setTemplateDetailLoading({
...templateDetailLoading,
[record.id]: false
})
})
resolve()
})
},
() => {
return new Promise((_, reject) => {
reject('请输入文件名')
})
}
)
})
}
}
const handleOnDeleteFile = (fileName: string) => {
return () => {
modal
.confirm({
centered: true,
maskClosable: true,
title: '确定删除',
content: `确定删除文件 ${fileName} 吗?`
})
.then(
(confirmed) => {
if (confirmed) {
setTemplateDetailLoading({
...templateDetailLoading,
[record.id]: true
})
delete sourceFiles![fileName]
void r_sys_tool_template_update({
id: record.id,
source: filesToBase64(sourceFiles!)
})
.then((res) => {
const response = res.data
switch (response.code) {
case DATABASE_UPDATE_SUCCESS:
void message.success('删除成功')
if (
editingTemplateId === record.id &&
editingFileName === fileName
) {
setEditingFileName('')
}
setTimeout(() => {
getTemplateDetail(record)
})
break
default:
void message.error('删除失败,请稍后重试')
}
})
.finally(() => {
setTemplateDetailLoading({
...templateDetailLoading,
[record.id]: false
})
})
}
},
() => {}
)
}
}
return (
<Card>
<AntdTable
loading={templateDetailLoading[record.id]}
dataSource={sourceFileList}
columns={detailColumns}
pagination={false}
rowKey={(record) => record.name}
/>
</Card>
)
}
const handleOnChangeFileContent = (_content: string, _fileName: string, files: IFiles) => {
if (!hasPermission('system:tool:modify:template')) {
return
}
setEditingFiles({ ...editingFiles, [editingTemplateId]: files })
if (!hasEdited[editingTemplateId]) {
setHasEdited({ ...hasEdited, [editingTemplateId]: true })
}
}
const getBaseData = () => {
if (isLoadingBaseData) {
return
}
setIsLoadingBaseData(true)
void r_sys_tool_base_get_list()
.then((res) => {
const response = res.data
switch (response.code) {
case DATABASE_SELECT_SUCCESS:
setBaseData(response.data!)
break
default:
void message.error('获取基板列表失败,请稍后重试')
}
})
.finally(() => {
setIsLoadingBaseData(false)
})
}
useEffect(() => {
try {
const tsconfigStr = editingFiles[editingTemplateId][TS_CONFIG_FILE_NAME].value
setTsconfig(JSON.parse(tsconfigStr) as ITsconfig)
} catch (e) {
/* empty */
}
}, [editingFiles, editingTemplateId])
useEffect(() => {
form.validateFields({ validateOnly: true }).then(
() => {
setIsSubmittable(true)
},
() => {
setIsSubmittable(false)
}
)
if (!isDrawerEdit && formValues) {
setNewFormValues({
name: formValues.name,
baseId: formValues.baseId,
entryPoint: formValues.entryPoint,
enable: formValues.enable
})
}
}, [formValues])
const baseDataGroupByPlatform = () => {
interface Node {
label: string
value: string
children?: Node[]
}
const temp: Node[] = []
baseData.forEach((value) => {
if (!temp.length) {
temp.push({
label: `${value.platform.slice(0, 1)}${value.platform.slice(1).toLowerCase()}`,
value: value.platform,
children: [
{
label: value.name,
value: value.id
}
]
})
} else {
if (
!temp.some((platform, platformIndex) => {
if (platform.value === value.platform) {
temp[platformIndex].children!.push({
label: value.name,
value: value.id
})
return true
}
return false
})
) {
temp.push({
label: `${value.platform.slice(0, 1)}${value.platform.slice(1).toLowerCase()}`,
value: value.platform,
children: [
{
label: value.name,
value: value.id
}
]
})
}
}
})
return temp
}
useEffect(() => {
getTemplate()
}, [
JSON.stringify(tableParams.filters),
JSON.stringify(tableParams.sortField),
JSON.stringify(tableParams.sortOrder),
JSON.stringify(tableParams.pagination?.pageSize),
JSON.stringify(tableParams.pagination?.current)
])
const drawerToolbar = (
<AntdSpace>
<AntdTooltip title={'刷新基板列表'}>
<AntdButton onClick={getBaseData} disabled={isSubmitting}>
<Icon component={IconOxygenRefresh} />
</AntdButton>
</AntdTooltip>
<AntdButton onClick={handleOnDrawerClose} disabled={isSubmitting}>
</AntdButton>
<AntdButton
type={'primary'}
disabled={!isSubmittable}
loading={isSubmitting}
onClick={handleOnSubmit}
>
</AntdButton>
</AntdSpace>
)
const addAndEditForm = (
<AntdForm form={form} disabled={isSubmitting} layout={'vertical'}>
<AntdForm.Item hidden name={'id'} label={'ID'}>
<AntdInput disabled />
</AntdForm.Item>
<AntdForm.Item
name={'name'}
label={'名称'}
rules={[{ required: true, whitespace: true }]}
>
<AntdInput allowClear placeholder={'请输入名称'} />
</AntdForm.Item>
<AntdForm.Item
hidden={isDrawerEdit}
name={'baseId'}
label={'基板'}
rules={[{ required: true }]}
>
<AntdCascader
showSearch
allowClear={false}
options={baseDataGroupByPlatform()}
placeholder={'请选择基板'}
/>
</AntdForm.Item>
<AntdForm.Item
name={'entryPoint'}
label={'入口文件'}
rules={[{ required: true, whitespace: true }]}
>
<AntdInput allowClear placeholder={'请输入入口文件'} />
</AntdForm.Item>
<AntdForm.Item name={'enable'} label={'状态'}>
<AntdSwitch checkedChildren={'启用'} unCheckedChildren={'禁用'} />
</AntdForm.Item>
</AntdForm>
)
return (
<>
<FitFullscreen data-component={'system-tools-template'}>
<HideScrollbar>
<FlexBox direction={'horizontal'} className={'root-content'}>
<Card>
<AntdTable
dataSource={templateData}
columns={templateColumns}
rowKey={(record) => record.id}
pagination={tableParams.pagination}
loading={isLoading}
scroll={{ x: true }}
expandable={{
expandedRowRender,
onExpand: handleOnExpand
}}
onChange={handleOnTableChange}
/>
</Card>
{editingFileName && (
<Card>
<Playground.CodeEditor
files={editingFiles[editingTemplateId]}
selectedFileName={editingFileName}
onSelectedFileChange={setEditingFileName}
onChangeFileContent={handleOnChangeFileContent}
showFileSelector={false}
tsconfig={tsconfig}
readonly={
isLoading ||
templateDetailLoading[editingTemplateId] ||
!hasPermission('system:tool:modify:template')
}
/>
<div className={'close-editor-btn'} onClick={handleOnCloseBtnClick}>
<Icon component={IconOxygenClose} />
</div>
</Card>
)}
</FlexBox>
</HideScrollbar>
<AntdDrawer
title={isDrawerEdit ? '编辑模板' : '添加模板'}
onClose={handleOnDrawerClose}
open={isDrawerOpen}
closable={!isSubmitting}
maskClosable={!isSubmitting}
extra={drawerToolbar}
>
{addAndEditForm}
</AntdDrawer>
</FitFullscreen>
{contextHolder}
<AntdModal
open={blocker.state === 'blocked'}
title={'未保存'}
onOk={() => blocker.proceed?.()}
onCancel={() => blocker.reset?.()}
footer={(_, { OkBtn, CancelBtn }) => (
<>
<OkBtn />
<CancelBtn />
</>
)}
>
</AntdModal>
</>
)
}
export default Template