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({ 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() const formValues = AntdForm.useWatch([], form) const [addFileForm] = AntdForm.useForm<{ fileName: string }>() const [renameFileForm] = AntdForm.useForm<{ fileName: string }>() const [newFormValues, setNewFormValues] = useState() const [isLoading, setIsLoading] = useState(false) const [isDrawerOpen, setIsDrawerOpen] = useState(false) const [isDrawerEdit, setIsDrawerEdit] = useState(false) const [baseData, setBaseData] = useState([]) const [isLoadingBaseData, setIsLoadingBaseData] = useState(false) const [isSubmittable, setIsSubmittable] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) const [editingTemplateId, setEditingTemplateId] = useState('') const [editingFiles, setEditingFiles] = useState>({}) const [editingFileName, setEditingFileName] = useState('') const [hasEdited, setHasEdited] = useState>({}) const [templateData, setTemplateData] = useState([]) const [templateDetailData, setTemplateDetailData] = useState>({}) const [templateDetailLoading, setTemplateDetailLoading] = useState>({}) const [tsconfig, setTsconfig] = useState() useBeforeUnload( useCallback( (event) => { if (Object.keys(hasEdited).length) { event.preventDefault() event.returnValue = '' } }, [hasEdited] ), { capture: true } ) const handleOnTableChange = ( pagination: _TablePaginationConfig, filters: Record, sorter: _SorterResult | _SorterResult[] ) => { 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 = [ { title: '名称', render: (_, record) => ( {record.name} ) }, { 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 ? 启用 : 禁用 }, { title: ( <> 操作 {!Object.keys(hasEdited).length && ( {' '} ( 新增 ) )} ), width: '8em', align: 'center', render: (_, record) => ( <> {hasEdited[record.id] && ( 保存 )} {!Object.keys(hasEdited).length && ( 编辑 )} 删除 ) } ] 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 }) => ( <> ), content: ( { setTimeout(() => { addFileForm?.getFieldInstance('fileName').focus() }, 50) }} > ({ 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() } }) ]} > ), onOk: () => addFileForm.validateFields().then( () => { return new Promise((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 = [ { title: '文件名', dataIndex: 'name' }, { title: ( <> 文件总大小
{sourceFiles ? getFilesSize(sourceFiles) : 'Unknown'} ), width: '10em', align: 'center' }, { title: ( <> 操作 {!Object.keys(hasEdited).length && ( {' '} ( 新增 ) )} ), width: '12em', align: 'center', render: (_, record) => ( <> {hasPermission('system:tool:modify:template') ? '编辑' : '查看'} {!Object.keys(hasEdited).length && ( 重命名 )} {!Object.keys(hasEdited).length && ( 删除 )} ) } ] 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 }) => ( <> ), content: ( { setTimeout(() => { renameFileForm?.getFieldInstance('fileName').focus() }, 50) }} > ({ 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() } }) ]} > ), onOk: () => renameFileForm.validateFields().then( () => { return new Promise((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 ( record.name} /> ) } 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 = ( 取消 提交 ) const addAndEditForm = ( ) return ( <> record.id} pagination={tableParams.pagination} loading={isLoading} scroll={{ x: true }} expandable={{ expandedRowRender, onExpand: handleOnExpand }} onChange={handleOnTableChange} /> {editingFileName && (
)}
{addAndEditForm}
{contextHolder} blocker.proceed?.()} onCancel={() => blocker.reset?.()} footer={(_, { OkBtn, CancelBtn }) => ( <> )} > 离开此页面将丢失所有未保存数据,是否继续? ) } export default Template