脚本管理增加强制打开文件操作

This commit is contained in:
whyour 2025-03-16 17:57:13 +08:00
parent cf94ecfb11
commit 576408de01
14 changed files with 347 additions and 130 deletions

View File

@ -186,7 +186,7 @@
"秒后重试": "Retry after seconds", "秒后重试": "Retry after seconds",
"在您的设备上打开两步验证应用程序以查看您的身份验证代码并验证您的身份。": "Open the two-factor authentication application on your device to view your authentication code and verify your identity.", "在您的设备上打开两步验证应用程序以查看您的身份验证代码并验证您的身份。": "Open the two-factor authentication application on your device to view your authentication code and verify your identity.",
"请选择脚本文件": "Please select a script file", "请选择脚本文件": "Please select a script file",
"当前文件不支持预览": "The current file does not support preview", "当前文件不支持预览": "Current file type is not supported for preview",
"清空日志": "Clear Logs", "清空日志": "Clear Logs",
"设置": "Settings", "设置": "Settings",
"退出": "Exit", "退出": "Exit",
@ -501,5 +501,9 @@
"常规定时": "Normal Timing", "常规定时": "Normal Timing",
"手动运行": "Manual Run", "手动运行": "Manual Run",
"开机运行": "Boot Run", "开机运行": "Boot Run",
"时区": "Timezone" "时区": "Timezone",
"强制打开": "Force Open",
"强制打开可能会导致编辑器显示异常": "Force opening may cause display issues in the editor",
"确认离开": "Confirm Leave",
"当前文件未保存,确认离开吗": "Current file is not saved, are you sure to leave?"
} }

View File

@ -501,6 +501,10 @@
"常规定时": "常规定时", "常规定时": "常规定时",
"手动运行": "手动运行", "手动运行": "手动运行",
"开机运行": "开机运行", "开机运行": "开机运行",
"时区": "时区" "时区": "时区",
"强制打开": "强制打开",
"强制打开可能会导致编辑器显示异常": "强制打开可能会导致编辑器显示异常",
"确认离开": "确认离开",
"当前文件未保存,确认离开吗": "当前文件未保存,确认离开吗"
} }

View File

