前端添加代码编辑器主题 缩略图

This commit is contained in:
opensnail 2024-12-20 23:39:57 +08:00
parent 6eb66b4c18
commit 7b556668d2
4 changed files with 197 additions and 115 deletions

View File

@ -29,3 +29,11 @@ export function saveFileContent(data: Api.File.SaveFileRequest) {
data data
}); });
} }
export function fetchInitProject(data: Api.File.InitProjectRequest) {
return request<Api.File.FileContent>({
url: '/file/init/project',
method: 'post',
data
});
}

View File

@ -8,18 +8,16 @@ export function runPythonCommand(data: Api.Python.SaveFileRequest) {
}); });
} }
export function pythonCommand3(data: Api.Python.SaveFileRequest) { export function pythonCommand3() {
return request<boolean>({ return request<boolean>({
url: '/python/stop', url: '/python/stop',
method: 'post', method: 'post'
data
}); });
} }
export function fetchPythonStatus(data: Api.Python.SaveFileRequest) { export function fetchPythonStatus() {
return request<boolean>({ return request<boolean>({
url: '/python/status', url: '/python/status',
method: 'get', method: 'get'
data
}); });
} }

View File

@ -1260,6 +1260,11 @@ declare namespace Api {
filePath: string; filePath: string;
content: string; content: string;
}>; }>;
type InitProjectRequest = Common.CommonRecord<{
projectName: string;
projectUrl: string;
}>;
} }
namespace Python { namespace Python {

View File

@ -5,6 +5,7 @@ import { Folder, FolderOpenOutline } from '@vicons/ionicons5';
import { NIcon } from 'naive-ui'; import { NIcon } from 'naive-ui';
import MonacoEditor from '@/components/common/monaco-editor.vue'; import MonacoEditor from '@/components/common/monaco-editor.vue';
import { import {
fetchInitProject,
fetchListFiles, fetchListFiles,
fetchPythonStatus, fetchPythonStatus,
fetchViewFile, fetchViewFile,
@ -14,26 +15,66 @@ import {
} from '@/service/api'; } from '@/service/api';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { fetchQuickPublish } from '@/service/api/docker'; import { fetchQuickPublish } from '@/service/api/docker';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
const code = ref(`Hello SnailJob`); const code = ref(`Hello SnailJob`);
const dataRef = ref(createData()); const dataRef = ref();
const curFile = ref<TreeOption>(); const curFile = ref<TreeOption>();
const pyStatus = ref<boolean>(false); const theme = ref('vs');
const eol = ref(0);
const wordWrap = ref('on');
const minimap = ref('true');
const themes = [
{
label: 'Visual Studio',
value: 'vs'
},
{
label: 'Visual Studio Dark',
value: 'vs-dark'
},
{
label: 'High Contrast Dark',
value: 'hc-black'
}
];
function createData() { const eols = [
return [ {
{ label: 'LF (Linux)',
label: 'snail-job-python', value: 0
key: 'snail-job-python', },
isLeaf: false, {
prefix: () => { label: 'CRLF (Windows)',
return h(NIcon, null, { value: 1
default: () => h(Folder) }
}); ];
}
} const wordWraps = [
]; {
} label: '启用',
value: 'on'
},
{
label: '关闭',
value: 'off'
}
];
const minimaps = [
{
label: '启用',
value: 'true'
},
{
label: '关闭',
value: 'false'
}
];
const pyStatus = defineModel<boolean>('pyStatus', {
default: false
});
const updatePrefixWithExpaned = ( const updatePrefixWithExpaned = (
_keys: Array<string | number>, _keys: Array<string | number>,
@ -60,6 +101,22 @@ const updatePrefixWithExpaned = (
break; break;
} }
}; };
const { formRef, validate } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();
type Model = Pick<Api.File.InitProjectRequest, 'projectName' | 'projectUrl'>;
type RuleKey = Extract<keyof Model, 'projectName' | 'projectUrl'>;
const rules: Record<RuleKey, App.Global.FormRule> = {
projectName: defaultRequiredRule,
projectUrl: defaultRequiredRule
};
const model: Model = reactive({
projectName: 'project',
projectUrl: 'https://github.com/open-snail/python-client/archive/refs/tags/v0.0.1.zip'
});
const nodeProps = ({ option }: { option: TreeOption }) => { const nodeProps = ({ option }: { option: TreeOption }) => {
return { return {
onClick() { onClick() {
@ -71,6 +128,14 @@ const nodeProps = ({ option }: { option: TreeOption }) => {
}; };
}; };
async function handleSubmit() {
await validate();
const { projectName, projectUrl } = model;
const { error } = await fetchInitProject({ projectName, projectUrl });
if (error) return;
window.$message?.success($t('common.addSuccess'));
}
async function getViewFile(option: TreeOption) { async function getViewFile(option: TreeOption) {
if (option.isLeaf) { if (option.isLeaf) {
const res = await fetchViewFile(option.key as string); const res = await fetchViewFile(option.key as string);
@ -141,65 +206,18 @@ async function onLoad(node: TreeOption) {
}); });
} }
type Model = Pick<Api.File.SaveFileRequest, 'fileName' | 'filePath' | 'content'>;
const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
fileName: curFile.value?.key as string,
filePath: curFile.value?.label as string,
content: code.value
};
}
async function saveFile() {
model.fileName = curFile.value?.key as string;
model.filePath = curFile.value?.label as string;
model.content = code.value;
const { fileName, filePath, content } = model;
const { error } = await saveFileContent({ fileName, filePath, content });
if (error) {
window.$message?.success($t('common.updateFailed'));
} else {
window.$message?.success($t('common.updateSuccess'));
}
}
type PythonModel = Pick<Api.Python.SaveFileRequest, 'pythonPath' | 'command'>;
const pythonModel: PythonModel = reactive(createPythonModelModel());
function createPythonModelModel(): PythonModel {
return {
pythonPath: '',
command: 'pip3 install -r && python3 ' /// ToDo
};
}
function codeUpdate(content: string) { function codeUpdate(content: string) {
code.value = content; code.value = content;
} }
function saveFileClick() { async function saveFileClick() {
saveFile(); const { error } = await saveFileContent(
} reactive<Api.File.SaveFileRequest>({
fileName: curFile.value?.key as string,
async function runPython() { filePath: curFile.value?.label as string,
pythonModel.pythonPath = curFile.value?.key as string; content: code.value
})
const { pythonPath, command } = pythonModel; );
const { error } = await runPythonCommand({ pythonPath, command });
if (error) {
window.$message?.success($t('common.updateFailed'));
} else {
window.$message?.success($t('common.updateSuccess'));
}
}
async function stopPython() {
const { error } = await pythonCommand3(pythonModel);
if (error) { if (error) {
window.$message?.success($t('common.updateFailed')); window.$message?.success($t('common.updateFailed'));
} else { } else {
@ -208,7 +226,7 @@ async function stopPython() {
} }
async function pythonStatus() { async function pythonStatus() {
const { error, data } = await fetchPythonStatus(pythonModel); const { error, data } = await fetchPythonStatus();
if (error) { if (error) {
window.$message?.success($t('common.updateFailed')); window.$message?.success($t('common.updateFailed'));
} else { } else {
@ -216,20 +234,40 @@ async function pythonStatus() {
} }
} }
type ContainerRequestModel = Pick<Api.Docker.CreateContainerRequest, 'groupName' | 'namespaceId' | 'imageName'>; async function runPythonClick() {
const { error } = await runPythonCommand(
const containerRequestModel: ContainerRequestModel = reactive(createContainerRequestModelModel()); reactive<Api.Python.SaveFileRequest>({
pythonPath: curFile.value?.key as string,
function createContainerRequestModelModel(): ContainerRequestModel { command: 'pip3 install -r && python3 ' /// ToDo
return { })
groupName: 'snail_job_demo_group', );
namespaceId: '764d604ec6fc45f68cd92514c40e9e1a', /// ToDo if (error) {
imageName: '' window.$message?.success($t('common.updateFailed'));
}; } else {
window.$message?.success($t('common.updateSuccess'));
pyStatus.value = true;
}
// pythonStatus();
} }
async function quickPublish() { async function stopPythonClick() {
const { error } = await fetchQuickPublish(containerRequestModel); const { error } = await pythonCommand3();
if (error) {
window.$message?.success($t('common.updateFailed'));
} else {
window.$message?.success($t('common.updateSuccess'));
}
pythonStatus();
}
async function quickPublishClick() {
const { error } = await fetchQuickPublish(
reactive<Api.Docker.CreateContainerRequest>({
groupName: 'snail_job_demo_group',
namespaceId: '764d604ec6fc45f68cd92514c40e9e1a', /// ToDo
imageName: ''
})
);
if (error) { if (error) {
window.$message?.success($t('common.updateFailed')); window.$message?.success($t('common.updateFailed'));
} else { } else {
@ -237,41 +275,57 @@ async function quickPublish() {
} }
} }
function runPythonClick() { onMounted(async () => {
runPython(); const res = await fetchListFiles('');
pythonStatus(); const files = res.data as Api.File.FileInfo[];
} dataRef.value = [
{
function stopPythonClick() { label: files[0].fileName,
stopPython(); key: files[0].fileName,
pythonStatus(); isLeaf: false,
} prefix: () => {
return h(NIcon, null, {
function quickPublishClick() { default: () => h(Folder)
quickPublish(); });
} }
}
onMounted(() => { ];
pythonStatus(); await pythonStatus();
}); });
</script> </script>
<template> <template>
<div class="code-editor"> <div v-if="dataRef">
<NSpace> <NCard>
<NButton strong secondary type="primary" @click="saveFileClick">保存</NButton> <NGrid x-gap="12" :cols="4">
<NButton v-if="pyStatus" strong secondary type="primary" @click="stopPythonClick">停止</NButton> <NGi>
<NButton v-else strong secondary type="primary" @click="runPythonClick">运行</NButton> 主题: <NSelect v-model:value="theme" :options="themes" />
<NButton strong secondary type="primary" @click="quickPublishClick">一键发布</NButton> </NGi>
</NSpace> <NGi>
<NSelect v-model:value="eol" :options="eols" />
</NGi>
<NGi>
<NSelect v-model:value="wordWrap" :options="wordWraps" />
</NGi>
<NGi>
<NSelect v-model:value="minimap" :options="minimaps" />
</NGi>
</NGrid>
<br/>
<NSpace reverse>
<NButton strong secondary type="success" @click="quickPublishClick">一键发布</NButton>
<NButton v-if="pyStatus" strong secondary type="error" @click="stopPythonClick">停止</NButton>
<NButton v-else strong secondary type="info" @click="runPythonClick">运行</NButton>
<NButton strong secondary type="warning" @click="saveFileClick">保存</NButton>
</NSpace>
</NCard>
<NLayout has-sider> <NLayout has-sider>
<NLayoutSider <NLayoutSider
collapse-mode="transform" collapse-mode="transform"
:collapsed-width="120" :collapsed-width="120"
:width="240" :width="240"
show-trigger="bar" show-trigger="bar"
content-style="padding: 24px;" content-style="padding-top: 12px;"
bordered bordered
> >
<NTree <NTree
@ -297,6 +351,23 @@ onMounted(() => {
</NLayoutContent> </NLayoutContent>
</NLayout> </NLayout>
</div> </div>
<div v-else>
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem path="projectName" label="项目名称">
<NInput v-model:value="model.projectName" />
</NFormItem>
<NFormItem path="项目地址" label="项目地址">
<NInput v-model:value="model.projectUrl" />
</NFormItem>
<NRow :gutter="[0, 24]">
<NCol :span="24">
<div style="display: flex; justify-content: flex-end">
<NButton type="primary" @click="handleSubmit">{{ $t('common.save') }}</NButton>
</div>
</NCol>
</NRow>
</NForm>
</div>
</template> </template>
<style scoped></style> <style scoped></style>