From 691f13381d637bb6a50e03b51ec96e86a889a17d Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Mon, 22 Jan 2024 16:54:42 +0800 Subject: [PATCH] Add rename file to ToolBase management page --- src/assets/css/pages/system/tools/base.scss | 28 ++- src/pages/System/Tools/Base.tsx | 230 ++++++++++++++++---- vite.config.ts | 4 +- 3 files changed, 222 insertions(+), 40 deletions(-) diff --git a/src/assets/css/pages/system/tools/base.scss b/src/assets/css/pages/system/tools/base.scss index b92b36d..2513936 100644 --- a/src/assets/css/pages/system/tools/base.scss +++ b/src/assets/css/pages/system/tools/base.scss @@ -2,7 +2,7 @@ [data-component=system-tools-base] { .root-content { - padding: 20px; + padding: 30px; gap: 10px; height: 100%; width: 100%; @@ -11,5 +11,31 @@ content: '*'; color: constants.$font-secondary-color; } + + >*:first-child { + height: fit-content; + } + + > *:nth-child(2) { + position: sticky; + top: 20px; + height: calc(100vh - 40px); + } + + .close-editor-btn { + display: flex; + justify-content: center; + align-items: center; + position: absolute; + top: 10px; + right: 10px; + background-color: constants.$font-secondary-color; + width: 32px; + height: 32px; + border-radius: 50%; + color: white; + box-shadow: 2px 2px 10px 0 rgba(0,0,0,0.2); + cursor: pointer; + } } } \ No newline at end of file diff --git a/src/pages/System/Tools/Base.tsx b/src/pages/System/Tools/Base.tsx index f4968a1..7734e0e 100644 --- a/src/pages/System/Tools/Base.tsx +++ b/src/pages/System/Tools/Base.tsx @@ -1,18 +1,5 @@ +import Icon from '@ant-design/icons' import '@/assets/css/pages/system/tools/base.scss' -import FitFullscreen from '@/components/common/FitFullscreen.tsx' -import FlexBox from '@/components/common/FlexBox.tsx' -import HideScrollbar from '@/components/common/HideScrollbar.tsx' -import Card from '@/components/common/Card.tsx' -import CodeEditor from '@/components/Playground/CodeEditor' -import { useState } from 'react' -import { IFile, IFiles, ITsconfig } from '@/components/Playground/shared.ts' -import { - r_sys_tool_base_add, - r_sys_tool_base_delete, - r_sys_tool_base_get_one, - r_sys_tool_base_getList, - r_sys_tool_base_update -} from '@/services/system.tsx' import { COLOR_PRODUCTION, DATABASE_DELETE_SUCCESS, @@ -20,22 +7,40 @@ import { DATABASE_INSERT_SUCCESS, DATABASE_SELECT_SUCCESS, DATABASE_UPDATE_SUCCESS -} from '@/constants/common.constants.ts' +} from '@/constants/common.constants' import { utcToLocalTime } from '@/util/datetime.tsx' -import Permission from '@/components/common/Permission.tsx' +import { + r_sys_tool_base_add, + r_sys_tool_base_delete, + r_sys_tool_base_get_one, + r_sys_tool_base_getList, + r_sys_tool_base_update +} from '@/services/system' +import { IFile, IFiles, ITsconfig } from '@/components/Playground/shared' import { base64ToFiles, fileNameToLanguage, filesToBase64, getFilesSize, TS_CONFIG_FILE_NAME -} from '@/components/Playground/files.ts' +} 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 CodeEditor from '@/components/Playground/CodeEditor' +import Permission from '@/components/common/Permission' const Base = () => { + const blocker = useBlocker( + ({ currentLocation, nextLocation }) => + currentLocation.pathname !== nextLocation.pathname && Object.keys(hasEdited).length > 0 + ) const [modal, contextHolder] = AntdModal.useModal() 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) @@ -51,6 +56,19 @@ const Base = () => { const [baseDetailLoading, setBaseDetailLoading] = useState>({}) const [tsconfig, setTsconfig] = useState() + useBeforeUnload( + useCallback( + (event) => { + if (Object.keys(hasEdited).length) { + event.preventDefault() + event.returnValue = '' + } + }, + [hasEdited] + ), + { capture: true } + ) + const handleOnAddBtnClick = () => { if (Object.keys(hasEdited).length) { void message.warning('新增前请保存修改') @@ -275,6 +293,10 @@ const Base = () => { } } + const handleOnCloseBtnClick = () => { + setEditingFileName('') + } + const handleOnDrawerClose = () => { setIsDrawerOpen(false) } @@ -301,7 +323,6 @@ const Base = () => { const handleOnExpand = (expanded: boolean, record: ToolBaseVo) => { if (!expanded) { - setEditingFileName('') return } getBaseDetail(record) @@ -361,7 +382,11 @@ const Base = () => { ({ getFieldValue }) => ({ validator() { const newFileName = getFieldValue('fileName') as string - if (Object.keys(sourceFiles!).includes(newFileName)) { + if ( + Object.keys(sourceFiles!) + .map((item) => item.toLowerCase()) + .includes(newFileName.toLowerCase()) + ) { return Promise.reject(new Error('文件已存在')) } return Promise.resolve() @@ -451,7 +476,7 @@ const Base = () => { ), dataIndex: 'enable', - width: '10em', + width: '12em', align: 'center', render: (_, record) => ( <> @@ -464,6 +489,14 @@ const Base = () => { 编辑 + + + 重命名 + + { } } + const handleOnRenameFile = (fileName: string) => { + return () => { + if (Object.keys(hasEdited).length) { + void message.warning('重命名文件前请先保存更改') + return + } + renameFileForm.setFieldValue('fileName', fileName) + void modal.confirm({ + title: '重命名文件', + content: ( + + ({ + 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_base_update({ + id: record.id, + source: filesToBase64(sourceFiles) + }) + .then((res) => { + const response = res.data + switch (response.code) { + case DATABASE_UPDATE_SUCCESS: + void message.success('重命名成功') + if ( + editingBaseId === record.id && + editingFileName === fileName + ) { + setEditingFileName('') + } + setTimeout(() => { + getBaseDetail(record) + }) + break + default: + void message.error('重命名失败,请稍后重试') + } + }) + .finally(() => { + setBaseDetailLoading({ + ...baseDetailLoading, + [record.id]: false + }) + }) + + resolve(true) + }) + }, + () => { + return new Promise((_, reject) => { + reject('请输入文件名') + }) + } + ) + }) + } + } + const handleOnDeleteFile = (fileName: string) => { return () => { - if (hasEdited) { + if (Object.keys(hasEdited).length) { void message.warning('删除文件前请先保存更改') return } @@ -519,6 +657,12 @@ const Base = () => { switch (response.code) { case DATABASE_UPDATE_SUCCESS: void message.success('删除成功') + if ( + editingBaseId === record.id && + editingFileName === fileName + ) { + setEditingFileName('') + } setTimeout(() => { getBaseDetail(record) }) @@ -547,6 +691,7 @@ const Base = () => { dataSource={sourceFileList} columns={detailColumns} pagination={false} + rowKey={(record) => record.name} /> ) @@ -625,8 +770,8 @@ const Base = () => { return ( <> - - + + { }} /> - - {editingFileName && ( - - {}} - onChangeFileContent={handleOnChangeFileContent} - showFileSelector={false} - tsconfig={tsconfig} - /> - - )} - + {editingFileName && ( + + {}} + onChangeFileContent={handleOnChangeFileContent} + showFileSelector={false} + tsconfig={tsconfig} + /> +
+ +
+
+ )} + + {
{contextHolder} + blocker.proceed?.()} + onCancel={() => blocker.reset?.()} + > + 离开此页面将丢失所有未保存数据,是否继续? + ) } diff --git a/vite.config.ts b/vite.config.ts index 5981025..9bde5ad 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -27,8 +27,8 @@ export default defineConfig({ 'react-router-dom', { react: ['Suspense', 'createContext'], - 'react-router': ['useMatches', 'RouterProvider'], - 'react-router-dom': ['createBrowserRouter'], + 'react-router': ['useMatches', 'RouterProvider', 'useBlocker'], + 'react-router-dom': ['createBrowserRouter', 'useBeforeUnload'], antd: ['message', 'notification'] }, {