@ -202,9 +202,6 @@ const CronDetailModal = ({
.catch((e) => reject(e)); .catch((e) => reject(e));
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -232,9 +229,6 @@ const CronDetailModal = ({
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -259,9 +253,6 @@ const CronDetailModal = ({
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -298,9 +289,6 @@ const CronDetailModal = ({
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -337,9 +325,6 @@ const CronDetailModal = ({
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };

View File

@ -20,7 +20,7 @@ import {
PushpinOutlined, PushpinOutlined,
SettingOutlined, SettingOutlined,
StopOutlined, StopOutlined,
UnorderedListOutlined UnorderedListOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-layout'; import { PageContainer } from '@ant-design/pro-layout';
import { history, useOutletContext } from '@umijs/max'; import { history, useOutletContext } from '@umijs/max';
@ -36,7 +36,7 @@ import {
TablePaginationConfig, TablePaginationConfig,
Tabs, Tabs,
Tag, Tag,
Typography Typography,
} from 'antd'; } from 'antd';
import { ColumnProps } from 'antd/lib/table'; import { ColumnProps } from 'antd/lib/table';
import { FilterValue, SorterResult } from 'antd/lib/table/interface'; import { FilterValue, SorterResult } from 'antd/lib/table/interface';
@ -453,9 +453,6 @@ const Crontab = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -488,9 +485,6 @@ const Crontab = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -524,9 +518,6 @@ const Crontab = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -569,9 +560,6 @@ const Crontab = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -614,9 +602,6 @@ const Crontab = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -738,9 +723,6 @@ const Crontab = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -766,9 +748,6 @@ const Crontab = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };

View File

@ -85,7 +85,7 @@ const ViewManageModal = ({
title: intl.get('名称'), title: intl.get('名称'),
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
render: (v) => (v === '全部任务' ? intl.get('全部任务') : v) render: (v) => (v === '全部任务' ? intl.get('全部任务') : v),
}, },
{ {
title: intl.get('类型'), title: intl.get('类型'),
@ -162,9 +162,6 @@ const ViewManageModal = ({
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };

View File

@ -315,9 +315,6 @@ const Dependence = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -342,9 +339,6 @@ const Dependence = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -367,9 +361,6 @@ const Dependence = () => {
getDependencies(); getDependencies();
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -419,9 +410,6 @@ const Dependence = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -439,9 +427,6 @@ const Dependence = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };

View File

@ -292,9 +292,6 @@ const Env = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -337,9 +334,6 @@ const Env = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -456,9 +450,6 @@ const Env = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -484,9 +475,6 @@ const Env = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };

View File

@ -175,9 +175,6 @@ const Log = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };

View File

@ -0,0 +1,67 @@
.container {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: var(--background-color);
padding: 16px;
}
.content {
text-align: center;
background: var(--card-background);
padding: 24px;
border-radius: 12px;
max-width: 360px;
width: 100%;
transition: all 0.3s ease;
}
.iconWrapper {
display: inline-flex;
align-items: center;
justify-content: center;
width: 64px;
height: 64px;
border-radius: 50%;
background: var(--background-color);
margin-bottom: 16px;
}
.icon {
font-size: 32px;
color: var(--text-color-secondary);
}
.message {
font-size: 16px;
color: var(--text-color);
margin-bottom: 16px;
font-weight: 500;
line-height: 1.5;
}
.actionArea {
width: 100%;
}
.button {
min-width: 140px;
height: 36px;
font-size: 14px;
}
.warning {
font-size: 13px;
color: var(--text-color-secondary);
line-height: 1.5;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.warningIcon {
font-size: 14px;
color: #faad14;
}

View File

@ -0,0 +1,41 @@
import React from 'react';
import { Button, Space } from 'antd';
import { FileUnknownOutlined, WarningOutlined } from '@ant-design/icons';
import intl from 'react-intl-universal';
import styles from './index.module.less';
interface UnsupportedFilePreviewProps {
onForceOpen: () => void;
}
const UnsupportedFilePreview: React.FC<UnsupportedFilePreviewProps> = ({
onForceOpen,
}) => {
return (
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.iconWrapper}>
<FileUnknownOutlined className={styles.icon} />
</div>
<div className={styles.message}>
{intl.get('当前文件不支持预览')}
</div>
<Space direction="vertical" size={8} className={styles.actionArea}>
<Button
type="primary"
onClick={onForceOpen}
className={styles.button}
>
{intl.get('强制打开')}
</Button>
<div className={styles.warning}>
<WarningOutlined className={styles.warningIcon} />
{intl.get('强制打开可能会导致编辑器显示异常')}
</div>
</Space>
</div>
</div>
);
};
export default UnsupportedFilePreview;

View File

@ -43,6 +43,7 @@ import EditModal from './editModal';
import EditScriptNameModal from './editNameModal'; import EditScriptNameModal from './editNameModal';
import styles from './index.module.less'; import styles from './index.module.less';
import RenameModal from './renameModal'; import RenameModal from './renameModal';
import UnsupportedFilePreview from './components/UnsupportedFilePreview';
const { Text } = Typography; const { Text } = Typography;
const Script = () => { const Script = () => {
@ -63,6 +64,7 @@ const Script = () => {
useState(false); useState(false);
const [currentNode, setCurrentNode] = useState<any>(); const [currentNode, setCurrentNode] = useState<any>();
const [expandedKeys, setExpandedKeys] = useState<string[]>([]); const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
const [showMonaco, setShowMonaco] = useState(true);
const handleIsEditing = (filename: string, value: boolean) => { const handleIsEditing = (filename: string, value: boolean) => {
setIsEditing(value && canPreviewInMonaco(filename)); setIsEditing(value && canPreviewInMonaco(filename));
@ -82,7 +84,7 @@ const Script = () => {
.finally(() => needLoading && setLoading(false)); .finally(() => needLoading && setLoading(false));
}; };
const getDetail = (node: any) => { const getDetail = (node: any, options: any = {}) => {
request request
.get( .get(
`${config.apiPrefix}scripts/detail?file=${encodeURIComponent( `${config.apiPrefix}scripts/detail?file=${encodeURIComponent(
@ -92,6 +94,9 @@ const Script = () => {
.then(({ code, data }) => { .then(({ code, data }) => {
if (code === 200) { if (code === 200) {
setValue(data); setValue(data);
if (options.callback) {
options.callback();
}
} }
}); });
}; };
@ -126,7 +131,7 @@ const Script = () => {
if (item) { if (item) {
obj.node = item; obj.node = item;
setExpandedKeys([p as string]); setExpandedKeys([p as string]);
onTreeSelect([vkey], obj); onSelect([vkey], obj);
} }
} }
}; };
@ -141,18 +146,27 @@ const Script = () => {
if (node.type === 'directory') { if (node.type === 'directory') {
setValue(intl.get('请选择脚本文件')); setValue(intl.get('请选择脚本文件'));
setShowMonaco(true);
return; return;
} }
if (!canPreviewInMonaco(node.title)) { if (!canPreviewInMonaco(node.title)) {
setValue(intl.get('当前文件不支持预览')); setShowMonaco(false);
return; return;
} }
setShowMonaco(true);
const newMode = getEditorMode(value); const newMode = getEditorMode(value);
setMode(isPhone && newMode === 'typescript' ? 'javascript' : newMode); setMode(isPhone && newMode === 'typescript' ? 'javascript' : newMode);
setValue(intl.get('加载中...')); setValue(intl.get('加载中...'));
getDetail(node);
getDetail(node, {
callback: () => {
if (isEditing) {
setIsEditing(true);
}
},
});
}; };
const onTreeSelect = useCallback( const onTreeSelect = useCallback(
@ -161,20 +175,20 @@ const Script = () => {
if (node.key === select && isEditing) { if (node.key === select && isEditing) {
return; return;
} }
const content = editorRef.current
const currentContent = editorRef.current
? editorRef.current.getValue().replace(/\r\n/g, '\n') ? editorRef.current.getValue().replace(/\r\n/g, '\n')
: value; : value;
if (content !== value) { const originalContent = value.replace(/\r\n/g, '\n');
if (currentContent !== originalContent && isEditing) {
Modal.confirm({ Modal.confirm({
title: `确认离开`, title: intl.get('确认离开'),
content: <>{intl.get('当前修改未保存,确定离开吗')}</>, content: <>{intl.get('当前文件未保存,确认离开吗')}</>,
onOk() { onOk() {
onSelect(keys[0], e.node); onSelect(keys[0], e.node);
handleIsEditing(e.node.title, false); handleIsEditing(e.node.title, false);
}, },
onCancel() {
console.log('Cancel');
},
}); });
} else { } else {
handleIsEditing(e.node.title, false); handleIsEditing(e.node.title, false);
@ -268,9 +282,6 @@ const Script = () => {
.catch((e) => reject(e)); .catch((e) => reject(e));
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -320,9 +331,6 @@ const Script = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -493,7 +501,7 @@ const Script = () => {
label: intl.get('编辑'), label: intl.get('编辑'),
key: 'edit', key: 'edit',
icon: <EditOutlined />, icon: <EditOutlined />,
disabled: !currentNode || !canPreviewInMonaco(currentNode?.title), disabled: !currentNode,
}, },
{ {
label: intl.get('重命名'), label: intl.get('重命名'),
@ -514,6 +522,20 @@ const Script = () => {
}, },
}; };
const handleForceOpen = () => {
if (!currentNode) return;
setMode('plaintext');
setValue(intl.get('加载中...'));
setShowMonaco(true);
getDetail(currentNode, {
callback: () => {
setIsEditing(true);
},
});
};
return ( return (
<PageContainer <PageContainer
className="ql-container-wrapper log-wrapper" className="ql-container-wrapper log-wrapper"
@ -575,9 +597,7 @@ const Script = () => {
</Tooltip>, </Tooltip>,
<Tooltip title={intl.get('编辑')}> <Tooltip title={intl.get('编辑')}>
<Button <Button
disabled={ disabled={!currentNode}
!currentNode || !canPreviewInMonaco(currentNode?.title)
}
type="primary" type="primary"
onClick={editFile} onClick={editFile}
icon={<EditOutlined />} icon={<EditOutlined />}
@ -666,21 +686,28 @@ const Script = () => {
</div> </div>
)} )}
</div> </div>
<Editor {showMonaco ? (
language={mode} <Editor
value={value} language={mode}
theme={theme} value={value}
options={{ theme={theme}
readOnly: !isEditing, options={{
fontSize: 12, readOnly: !isEditing,
lineNumbersMinChars: 3, fontSize: 12,
glyphMargin: false, lineNumbersMinChars: 3,
accessibilitySupport: 'off', glyphMargin: false,
}} accessibilitySupport: 'off',
onMount={(editor) => { }}
editorRef.current = editor; onMount={(editor) => {
}} editorRef.current = editor;
/> }}
/>
) : (
<UnsupportedFilePreview
filename={currentNode?.title || ''}
onForceOpen={handleForceOpen}
/>
)}
</SplitPane> </SplitPane>
)} )}
{isPhone && ( {isPhone && (

View File

@ -181,9 +181,6 @@ const Setting = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -213,9 +210,6 @@ const Setting = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };

View File

@ -266,9 +266,6 @@ const Subscription = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -302,9 +299,6 @@ const Subscription = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -370,9 +364,6 @@ const Subscription = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };
@ -415,9 +406,6 @@ const Subscription = () => {
} }
}); });
}, },
onCancel() {
console.log('Cancel');
},
}); });
}; };

View File

@ -1,7 +1,168 @@
import * as monaco from 'monaco-editor'; import * as monaco from 'monaco-editor';
interface FileTypeConfig {
extensions?: string[]; // 文件扩展名
filenames?: string[]; // 完整文件名
patterns?: RegExp[]; // 文件名正则匹配
startsWith?: string[]; // 文件名前缀匹配
endsWith?: string[]; // 文件名后缀匹配
}
// 文件类型分类配置(只包含特殊文件类型)
const fileTypeConfigs: Record<string, FileTypeConfig> = {
// 前端特殊文件
frontend: {
extensions: [
'.json5', // JSON5
'.vue', // Vue
'.svelte', // Svelte
'.astro', // Astro
'.wxss', // 微信小程序样式
'.pcss', // PostCSS
'.acss', // 支付宝小程序样式
],
patterns: [
/\.env\.(local|development|production|test)$/,
/\.module\.(css|less|scss|sass)$/,
/\.d\.ts$/,
/\.config\.(js|ts|json)$/,
],
},
// 小程序相关
miniprogram: {
extensions: [
'.wxml', // 微信小程序
'.wxs', // 微信小程序
'.axml', // 支付宝小程序
'.sjs', // 支付宝小程序
'.swan', // 百度小程序
'.ttml', // 字节跳动小程序
'.ttss', // 字节跳动小程序
'.wxl', // 微信小程序语言包
'.qml', // QQ小程序
'.qss', // QQ小程序
'.ksml', // 快手小程序
'.kss', // 快手小程序
],
},
// 开发工具相关
devtools: {
extensions: [
'.prisma', // Prisma
'.mdx', // MDX
'.swagger', // Swagger
'.openapi', // OpenAPI
],
},
// 锁文件
lock: {
filenames: [
'yarn.lock',
'pnpm-lock.yaml',
'package-lock.json',
'composer.lock',
'Gemfile.lock',
'poetry.lock',
'Cargo.lock',
],
},
// 无后缀配置文件
noExtension: {
filenames: [
'.dockerignore',
'.gitignore',
'.npmignore',
'.browserslistrc',
'.czrc',
'.huskyrc',
'.lintstagedrc',
'.nvmrc',
'.gcloudignore',
'.htaccess',
],
patterns: [
/^\.env\./,
],
},
// CI/CD 配置
cicd: {
patterns: [
/^\.github\/workflows\/.*\.yml$/,
/^\.gitlab\/.*\.yml$/,
/^\.circleci\/.*\.yml$/,
],
},
};
/**
* Monaco
* @param fileName
* @returns boolean
*/
export function canPreviewInMonaco(fileName: string): boolean { export function canPreviewInMonaco(fileName: string): boolean {
if (!fileName) return false;
// 获取 Monaco 支持的语言
const supportedLanguages = monaco.languages.getLanguages(); const supportedLanguages = monaco.languages.getLanguages();
const ext = fileName.slice(fileName.lastIndexOf('.')).toLowerCase(); const ext = fileName.slice(fileName.lastIndexOf('.')).toLowerCase();
return supportedLanguages.some((lang) => lang.extensions?.includes(ext)); const lowercaseFileName = fileName.toLowerCase();
// 检查 Monaco 原生支持
if (supportedLanguages.some((lang) =>
lang.extensions?.includes(ext) ||
(lang.filenames?.includes(lowercaseFileName))
)) {
return true;
}
// 检查额外支持的文件类型
return Object.values(fileTypeConfigs).some(config => {
return (
(config.extensions?.includes(ext)) ||
(config.filenames?.includes(lowercaseFileName)) ||
(config.patterns?.some(pattern => pattern.test(lowercaseFileName))) ||
(config.startsWith?.some(prefix => lowercaseFileName.startsWith(prefix))) ||
(config.endsWith?.some(suffix => lowercaseFileName.endsWith(suffix)))
);
});
}
/**
*
* @param fileName
* @returns string
*/
export function getFileCategory(fileName: string): string {
if (!fileName) return 'unknown';
const lowercaseFileName = fileName.toLowerCase();
const ext = fileName.slice(fileName.lastIndexOf('.')).toLowerCase();
for (const [category, config] of Object.entries(fileTypeConfigs)) {
if (
(config.extensions?.includes(ext)) ||
(config.filenames?.includes(lowercaseFileName)) ||
(config.patterns?.some(pattern => pattern.test(lowercaseFileName))) ||
(config.startsWith?.some(prefix => lowercaseFileName.startsWith(prefix))) ||
(config.endsWith?.some(suffix => lowercaseFileName.endsWith(suffix)))
) {
return category;
}
}
// 检查 Monaco 原生支持
const supportedLanguages = monaco.languages.getLanguages();
if (supportedLanguages.some((lang) =>
lang.extensions?.includes(ext) ||
(lang.filenames?.includes(lowercaseFileName))
)) {
return 'monaco-native';
}
return 'unknown';
} }