diff --git a/back/api/script.ts b/back/api/script.ts index 1b0a09c4..43f0d90f 100644 --- a/back/api/script.ts +++ b/back/api/script.ts @@ -70,7 +70,10 @@ export default (app: Router) => { path += '/'; } if (config.writePathList.every((x) => !path.startsWith(x))) { - return res.send({ code: 400, data: '文件路径错误,可保存目录/ql/scripts、/ql/config、/ql/jbot、/ql/bak' }); + return res.send({ + code: 400, + data: '文件路径错误,可保存目录/ql/scripts、/ql/config、/ql/jbot、/ql/bak', + }); } const filePath = `${path}${filename.replace(/\//g, '')}`; const bakPath = '/ql/bak'; @@ -88,4 +91,52 @@ export default (app: Router) => { } }, ); + + route.put( + '/scripts', + celebrate({ + body: Joi.object({ + filename: Joi.string().required(), + content: Joi.string().required(), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + let { filename, content } = req.body as { + filename: string; + content: string; + }; + const filePath = `${config.scriptPath}${filename}`; + fs.writeFileSync(filePath, content); + return res.send({ code: 200 }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); + + route.delete( + '/scripts', + celebrate({ + body: Joi.object({ + filename: Joi.string().required(), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + let { filename } = req.body as { + filename: string; + }; + const filePath = `${config.scriptPath}${filename}`; + fs.unlinkSync(filePath); + res.send({ code: 200 }); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); }; diff --git a/src/pages/script/index.tsx b/src/pages/script/index.tsx index 6046e61c..f87f6155 100644 --- a/src/pages/script/index.tsx +++ b/src/pages/script/index.tsx @@ -1,5 +1,13 @@ import { useState, useEffect, useCallback, Key, useRef } from 'react'; -import { TreeSelect, Tree, Input, Button } from 'antd'; +import { + TreeSelect, + Tree, + Input, + Button, + Modal, + message, + Typography, +} from 'antd'; import config from '@/utils/config'; import { PageContainer } from '@ant-design/pro-layout'; import Editor from '@monaco-editor/react'; @@ -10,6 +18,8 @@ import { Controlled as CodeMirror } from 'react-codemirror2'; import { useCtx, useTheme } from '@/utils/hooks'; import SplitPane from 'react-split-pane'; +const { Text } = Typography; + function getFilterData(keyword: string, data: any) { if (keyword) { const tree: any = []; @@ -33,7 +43,7 @@ const LangMap: any = { const Script = () => { const [title, setTitle] = useState('请选择脚本文件'); const [value, setValue] = useState('请选择脚本文件'); - const [select, setSelect] = useState(); + const [select, setSelect] = useState(); const [data, setData] = useState([]); const [filterData, setFilterData] = useState([]); const [loading, setLoading] = useState(false); @@ -43,6 +53,9 @@ const Script = () => { const [isLogModalVisible, setIsLogModalVisible] = useState(false); const { headerStyle, isPhone } = useCtx(); const { theme } = useTheme(); + const [searchValue, setSearchValue] = useState(''); + const [isEditing, setIsEditing] = useState(false); + const editorRef = useRef(null); const getScripts = () => { setLoading(true); @@ -76,12 +89,102 @@ const Script = () => { const onSearch = useCallback( (e) => { const keyword = e.target.value; + setSearchValue(keyword); const { tree } = getFilterData(keyword.toLocaleLowerCase(), data); setFilterData(tree); }, [data, setFilterData], ); + const editFile = () => { + setIsEditing(true); + }; + + const saveFile = () => { + Modal.confirm({ + title: `确认保存`, + content: ( + <> + 确认保存文件 + + {select} + {' '} + ,保存后不可恢复 + + ), + onOk() { + const content = editorRef.current + ? editorRef.current.getValue().replace(/\r\n/g, '\n') + : value; + request + .put(`${config.apiPrefix}scripts`, { + data: { + filename: select, + content, + }, + }) + .then((_data: any) => { + if (_data.code === 200) { + message.success(`保存成功`); + } else { + message.error(_data); + } + }); + }, + onCancel() { + console.log('Cancel'); + }, + }); + }; + + const deleteFile = () => { + Modal.confirm({ + title: `确认删除`, + content: ( + <> + 确认删除文件 + + {select} + {' '} + ,删除后不可恢复 + + ), + onOk() { + request + .delete(`${config.apiPrefix}scripts`, { + data: { + filename: select, + }, + }) + .then((_data: any) => { + if (_data.code === 200) { + message.success(`删除成功`); + let newData = [...data]; + const index = newData.findIndex((x) => x.value === select); + newData.splice(index, 1); + setData(newData); + } else { + message.error(_data); + } + }); + }, + onCancel() { + console.log('Cancel'); + }, + }); + }; + + useEffect(() => { + const word = searchValue || ''; + const { tree } = getFilterData(word.toLocaleLowerCase(), data); + console.log(word); + console.log(tree); + setFilterData(tree); + setSelect(''); + setTitle('请选择脚本文件'); + setValue('请选择脚本文件'); + }, [data]); + useEffect(() => { getScripts(); setHeight(treeDom.current.clientHeight); @@ -105,8 +208,23 @@ const Script = () => { key="value" onSelect={onSelect} />, + , + ] + : isEditing + ? [ + , ] : [ + , + ,