mirror of
https://github.com/whyour/qinglong.git
synced 2026-06-30 20:35:09 +08:00
手机端使用codemirror
This commit is contained in:
+68
-14
@@ -1,4 +1,5 @@
|
||||
@import '~@/styles/variable.less';
|
||||
@import '~codemirror/lib/codemirror.css';
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
@@ -13,7 +14,7 @@ body {
|
||||
background-color: rgb(248, 248, 248);
|
||||
}
|
||||
|
||||
.ant-modal {
|
||||
.log-modal .ant-modal {
|
||||
padding-bottom: 0 !important;
|
||||
width: 580px !important;
|
||||
}
|
||||
@@ -21,22 +22,14 @@ body {
|
||||
.monaco-editor:not(.rename-box) {
|
||||
height: calc(100vh - 128px) !important;
|
||||
height: calc(100vh - var(--vh-offset, 0px) - 128px) !important;
|
||||
.view-overlays .current-line{
|
||||
.view-overlays .current-line {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.log-modal {
|
||||
.monaco-editor:not(.rename-box) {
|
||||
height: calc(100vh - 176px) !important;
|
||||
height: calc(100vh - var(--vh-offset, 0px) - 176px) !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
.rename-box {
|
||||
.rename-box {
|
||||
height: 0;
|
||||
.rename-input{
|
||||
.rename-input {
|
||||
height: 0;
|
||||
padding: 0 !important;
|
||||
}
|
||||
@@ -144,12 +137,17 @@ input:-webkit-autofill:active {
|
||||
height: calc(100vh - 184px);
|
||||
height: calc(100vh - var(--vh-offset, 0px) - 184px);
|
||||
}
|
||||
.monaco-editor:not(.rename-box) {
|
||||
.monaco-editor:not(.rename-box),
|
||||
.CodeMirror {
|
||||
height: calc(100vh - 216px) !important;
|
||||
height: calc(100vh - var(--vh-offset, 0px) - 216px) !important;
|
||||
}
|
||||
.CodeMirror {
|
||||
width: calc(100vw - 80px);
|
||||
}
|
||||
}
|
||||
.monaco-editor:not(.rename-box) {
|
||||
.monaco-editor:not(.rename-box),
|
||||
.CodeMirror {
|
||||
height: calc(100vh - 176px) !important;
|
||||
height: calc(100vh - var(--vh-offset, 0px) - 176px) !important;
|
||||
}
|
||||
@@ -166,3 +164,59 @@ input:-webkit-autofill:active {
|
||||
min-height: calc(100vh - var(--vh-offset, 0px) - 72px);
|
||||
}
|
||||
}
|
||||
|
||||
.Resizer {
|
||||
background: #000;
|
||||
opacity: 0.2;
|
||||
z-index: 1;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-background-clip: padding;
|
||||
-webkit-background-clip: padding;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.Resizer:hover {
|
||||
-webkit-transition: all 2s ease;
|
||||
transition: all 2s ease;
|
||||
}
|
||||
|
||||
.Resizer.horizontal {
|
||||
height: 11px;
|
||||
margin: -5px 0;
|
||||
border-top: 5px solid rgba(255, 255, 255, 0);
|
||||
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: row-resize;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.Resizer.horizontal:hover {
|
||||
border-top: 5px solid rgba(0, 0, 0, 0.5);
|
||||
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.Resizer.vertical {
|
||||
width: 11px;
|
||||
margin: 0 -5px;
|
||||
border-left: 5px solid rgba(255, 255, 255, 0);
|
||||
border-right: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.Resizer.vertical:hover {
|
||||
border-left: 5px solid rgba(0, 0, 0, 0.5);
|
||||
border-right: 5px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.Resizer.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.Resizer.disabled:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.edit-modal {
|
||||
.ant-drawer-body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ export default function (props: any) {
|
||||
const isSafari =
|
||||
navigator.userAgent.includes('Safari') &&
|
||||
!navigator.userAgent.includes('Chrome');
|
||||
const isQQBrowser = navigator.userAgent.includes('QQBrowser');
|
||||
|
||||
return (
|
||||
<ProLayout
|
||||
selectedKeys={[props.location.pathname]}
|
||||
@@ -76,8 +78,9 @@ export default function (props: any) {
|
||||
style={{
|
||||
fontSize: isFirefox ? 9 : 12,
|
||||
color: '#666',
|
||||
marginLeft: 5,
|
||||
marginLeft: 2,
|
||||
zoom: isSafari ? 0.66 : 0.8,
|
||||
letterSpacing: isQQBrowser ? -2 : 0,
|
||||
}}
|
||||
>
|
||||
{version}
|
||||
|
||||
+36
-16
@@ -4,6 +4,7 @@ import config from '@/utils/config';
|
||||
import { PageContainer } from '@ant-design/pro-layout';
|
||||
import { request } from '@/utils/http';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
|
||||
const Config = () => {
|
||||
const [width, setWidth] = useState('100%');
|
||||
@@ -15,6 +16,7 @@ const Config = () => {
|
||||
const [select, setSelect] = useState('config.sh');
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [theme, setTheme] = useState<string>('');
|
||||
const [isPhone, setIsPhone] = useState(false);
|
||||
|
||||
const getConfig = (name: string) => {
|
||||
request.get(`${config.apiPrefix}configs/${name}`).then((data: any) => {
|
||||
@@ -38,7 +40,7 @@ const Config = () => {
|
||||
data: { content: value, name: select },
|
||||
})
|
||||
.then((data: any) => {
|
||||
message.success(data.msg);
|
||||
message.success(data.message);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -53,10 +55,12 @@ const Config = () => {
|
||||
setWidth('auto');
|
||||
setMarginLeft(0);
|
||||
setMarginTop(0);
|
||||
setIsPhone(true);
|
||||
} else {
|
||||
setWidth('100%');
|
||||
setMarginLeft(0);
|
||||
setMarginTop(-72);
|
||||
setIsPhone(false);
|
||||
}
|
||||
getFiles();
|
||||
getConfig('config.sh');
|
||||
@@ -111,21 +115,37 @@ const Config = () => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Editor
|
||||
defaultLanguage="shell"
|
||||
value={value}
|
||||
theme={theme}
|
||||
options={{
|
||||
fontSize: 12,
|
||||
minimap: { enabled: width === '100%' },
|
||||
lineNumbersMinChars: 3,
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
}}
|
||||
onChange={(val) => {
|
||||
setValue((val as string).replace(/\r\n/g, '\n'));
|
||||
}}
|
||||
/>
|
||||
{isPhone ? (
|
||||
<CodeMirror
|
||||
value={value}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
styleActiveLine: true,
|
||||
matchBrackets: true,
|
||||
mode: 'shell',
|
||||
}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
setValue(value);
|
||||
}}
|
||||
onChange={(editor, data, value) => {}}
|
||||
/>
|
||||
) : (
|
||||
<Editor
|
||||
defaultLanguage="shell"
|
||||
value={value}
|
||||
theme={theme}
|
||||
options={{
|
||||
fontSize: 12,
|
||||
minimap: { enabled: width === '100%' },
|
||||
lineNumbersMinChars: 3,
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
}}
|
||||
onChange={(val) => {
|
||||
setValue((val as string).replace(/\r\n/g, '\n'));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -96,6 +96,7 @@ const CronLogModal = ({
|
||||
title={titleElement()}
|
||||
visible={visible}
|
||||
centered
|
||||
className="log-modal"
|
||||
bodyStyle={{
|
||||
overflowY: 'auto',
|
||||
maxHeight: 'calc(80vh - var(--vh-offset, 0px))',
|
||||
|
||||
+60
-28
@@ -3,9 +3,9 @@ import { Button, message, Modal } from 'antd';
|
||||
import config from '@/utils/config';
|
||||
import { PageContainer } from '@ant-design/pro-layout';
|
||||
import { request } from '@/utils/http';
|
||||
import ReactDiffViewer from 'react-diff-viewer';
|
||||
import './index.less';
|
||||
import { DiffEditor } from "@monaco-editor/react";
|
||||
import { DiffEditor } from '@monaco-editor/react';
|
||||
import ReactDiffViewer from 'react-diff-viewer';
|
||||
|
||||
const Crontab = () => {
|
||||
const [width, setWidth] = useState('100%');
|
||||
@@ -15,6 +15,7 @@ const Crontab = () => {
|
||||
const [sample, setSample] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [theme, setTheme] = useState<string>('');
|
||||
const [isPhone, setIsPhone] = useState(false);
|
||||
|
||||
const getConfig = () => {
|
||||
request.get(`${config.apiPrefix}configs/config.sh`).then((data) => {
|
||||
@@ -37,30 +38,33 @@ const Crontab = () => {
|
||||
setWidth('auto');
|
||||
setMarginLeft(0);
|
||||
setMarginTop(0);
|
||||
setIsPhone(true);
|
||||
} else {
|
||||
setWidth('100%');
|
||||
setMarginLeft(0);
|
||||
setMarginTop(-72);
|
||||
setIsPhone(false);
|
||||
}
|
||||
getConfig();
|
||||
getSample();
|
||||
}, []);
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const storageTheme = localStorage.getItem('qinglong_dark_theme');
|
||||
const isDark = (media.matches && storageTheme !== 'light') || storageTheme === 'dark';
|
||||
setTheme(isDark?'vs-dark':'vs');
|
||||
media.addEventListener('change',(e)=>{
|
||||
if(storageTheme === 'auto' || !storageTheme){
|
||||
if(e.matches){
|
||||
setTheme('vs-dark')
|
||||
}else{
|
||||
const isDark =
|
||||
(media.matches && storageTheme !== 'light') || storageTheme === 'dark';
|
||||
setTheme(isDark ? 'vs-dark' : 'vs');
|
||||
media.addEventListener('change', (e) => {
|
||||
if (storageTheme === 'auto' || !storageTheme) {
|
||||
if (e.matches) {
|
||||
setTheme('vs-dark');
|
||||
} else {
|
||||
setTheme('vs');
|
||||
}
|
||||
}
|
||||
})
|
||||
},[])
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
@@ -80,22 +84,50 @@ const Crontab = () => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DiffEditor
|
||||
language={"shell"}
|
||||
original={sample}
|
||||
modified={value}
|
||||
options={{
|
||||
readOnly: true,
|
||||
fontSize: 12,
|
||||
minimap: {enabled: width==='100%'},
|
||||
lineNumbersMinChars: 3,
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
renderSideBySide: width==='100%',
|
||||
wordWrap: 'on'
|
||||
}}
|
||||
theme={theme}
|
||||
/>
|
||||
{isPhone ? (
|
||||
<ReactDiffViewer
|
||||
styles={{
|
||||
diffContainer: {
|
||||
overflowX: 'auto',
|
||||
minWidth: 768,
|
||||
},
|
||||
diffRemoved: {
|
||||
overflowX: 'auto',
|
||||
maxWidth: 300,
|
||||
},
|
||||
diffAdded: {
|
||||
overflowX: 'auto',
|
||||
maxWidth: 300,
|
||||
},
|
||||
line: {
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
}}
|
||||
oldValue={value}
|
||||
newValue={sample}
|
||||
splitView={true}
|
||||
leftTitle="config.sh"
|
||||
rightTitle="config.sample.sh"
|
||||
disableWordDiff={true}
|
||||
/>
|
||||
) : (
|
||||
<DiffEditor
|
||||
language={'shell'}
|
||||
original={sample}
|
||||
modified={value}
|
||||
options={{
|
||||
readOnly: true,
|
||||
fontSize: 12,
|
||||
minimap: { enabled: width === '100%' },
|
||||
lineNumbersMinChars: 3,
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
renderSideBySide: width === '100%',
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,5 +27,9 @@
|
||||
.ant-pro-grid-content.wide .ant-pro-page-container-children-content {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
width: calc(100% - 32px - @tree-width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
-18
@@ -5,6 +5,7 @@ import { PageContainer } from '@ant-design/pro-layout';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import { request } from '@/utils/http';
|
||||
import styles from './index.module.less';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
|
||||
function getFilterData(keyword: string, data: any) {
|
||||
const expandedKeys: string[] = [];
|
||||
@@ -188,24 +189,41 @@ const Log = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Editor
|
||||
language="shell"
|
||||
theme={theme}
|
||||
value={value}
|
||||
options={{
|
||||
readOnly: true,
|
||||
fontSize: 12,
|
||||
minimap: { enabled: width === '100%' },
|
||||
lineNumbersMinChars: 3,
|
||||
fontFamily: 'Source Code Pro',
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
onChange={(val, ev) => {
|
||||
setValue((val as string).replace(/\r\n/g, '\n'));
|
||||
}}
|
||||
/>
|
||||
{isPhone ? (
|
||||
<CodeMirror
|
||||
value={value}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
styleActiveLine: true,
|
||||
matchBrackets: true,
|
||||
readOnly: true,
|
||||
}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
setValue(value);
|
||||
}}
|
||||
onChange={(editor, data, value) => {}}
|
||||
/>
|
||||
) : (
|
||||
<Editor
|
||||
language="shell"
|
||||
theme={theme}
|
||||
value={value}
|
||||
options={{
|
||||
readOnly: true,
|
||||
fontSize: 12,
|
||||
minimap: { enabled: width === '100%' },
|
||||
lineNumbersMinChars: 3,
|
||||
fontFamily: 'Source Code Pro',
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
onChange={(val, ev) => {
|
||||
setValue((val as string).replace(/\r\n/g, '\n'));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
|
||||
@@ -21,9 +21,9 @@ const Login = () => {
|
||||
localStorage.setItem(config.authKey, data.token);
|
||||
history.push('/crontab');
|
||||
} else if (data.code === 100) {
|
||||
message.warn(data.msg);
|
||||
message.warn(data.message);
|
||||
} else {
|
||||
message.error(data.msg);
|
||||
message.error(data.message);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Drawer, Button, Tabs, Badge, Select, TreeSelect } from 'antd';
|
||||
import { request } from '@/utils/http';
|
||||
import config from '@/utils/config';
|
||||
import SplitPane from 'react-split-pane';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import SaveModal from './saveModal';
|
||||
import SettingModal from './setting';
|
||||
|
||||
const { Option } = Select;
|
||||
const LangMap: any = {
|
||||
'.py': 'python',
|
||||
'.js': 'javascript',
|
||||
'.sh': 'shell',
|
||||
'.ts': 'typescript',
|
||||
};
|
||||
const prefixMap: any = {
|
||||
python: '.py',
|
||||
javascript: '.js',
|
||||
shell: '.sh',
|
||||
typescript: '.ts',
|
||||
};
|
||||
|
||||
const EditModal = ({
|
||||
treeData,
|
||||
currentFile,
|
||||
content,
|
||||
handleCancel,
|
||||
visible,
|
||||
}: {
|
||||
treeData?: any;
|
||||
currentFile?: string;
|
||||
content?: string;
|
||||
visible: boolean;
|
||||
handleCancel: () => void;
|
||||
}) => {
|
||||
const [value, setValue] = useState('');
|
||||
const [theme, setTheme] = useState<string>('');
|
||||
const [language, setLanguage] = useState<string>('javascript');
|
||||
const [fileName, setFileName] = useState<string>('');
|
||||
const [saveModalVisible, setSaveModalVisible] = useState<boolean>(false);
|
||||
const [settingModalVisible, setSettingModalVisible] =
|
||||
useState<boolean>(false);
|
||||
const [isNewFile, setIsNewFile] = useState<boolean>(false);
|
||||
const [log, setLog] = useState<string>('');
|
||||
|
||||
const cancel = () => {
|
||||
handleCancel();
|
||||
};
|
||||
|
||||
const onSelect = (value: any, node: any) => {
|
||||
const newMode = LangMap[value.slice(-3)] || '';
|
||||
setFileName(value);
|
||||
setLanguage(newMode);
|
||||
setIsNewFile(false);
|
||||
getDetail(node);
|
||||
};
|
||||
|
||||
const getDetail = (node: any) => {
|
||||
request.get(`${config.apiPrefix}scripts/${node.value}`).then((data) => {
|
||||
setValue(data.data);
|
||||
});
|
||||
};
|
||||
|
||||
const createFile = () => {
|
||||
setFileName(`未命名${prefixMap[language]}`);
|
||||
setIsNewFile(true);
|
||||
setValue('');
|
||||
};
|
||||
|
||||
const run = () => {};
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentFile) {
|
||||
createFile();
|
||||
} else {
|
||||
setFileName(currentFile);
|
||||
setValue(content as string);
|
||||
}
|
||||
setIsNewFile(!currentFile);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const storageTheme = localStorage.getItem('qinglong_dark_theme');
|
||||
const isDark =
|
||||
(media.matches && storageTheme !== 'light') || storageTheme === 'dark';
|
||||
setTheme(isDark ? 'vs-dark' : 'vs');
|
||||
media.addEventListener('change', (e) => {
|
||||
if (storageTheme === 'auto' || !storageTheme) {
|
||||
if (e.matches) {
|
||||
setTheme('vs-dark');
|
||||
} else {
|
||||
setTheme('vs');
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
className="edit-modal"
|
||||
title={
|
||||
<>
|
||||
<span style={{ marginRight: 8 }}>{fileName}</span>
|
||||
<TreeSelect
|
||||
style={{ marginRight: 8, width: 120 }}
|
||||
value={currentFile}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={treeData}
|
||||
placeholder="请选择脚本文件"
|
||||
showSearch
|
||||
key="value"
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
<Select
|
||||
value={language}
|
||||
style={{ width: 120, marginRight: 8 }}
|
||||
onChange={(e) => {
|
||||
setLanguage(e);
|
||||
}}
|
||||
>
|
||||
<Option value="javascript">javascript</Option>
|
||||
<Option value="typescript">typescript</Option>
|
||||
<Option value="shell">shell</Option>
|
||||
<Option value="python">python</Option>
|
||||
</Select>
|
||||
<Button type="primary" style={{ marginRight: 8 }} onClick={run}>
|
||||
运行
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={() => {
|
||||
setLog('');
|
||||
}}
|
||||
>
|
||||
清空日志
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={() => {
|
||||
setSettingModalVisible(true);
|
||||
}}
|
||||
>
|
||||
设置
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={createFile}
|
||||
>
|
||||
新建文件
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={() => {
|
||||
setSaveModalVisible(true);
|
||||
}}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
width={'100%'}
|
||||
headerStyle={{ padding: '11px 24px' }}
|
||||
onClose={cancel}
|
||||
visible={visible}
|
||||
>
|
||||
<SplitPane split="vertical" minSize={200} defaultSize="50%">
|
||||
<Editor
|
||||
language={language}
|
||||
value={value}
|
||||
theme={theme}
|
||||
options={{
|
||||
fontSize: 12,
|
||||
minimap: { enabled: false },
|
||||
lineNumbersMinChars: 3,
|
||||
glyphMargin: false,
|
||||
}}
|
||||
onChange={(val) => {
|
||||
setValue((val as string).replace(/\r\n/g, '\n'));
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<pre>{log}</pre>
|
||||
</div>
|
||||
</SplitPane>
|
||||
<SaveModal
|
||||
visible={saveModalVisible}
|
||||
handleCancel={() => {
|
||||
setSaveModalVisible(false);
|
||||
}}
|
||||
isNewFile={isNewFile}
|
||||
file={{ content: value, filename: fileName }}
|
||||
/>
|
||||
<SettingModal
|
||||
visible={settingModalVisible}
|
||||
handleCancel={() => {
|
||||
setSettingModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditModal;
|
||||
@@ -27,5 +27,9 @@
|
||||
.ant-pro-grid-content.wide .ant-pro-page-container-children-content {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
width: calc(100% - 32px - @tree-width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+68
-27
@@ -1,10 +1,12 @@
|
||||
import { useState, useEffect, useCallback, Key, useRef } from 'react';
|
||||
import { TreeSelect, Tree, Input } from 'antd';
|
||||
import { TreeSelect, Tree, Input, Button } from 'antd';
|
||||
import config from '@/utils/config';
|
||||
import { PageContainer } from '@ant-design/pro-layout';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import { request } from '@/utils/http';
|
||||
import styles from './index.module.less';
|
||||
import EditModal from './editModal';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
|
||||
function getFilterData(keyword: string, data: any) {
|
||||
if (keyword) {
|
||||
@@ -41,6 +43,7 @@ const Script = () => {
|
||||
const [height, setHeight] = useState<number>();
|
||||
const treeDom = useRef<any>();
|
||||
const [theme, setTheme] = useState<string>('');
|
||||
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||
|
||||
const getScripts = () => {
|
||||
setLoading(true);
|
||||
@@ -119,18 +122,29 @@ const Script = () => {
|
||||
title={title}
|
||||
loading={loading}
|
||||
extra={
|
||||
isPhone && [
|
||||
<TreeSelect
|
||||
className="log-select"
|
||||
value={select}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={data}
|
||||
placeholder="请选择脚本文件"
|
||||
showSearch
|
||||
key="value"
|
||||
onSelect={onSelect}
|
||||
/>,
|
||||
]
|
||||
isPhone
|
||||
? [
|
||||
<TreeSelect
|
||||
className="log-select"
|
||||
value={select}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={data}
|
||||
placeholder="请选择脚本文件"
|
||||
showSearch
|
||||
key="value"
|
||||
onSelect={onSelect}
|
||||
/>,
|
||||
]
|
||||
: [
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setIsLogModalVisible(true);
|
||||
}}
|
||||
>
|
||||
调试
|
||||
</Button>,
|
||||
]
|
||||
}
|
||||
header={{
|
||||
style: {
|
||||
@@ -164,20 +178,47 @@ const Script = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Editor
|
||||
language={mode}
|
||||
value={value}
|
||||
theme={theme}
|
||||
options={{
|
||||
readOnly: true,
|
||||
fontSize: 12,
|
||||
minimap: { enabled: width === '100%' },
|
||||
lineNumbersMinChars: 3,
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
}}
|
||||
onChange={(val) => {
|
||||
setValue((val as string).replace(/\r\n/g, '\n'));
|
||||
{isPhone ? (
|
||||
<CodeMirror
|
||||
value={value}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
styleActiveLine: true,
|
||||
matchBrackets: true,
|
||||
mode,
|
||||
readOnly: true,
|
||||
}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
setValue(value);
|
||||
}}
|
||||
onChange={(editor, data, value) => {}}
|
||||
/>
|
||||
) : (
|
||||
<Editor
|
||||
language={mode}
|
||||
value={value}
|
||||
theme={theme}
|
||||
options={{
|
||||
readOnly: true,
|
||||
fontSize: 12,
|
||||
minimap: { enabled: width === '100%' },
|
||||
lineNumbersMinChars: 3,
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
}}
|
||||
onChange={(val) => {
|
||||
setValue((val as string).replace(/\r\n/g, '\n'));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<EditModal
|
||||
visible={isLogModalVisible}
|
||||
treeData={data}
|
||||
currentFile={select}
|
||||
content={value}
|
||||
handleCancel={() => {
|
||||
setIsLogModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, message, Input, Form } from 'antd';
|
||||
import { request } from '@/utils/http';
|
||||
import config from '@/utils/config';
|
||||
|
||||
const SaveModal = ({
|
||||
file,
|
||||
handleCancel,
|
||||
visible,
|
||||
isNewFile,
|
||||
}: {
|
||||
file?: any;
|
||||
visible: boolean;
|
||||
handleCancel: (cks?: any[]) => void;
|
||||
isNewFile: boolean;
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleOk = async (values: any) => {
|
||||
console.log(file.filename);
|
||||
setLoading(true);
|
||||
const payload = { ...file, ...values };
|
||||
request
|
||||
.post(`${config.apiPrefix}scripts`, {
|
||||
data: payload,
|
||||
})
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('保存文件成功');
|
||||
handleCancel(data);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
form.resetFields();
|
||||
setLoading(false);
|
||||
}, [file, visible]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="保存文件"
|
||||
visible={visible}
|
||||
forceRender
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
handleOk(values);
|
||||
})
|
||||
.catch((info) => {
|
||||
console.log('Validate Failed:', info);
|
||||
});
|
||||
}}
|
||||
onCancel={() => handleCancel()}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
name="script_modal"
|
||||
initialValues={file}
|
||||
>
|
||||
<Form.Item
|
||||
name="filename"
|
||||
label="文件名"
|
||||
rules={[{ required: true, message: '请输入文件名' }]}
|
||||
>
|
||||
<Input placeholder="请输入文件名" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="path"
|
||||
label="保存目录"
|
||||
rules={[{ required: true, message: '请输入保存目录' }]}
|
||||
>
|
||||
<Input placeholder="请输入保存目录" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SaveModal;
|
||||
@@ -0,0 +1,67 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, message, Input, Form } from 'antd';
|
||||
import { request } from '@/utils/http';
|
||||
import config from '@/utils/config';
|
||||
|
||||
const SettingModal = ({
|
||||
file,
|
||||
handleCancel,
|
||||
visible,
|
||||
}: {
|
||||
file?: any;
|
||||
visible: boolean;
|
||||
handleCancel: (cks?: any[]) => void;
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleOk = async (values: any) => {
|
||||
console.log(file.filename);
|
||||
setLoading(true);
|
||||
const payload = { ...file, ...values };
|
||||
request
|
||||
.post(`${config.apiPrefix}scripts`, {
|
||||
data: payload,
|
||||
})
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
message.success('保存文件成功');
|
||||
handleCancel(data);
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
form.resetFields();
|
||||
setLoading(false);
|
||||
}, [file, visible]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="运行设置"
|
||||
visible={visible}
|
||||
forceRender
|
||||
onCancel={() => handleCancel()}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
name="setting_modal"
|
||||
initialValues={file}
|
||||
>
|
||||
<Form.Item
|
||||
name="filename"
|
||||
label="待开发"
|
||||
rules={[{ required: true, message: '待开发' }]}
|
||||
>
|
||||
<Input placeholder="待开发" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingModal;
|
||||
Reference in New Issue
Block a user