mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-24 02:59:16 +08:00
feat: 增加文件编辑功能
This commit is contained in:
parent
1afe79068a
commit
b172a5124e
@ -104,3 +104,30 @@ func (b *BaseApi) DeCompressFile(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetContent(c *gin.Context) {
|
||||
var req dto.FileOption
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
info, err := fileService.GetContent(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
|
||||
func (b *BaseApi) SaveContent(c *gin.Context) {
|
||||
var req dto.FileEdit
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := fileService.SaveContent(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
@ -47,3 +47,8 @@ type FileDeCompress struct {
|
||||
Type string
|
||||
Path string
|
||||
}
|
||||
|
||||
type FileEdit struct {
|
||||
Path string
|
||||
Content string
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type FileService struct {
|
||||
@ -92,6 +93,28 @@ func (f FileService) DeCompress(c dto.FileDeCompress) error {
|
||||
return fo.Decompress(c.Path, c.Dst, files.CompressType(c.Type))
|
||||
}
|
||||
|
||||
func (f FileService) GetContent(c dto.FileOption) (dto.FileInfo, error) {
|
||||
info, err := files.NewFileInfo(c.FileOption)
|
||||
if err != nil {
|
||||
return dto.FileInfo{}, err
|
||||
}
|
||||
return dto.FileInfo{*info}, nil
|
||||
}
|
||||
|
||||
func (f FileService) SaveContent(c dto.FileEdit) error {
|
||||
|
||||
info, err := files.NewFileInfo(files.FileOption{
|
||||
Path: c.Path,
|
||||
Expand: false,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fo := files.NewFileOp()
|
||||
return fo.WriteFile(c.Path, strings.NewReader(c.Content), info.FileMode)
|
||||
}
|
||||
|
||||
func getUuid() string {
|
||||
b := make([]byte, 16)
|
||||
io.ReadFull(rand.Reader, b)
|
||||
|
@ -22,6 +22,8 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
|
||||
fileRouter.POST("/mode", baseApi.ChangeFileMode)
|
||||
fileRouter.POST("/compress", baseApi.CompressFile)
|
||||
fileRouter.POST("/decompress", baseApi.DeCompressFile)
|
||||
fileRouter.POST("/content", baseApi.GetContent)
|
||||
fileRouter.POST("/save", baseApi.SaveContent)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@ -58,10 +57,6 @@ func (f FileOp) DeleteFile(dst string) error {
|
||||
}
|
||||
|
||||
func (f FileOp) WriteFile(dst string, in io.Reader, mode fs.FileMode) error {
|
||||
dir, _ := path.Split(dst)
|
||||
if err := f.Fs.MkdirAll(dir, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2,6 +2,7 @@ package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/afero"
|
||||
"os"
|
||||
"path"
|
||||
@ -129,5 +130,15 @@ func (f *FileInfo) listChildren() error {
|
||||
}
|
||||
|
||||
func (f *FileInfo) getContent() error {
|
||||
return nil
|
||||
if f.Size <= 10*1024*1024 {
|
||||
afs := &afero.Afero{Fs: f.Fs}
|
||||
cByte, err := afs.ReadFile(f.Path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
f.Content = string(cByte)
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("file is too large!")
|
||||
}
|
||||
}
|
||||
|
26
frontend/package-lock.json
generated
26
frontend/package-lock.json
generated
@ -16,12 +16,14 @@
|
||||
"element-plus": "^2.2.13",
|
||||
"fit2cloud-ui-plus": "^0.0.1-beta.15",
|
||||
"js-md5": "^0.7.3",
|
||||
"monaco-editor": "^0.34.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.12",
|
||||
"pinia-plugin-persistedstate": "^1.6.1",
|
||||
"qs": "^6.10.3",
|
||||
"sass-loader": "^13.0.2",
|
||||
"unplugin-vue-define-options": "^0.7.3",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vue": "^3.2.25",
|
||||
"vue-i18n": "^9.1.9",
|
||||
"vue-router": "^4.0.12",
|
||||
@ -6549,6 +6551,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/monaco-editor": {
|
||||
"version": "0.34.0",
|
||||
"resolved": "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.34.0.tgz",
|
||||
"integrity": "sha512-VF+S5zG8wxfinLKLrWcl4WUizMx+LeJrG4PM/M78OhcwocpV0jiyhX/pG6Q9jIOhrb/ckYi6nHnaR5OojlOZCQ=="
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@ -9274,6 +9281,14 @@
|
||||
"vite": "^2.0.0 || ^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-monaco-editor": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz",
|
||||
"integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==",
|
||||
"peerDependencies": {
|
||||
"monaco-editor": ">=0.33.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-vue-setup-extend": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-vue-setup-extend/-/vite-plugin-vue-setup-extend-0.4.0.tgz",
|
||||
@ -14930,6 +14945,11 @@
|
||||
"integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==",
|
||||
"dev": true
|
||||
},
|
||||
"monaco-editor": {
|
||||
"version": "0.34.0",
|
||||
"resolved": "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.34.0.tgz",
|
||||
"integrity": "sha512-VF+S5zG8wxfinLKLrWcl4WUizMx+LeJrG4PM/M78OhcwocpV0jiyhX/pG6Q9jIOhrb/ckYi6nHnaR5OojlOZCQ=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@ -17007,6 +17027,12 @@
|
||||
"markdown-it": "^12.0.0"
|
||||
}
|
||||
},
|
||||
"vite-plugin-monaco-editor": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz",
|
||||
"integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==",
|
||||
"requires": {}
|
||||
},
|
||||
"vite-plugin-vue-setup-extend": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-vue-setup-extend/-/vite-plugin-vue-setup-extend-0.4.0.tgz",
|
||||
|
@ -29,6 +29,7 @@
|
||||
"element-plus": "^2.2.13",
|
||||
"fit2cloud-ui-plus": "^0.0.1-beta.15",
|
||||
"js-md5": "^0.7.3",
|
||||
"monaco-editor": "^0.34.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.12",
|
||||
"pinia-plugin-persistedstate": "^1.6.1",
|
||||
@ -36,6 +37,7 @@
|
||||
"sass-loader": "^13.0.2",
|
||||
"screenfull": "^6.0.2",
|
||||
"unplugin-vue-define-options": "^0.7.3",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vue": "^3.2.25",
|
||||
"vue-i18n": "^9.1.9",
|
||||
"vue-router": "^4.0.12",
|
||||
|
@ -59,4 +59,9 @@ export namespace File {
|
||||
dst: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface FileEdit {
|
||||
path: string;
|
||||
content: string;
|
||||
}
|
||||
}
|
||||
|
@ -18,12 +18,21 @@ export const DeleteFile = (form: File.FileDelete) => {
|
||||
};
|
||||
|
||||
export const ChangeFileMode = (form: File.FileCreate) => {
|
||||
return http.post<File.FileCreate>('files/mode', form);
|
||||
return http.post<File.File>('files/mode', form);
|
||||
};
|
||||
|
||||
export const CompressFile = (form: File.FileCompress) => {
|
||||
return http.post<File.FileCompress>('files/compress', form);
|
||||
return http.post<File.File>('files/compress', form);
|
||||
};
|
||||
|
||||
export const DeCompressFile = (form: File.FileDeCompress) => {
|
||||
return http.post<File.FileCompress>('files/decompress', form);
|
||||
return http.post<File.File>('files/decompress', form);
|
||||
};
|
||||
|
||||
export const GetFileContent = (params: File.ReqFile) => {
|
||||
return http.post<File.File>('files/content', params);
|
||||
};
|
||||
|
||||
export const SaveFileContent = (params: File.FileEdit) => {
|
||||
return http.post<File.File>('files/save', params);
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ export default {
|
||||
reset: '重置',
|
||||
login: '登陆',
|
||||
conn: '连接',
|
||||
login: '登录',
|
||||
},
|
||||
table: {
|
||||
name: '名称',
|
||||
|
81
frontend/src/views/file-management/code-editor/index.vue
Normal file
81
frontend/src/views/file-management/code-editor/index.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<el-dialog v-model="open" :title="'code editor'" @opened="onOpen" :before-close="handleClose">
|
||||
<div>
|
||||
<div id="codeBox" style="height: 600px"></div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveContent">{{ $t('commons.button.confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { reactive } from 'vue';
|
||||
|
||||
let editor: monaco.editor.IStandaloneCodeEditor | undefined;
|
||||
|
||||
const props = defineProps({
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: 'json',
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
let data = reactive({
|
||||
content: '',
|
||||
language: '',
|
||||
});
|
||||
|
||||
const em = defineEmits(['close', 'save']);
|
||||
|
||||
const handleClose = () => {
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
}
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const initEditor = () => {
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
}
|
||||
const codeBox = document.getElementById('codeBox');
|
||||
editor = monaco.editor.create(codeBox as HTMLElement, {
|
||||
theme: 'vs-dark', //官方自带三种主题vs, hc-black, or vs-dark
|
||||
value: data.content,
|
||||
readOnly: false,
|
||||
automaticLayout: true,
|
||||
language: data.language,
|
||||
folding: true, //代码折叠
|
||||
roundedSelection: false, // 右侧不显示编辑器预览框
|
||||
});
|
||||
|
||||
editor.onDidChangeModelContent(() => {
|
||||
if (editor) {
|
||||
data.content = editor.getValue();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const saveContent = () => {
|
||||
em('save', data.content);
|
||||
};
|
||||
|
||||
const onOpen = () => {
|
||||
data.content = props.content;
|
||||
data.language = props.language;
|
||||
initEditor();
|
||||
};
|
||||
</script>
|
@ -82,7 +82,7 @@ const getMode = (val: number) => {
|
||||
};
|
||||
|
||||
let getPath = computed(() => {
|
||||
if (addForm.path === '/') {
|
||||
if (addForm.path.endsWith('/')) {
|
||||
return addForm.path + addForm.name;
|
||||
} else {
|
||||
return addForm.path + '/' + addForm.name;
|
||||
|
@ -60,9 +60,9 @@
|
||||
<el-button type="primary" plain> {{ $t('file.upload') }}</el-button>
|
||||
<el-button type="primary" plain> {{ $t('file.search') }}</el-button>
|
||||
<el-button type="primary" plain> {{ $t('file.remoteFile') }}</el-button>
|
||||
<el-button type="primary" plain> {{ $t('file.sync') }}</el-button>
|
||||
<!-- <el-button type="primary" plain> {{ $t('file.sync') }}</el-button>
|
||||
<el-button type="primary" plain> {{ $t('file.terminal') }}</el-button>
|
||||
<el-button type="primary" plain> {{ $t('file.shareList') }}</el-button>
|
||||
<el-button type="primary" plain> {{ $t('file.shareList') }}</el-button> -->
|
||||
</template>
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="250" fix show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
@ -114,6 +114,13 @@
|
||||
:mimeType="deCompressPage.mimeType"
|
||||
@close="closeDeCompress"
|
||||
></Decompress>
|
||||
<CodeEditor
|
||||
:open="editorPage.open"
|
||||
:language="'json'"
|
||||
:content="editorPage.content"
|
||||
@close="closeCodeEditor"
|
||||
@save="saveContent"
|
||||
></CodeEditor>
|
||||
</el-row>
|
||||
</LayoutContent>
|
||||
</template>
|
||||
@ -123,7 +130,7 @@ import { onMounted, reactive, ref } from '@vue/runtime-core';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { GetFilesList, GetFilesTree, DeleteFile } from '@/api/modules/files';
|
||||
import { GetFilesList, GetFilesTree, DeleteFile, GetFileContent, SaveFileContent } from '@/api/modules/files';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
import { File } from '@/api/interface/file';
|
||||
import BreadCrumbs from '@/components/bread-crumbs/index.vue';
|
||||
@ -133,6 +140,7 @@ import ChangeRole from './change-role/index.vue';
|
||||
import Compress from './compress/index.vue';
|
||||
import Decompress from './decompress/index.vue';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
import CodeEditor from './code-editor/index.vue';
|
||||
|
||||
let data = ref();
|
||||
let selects = ref<any>([]);
|
||||
@ -147,6 +155,8 @@ let filePage = reactive({ open: false, createForm: { path: '/', isDir: false, mo
|
||||
let modePage = reactive({ open: false, modeForm: { path: '/', isDir: false, mode: 0o755 } });
|
||||
let compressPage = reactive({ open: false, files: [''], name: '', dst: '' });
|
||||
let deCompressPage = reactive({ open: false, path: '', name: '', dst: '', mimeType: '' });
|
||||
let editorPage = reactive({ open: false, content: '' });
|
||||
let codeReq = reactive({ path: '', expand: false });
|
||||
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
@ -183,12 +193,14 @@ const open = async (row: File.File) => {
|
||||
if (row.isDir) {
|
||||
const name = row.name;
|
||||
paths.value.push(name);
|
||||
if (req.path === '/') {
|
||||
if (req.path.endsWith('/')) {
|
||||
req.path = req.path + name;
|
||||
} else {
|
||||
req.path = req.path + '/' + name;
|
||||
}
|
||||
search(req);
|
||||
} else {
|
||||
openCodeEditor(row);
|
||||
}
|
||||
};
|
||||
|
||||
@ -296,6 +308,25 @@ const closeDeCompress = () => {
|
||||
search(req);
|
||||
};
|
||||
|
||||
const openCodeEditor = (row: File.File) => {
|
||||
codeReq.path = row.path;
|
||||
codeReq.expand = true;
|
||||
GetFileContent(codeReq).then((res) => {
|
||||
editorPage.content = res.data.content;
|
||||
});
|
||||
editorPage.open = true;
|
||||
};
|
||||
|
||||
const closeCodeEditor = () => {
|
||||
editorPage.open = false;
|
||||
};
|
||||
|
||||
const saveContent = (content: string) => {
|
||||
SaveFileContent({ path: codeReq.path, content: content }).then(() => {
|
||||
editorPage.open = false;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search(req);
|
||||
});
|
||||
|
@ -8,8 +8,8 @@ import viteCompression from 'vite-plugin-compression';
|
||||
import VueSetupExtend from 'vite-plugin-vue-setup-extend';
|
||||
import eslintPlugin from 'vite-plugin-eslint';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
|
||||
import DefineOptions from 'unplugin-vue-define-options/vite';
|
||||
import MonacoEditorPlugin from 'vite-plugin-monaco-editor';
|
||||
|
||||
// @see: https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||
@ -36,6 +36,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||
server: {
|
||||
port: viteEnv.VITE_PORT,
|
||||
open: viteEnv.VITE_OPEN,
|
||||
host: '0.0.0.0',
|
||||
// https: false,
|
||||
proxy: {
|
||||
'/api/v1': {
|
||||
@ -64,6 +65,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||
// * name 可以写在 script 标签上
|
||||
VueSetupExtend(),
|
||||
|
||||
MonacoEditorPlugin({}),
|
||||
viteEnv.VITE_REPORT && visualizer(),
|
||||
// * gzip compress
|
||||
viteEnv.VITE_BUILD_GZIP &&
|
||||
|
Loading…
Reference in New Issue
Block a user