mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-18 22:22:59 +08:00
feat: 增加文件上传功能
This commit is contained in:
parent
b172a5124e
commit
89b95e0d45
@ -1,10 +1,13 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
"path"
|
||||
)
|
||||
|
||||
func (b *BaseApi) ListFiles(c *gin.Context) {
|
||||
@ -131,3 +134,23 @@ func (b *BaseApi) SaveContent(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) UploadFiles(c *gin.Context) {
|
||||
form, err := c.MultipartForm()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
files := form.File["file"]
|
||||
paths := form.Value["path"]
|
||||
success := 0
|
||||
for _, file := range files {
|
||||
err := c.SaveUploadedFile(file, path.Join(paths[0], file.Filename))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("upload [%s] file failed, err: %v", file.Filename, err)
|
||||
continue
|
||||
}
|
||||
success++
|
||||
}
|
||||
helper.SuccessWithMsg(c, fmt.Sprintf("%d files upload success", success))
|
||||
}
|
||||
|
@ -68,6 +68,15 @@ func SuccessWithData(ctx *gin.Context, data interface{}) {
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func SuccessWithMsg(ctx *gin.Context, msg string) {
|
||||
res := dto.Response{
|
||||
Code: constant.CodeSuccess,
|
||||
Msg: msg,
|
||||
}
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func GetParamID(c *gin.Context) (uint, error) {
|
||||
idParam, ok := c.Params.Get("id")
|
||||
if !ok {
|
||||
|
@ -24,6 +24,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
|
||||
fileRouter.POST("/decompress", baseApi.DeCompressFile)
|
||||
fileRouter.POST("/content", baseApi.GetContent)
|
||||
fileRouter.POST("/save", baseApi.SaveContent)
|
||||
fileRouter.POST("/upload", baseApi.UploadFiles)
|
||||
}
|
||||
|
||||
}
|
||||
|
68
frontend/package-lock.json
generated
68
frontend/package-lock.json
generated
@ -15,6 +15,7 @@
|
||||
"echarts-liquidfill": "^3.1.0",
|
||||
"element-plus": "^2.2.13",
|
||||
"fit2cloud-ui-plus": "^0.0.1-beta.15",
|
||||
"js-base64": "^3.7.2",
|
||||
"js-md5": "^0.7.3",
|
||||
"monaco-editor": "^0.34.0",
|
||||
"nprogress": "^0.2.0",
|
||||
@ -22,12 +23,16 @@
|
||||
"pinia-plugin-persistedstate": "^1.6.1",
|
||||
"qs": "^6.10.3",
|
||||
"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",
|
||||
"vue3-seamless-scroll": "^1.2.0"
|
||||
"vue3-seamless-scroll": "^1.2.0",
|
||||
"xterm": "^4.19.0",
|
||||
"xterm-addon-attach": "^0.6.0",
|
||||
"xterm-addon-fit": "^0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.0.1",
|
||||
@ -5863,6 +5868,11 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/js-base64": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.2.tgz",
|
||||
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
|
||||
},
|
||||
"node_modules/js-md5": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
|
||||
@ -7806,6 +7816,14 @@
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/screenfull": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/screenfull/-/screenfull-6.0.2.tgz",
|
||||
"integrity": "sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw==",
|
||||
"engines": {
|
||||
"node": "^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/select": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||
@ -9774,6 +9792,27 @@
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/xterm": {
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmmirror.com/xterm/-/xterm-4.19.0.tgz",
|
||||
"integrity": "sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ=="
|
||||
},
|
||||
"node_modules/xterm-addon-attach": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/xterm-addon-attach/-/xterm-addon-attach-0.6.0.tgz",
|
||||
"integrity": "sha512-Mo8r3HTjI/EZfczVCwRU6jh438B4WLXxdFO86OB7bx0jGhwh2GdF4ifx/rP+OB+Cb2vmLhhVIZ00/7x3YSP3dg==",
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xterm-addon-fit": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz",
|
||||
"integrity": "sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ==",
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
@ -14390,6 +14429,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.2.tgz",
|
||||
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
|
||||
},
|
||||
"js-md5": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
|
||||
@ -15942,6 +15986,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"screenfull": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/screenfull/-/screenfull-6.0.2.tgz",
|
||||
"integrity": "sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw=="
|
||||
},
|
||||
"select": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||
@ -17428,6 +17477,23 @@
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"xterm": {
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmmirror.com/xterm/-/xterm-4.19.0.tgz",
|
||||
"integrity": "sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ=="
|
||||
},
|
||||
"xterm-addon-attach": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/xterm-addon-attach/-/xterm-addon-attach-0.6.0.tgz",
|
||||
"integrity": "sha512-Mo8r3HTjI/EZfczVCwRU6jh438B4WLXxdFO86OB7bx0jGhwh2GdF4ifx/rP+OB+Cb2vmLhhVIZ00/7x3YSP3dg==",
|
||||
"requires": {}
|
||||
},
|
||||
"xterm-addon-fit": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz",
|
||||
"integrity": "sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
@ -36,3 +36,7 @@ export const GetFileContent = (params: File.ReqFile) => {
|
||||
export const SaveFileContent = (params: File.FileEdit) => {
|
||||
return http.post<File.File>('files/save', params);
|
||||
};
|
||||
|
||||
export const UploadFileData = (params: FormData) => {
|
||||
return http.post<File.File>('files/upload', params);
|
||||
};
|
||||
|
@ -2,14 +2,12 @@ export default {
|
||||
commons: {
|
||||
button: {
|
||||
create: '新建',
|
||||
create: '创建',
|
||||
add: '添加',
|
||||
delete: '删除',
|
||||
edit: '编辑',
|
||||
confirm: '确认',
|
||||
cancel: '取消',
|
||||
reset: '重置',
|
||||
login: '登陆',
|
||||
conn: '连接',
|
||||
login: '登录',
|
||||
},
|
||||
@ -197,5 +195,6 @@ export default {
|
||||
softLink: '软链接',
|
||||
hardLink: '硬链接',
|
||||
linkPath: '链接路径',
|
||||
selectFile: '选择文件',
|
||||
},
|
||||
};
|
||||
|
@ -1,12 +1,22 @@
|
||||
<template>
|
||||
<el-dialog v-model="open" :title="'code editor'" @opened="onOpen" :before-close="handleClose">
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
:title="$t('commons.button.edit')"
|
||||
@opened="onOpen"
|
||||
:before-close="handleClose"
|
||||
destroy-on-close
|
||||
width="70%"
|
||||
draggable
|
||||
>
|
||||
<div>
|
||||
<div id="codeBox" style="height: 600px"></div>
|
||||
<div v-loading="loading">
|
||||
<div id="codeBox" style="height: 60vh"></div>
|
||||
</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>
|
||||
<el-button type="primary" @click="save()">{{ $t('commons.button.confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@ -31,6 +41,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
let data = reactive({
|
||||
@ -38,7 +52,7 @@ let data = reactive({
|
||||
language: '',
|
||||
});
|
||||
|
||||
const em = defineEmits(['close', 'save']);
|
||||
const em = defineEmits(['close', 'qsave', 'save']);
|
||||
|
||||
const handleClose = () => {
|
||||
if (editor) {
|
||||
@ -47,6 +61,14 @@ const handleClose = () => {
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
em('save', data.content);
|
||||
};
|
||||
|
||||
const saveNotClose = () => {
|
||||
em('qsave', data.content);
|
||||
};
|
||||
|
||||
const initEditor = () => {
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
@ -67,10 +89,8 @@ const initEditor = () => {
|
||||
data.content = editor.getValue();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const saveContent = () => {
|
||||
em('save', data.content);
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, saveNotClose);
|
||||
};
|
||||
|
||||
const onOpen = () => {
|
||||
|
@ -65,9 +65,9 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
type: [Rules.required],
|
||||
dst: [Rules.required],
|
||||
name: [Rules.required],
|
||||
type: [Rules.requiredSelect],
|
||||
dst: [Rules.requiredInput],
|
||||
name: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
const { open, files, type, dst, name } = toRefs(props);
|
||||
|
@ -71,10 +71,10 @@ const handleClose = () => {
|
||||
};
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
name: [Rules.required],
|
||||
path: [Rules.required],
|
||||
isSymlink: [Rules.required],
|
||||
linkPath: [Rules.required],
|
||||
name: [Rules.requiredInput],
|
||||
path: [Rules.requiredInput],
|
||||
isSymlink: [Rules.requiredInput],
|
||||
linkPath: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
const getMode = (val: number) => {
|
||||
|
@ -60,7 +60,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
dst: [Rules.required],
|
||||
dst: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
const { open, dst, path, name, mimeType } = toRefs(props);
|
||||
|
@ -2,7 +2,7 @@
|
||||
<LayoutContent>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="5">
|
||||
<el-scrollbar height="800px">
|
||||
<el-scrollbar height="80vh">
|
||||
<el-tree
|
||||
:data="fileTree"
|
||||
:props="defaultProps"
|
||||
@ -57,7 +57,7 @@
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button type="primary" plain> {{ $t('file.upload') }}</el-button>
|
||||
<el-button type="primary" plain @click="openUpload"> {{ $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>
|
||||
@ -85,6 +85,7 @@
|
||||
prop="modTime"
|
||||
:formatter="dateFromat"
|
||||
min-width="100"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
</el-table-column>
|
||||
|
||||
@ -118,9 +119,12 @@
|
||||
:open="editorPage.open"
|
||||
:language="'json'"
|
||||
:content="editorPage.content"
|
||||
:loading="editorPage.loading"
|
||||
@close="closeCodeEditor"
|
||||
@qsave="quickSave"
|
||||
@save="saveContent"
|
||||
></CodeEditor>
|
||||
<Upload :open="uploadPage.open" :path="uploadPage.path" @close="closeUpload"></Upload>
|
||||
</el-row>
|
||||
</LayoutContent>
|
||||
</template>
|
||||
@ -139,8 +143,10 @@ import CreateFile from './create/index.vue';
|
||||
import ChangeRole from './change-role/index.vue';
|
||||
import Compress from './compress/index.vue';
|
||||
import Decompress from './decompress/index.vue';
|
||||
import Upload from './upload/index.vue';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
import CodeEditor from './code-editor/index.vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
let data = ref();
|
||||
let selects = ref<any>([]);
|
||||
@ -155,8 +161,9 @@ 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 editorPage = reactive({ open: false, content: '', loading: false });
|
||||
let codeReq = reactive({ path: '', expand: false });
|
||||
const uploadPage = reactive({ open: false, path: '' });
|
||||
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
@ -321,9 +328,30 @@ const closeCodeEditor = () => {
|
||||
editorPage.open = false;
|
||||
};
|
||||
|
||||
const openUpload = () => {
|
||||
uploadPage.open = true;
|
||||
uploadPage.path = req.path;
|
||||
};
|
||||
|
||||
const closeUpload = () => {
|
||||
uploadPage.open = false;
|
||||
search(req);
|
||||
};
|
||||
|
||||
const saveContent = (content: string) => {
|
||||
SaveFileContent({ path: codeReq.path, content: content }).then(() => {
|
||||
editorPage.loading = true;
|
||||
SaveFileContent({ path: codeReq.path, content: content }).finally(() => {
|
||||
editorPage.loading = false;
|
||||
editorPage.open = false;
|
||||
ElMessage.success(i18n.global.t('commons.msg.updateSuccess'));
|
||||
});
|
||||
};
|
||||
|
||||
const quickSave = (content: string) => {
|
||||
editorPage.loading = true;
|
||||
SaveFileContent({ path: codeReq.path, content: content }).finally(() => {
|
||||
editorPage.loading = false;
|
||||
ElMessage.success(i18n.global.t('commons.msg.updateSuccess'));
|
||||
});
|
||||
};
|
||||
|
||||
|
64
frontend/src/views/file-management/upload/index.vue
Normal file
64
frontend/src/views/file-management/upload/index.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<el-dialog v-model="open" :title="$t('file.upload')" @open="onOpen" :before-close="handleClose">
|
||||
<el-upload action="#" :auto-upload="false" ref="uploadRef" :multiple="true" :on-change="fileOnChange">
|
||||
<template #trigger>
|
||||
<el-button type="primary">{{ $t('file.selectFile') }}</el-button>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit()">{{ $t('commons.button.confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ElMessage, UploadFile, UploadFiles, UploadInstance } from 'element-plus';
|
||||
import { UploadFileData } from '@/api/modules/files';
|
||||
|
||||
const props = defineProps({
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
// let loading = ref<Boolean>(false);
|
||||
|
||||
const em = defineEmits(['close']);
|
||||
const handleClose = () => {
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const uploaderFiles = ref<UploadFiles>([]);
|
||||
|
||||
const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
|
||||
uploaderFiles.value = uploadFiles;
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
const formData = new FormData();
|
||||
for (const file of uploaderFiles.value) {
|
||||
if (file.raw != undefined) {
|
||||
formData.append('file', file.raw);
|
||||
}
|
||||
}
|
||||
formData.append('path', props.path);
|
||||
|
||||
UploadFileData(formData).then(() => {
|
||||
ElMessage('upload success');
|
||||
handleClose();
|
||||
});
|
||||
};
|
||||
|
||||
const onOpen = () => {};
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user