文件管理 完成

pull/1/head
李强 2021-02-28 03:15:53 +08:00
parent 29b426ec1c
commit 5de0213cf5
11 changed files with 430 additions and 12 deletions

View File

@ -2,3 +2,4 @@ from ..models.config_settings import ConfigSettings
from ..models.dict_data import DictData from ..models.dict_data import DictData
from ..models.dict_details import DictDetails from ..models.dict_details import DictDetails
from ..models.web_set import WebSet from ..models.web_set import WebSet
from ..models.save_file import SaveFile

View File

@ -0,0 +1,27 @@
import os
import uuid
from django.db.models import CharField, FileField
from django.utils import timezone
from apps.op_drf.models import CoreModel
def files_path(instance, filename):
return '/'.join(['system', timezone.now().strftime("%Y-%m-%d"), str(uuid.uuid4()) + os.path.splitext(filename)[-1]])
class SaveFile(CoreModel):
name = CharField(max_length=128, verbose_name="文件名称", null=True, blank=True)
type = CharField(max_length=32, verbose_name="文件类型", null=True, blank=True)
size = CharField(max_length=64, verbose_name="文件大小", null=True, blank=True)
address = CharField(max_length=16, verbose_name="存储位置", null=True, blank=True) # 本地、阿里云、腾讯云..
oss_url = CharField(max_length=200, verbose_name="OSS地址", null=True, blank=True)
file = FileField(verbose_name="文件URL", upload_to=files_path, )
class Meta:
verbose_name = '文件管理'
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.name}"

View File

@ -1,7 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from apps.op_drf.serializers import CustomModelSerializer from apps.op_drf.serializers import CustomModelSerializer
from apps.system.models import DictData, DictDetails, ConfigSettings from apps.system.models import DictData, DictDetails, ConfigSettings, SaveFile
# ================================================= # # ================================================= #
@ -89,3 +89,39 @@ class ConfigSettingsCreateUpdateSerializer(CustomModelSerializer):
model = ConfigSettings model = ConfigSettings
exclude = ('description', 'creator', 'modifier') exclude = ('description', 'creator', 'modifier')
read_only_fields = ('update_datetime', 'create_datetime', 'creator', 'modifier') read_only_fields = ('update_datetime', 'create_datetime', 'creator', 'modifier')
# ================================================= #
# ************** 参数设置 序列化器 ************** #
# ================================================= #
class SaveFileSerializer(CustomModelSerializer):
"""
文件管理 简单序列化器
"""
file_url = serializers.CharField(source='file.url', read_only=True)
class Meta:
model = SaveFile
exclude = ('description',)
class SaveFileCreateUpdateSerializer(CustomModelSerializer):
"""
字典详情 创建/更新时的列化器
"""
file_url = serializers.CharField(source='file.url', read_only=True)
def save(self, **kwargs):
files = self.context.get('request').FILES.get('file')
self.validated_data['name'] = files.name
self.validated_data['size'] = files.size
self.validated_data['type'] = files.content_type
self.validated_data['address'] = '本地存储'
instance = super().save(**kwargs)
# 进行判断是否需要OSS上传
return instance
class Meta:
model = SaveFile
exclude = ('description', 'creator', 'modifier')

View File

@ -2,14 +2,17 @@ from django.urls import re_path
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from apps.system.views import DictDataModelViewSet, DictDetailsModelViewSet, \ from apps.system.views import DictDataModelViewSet, DictDetailsModelViewSet, \
ConfigSettingsModelViewSet ConfigSettingsModelViewSet, SaveFileModelViewSet
router = DefaultRouter() router = DefaultRouter()
router.register(r'dict/type', DictDataModelViewSet) router.register(r'dict/type', DictDataModelViewSet)
router.register(r'dict/data', DictDetailsModelViewSet) router.register(r'dict/data', DictDetailsModelViewSet)
router.register(r'config', ConfigSettingsModelViewSet) router.register(r'config', ConfigSettingsModelViewSet)
router.register(r'savefile', SaveFileModelViewSet)
urlpatterns = [ urlpatterns = [
re_path('dict/get/type/(?P<pk>.*)/', DictDetailsModelViewSet.as_view({'get': 'dict_details_list'})), re_path('dict/get/type/(?P<pk>.*)/', DictDetailsModelViewSet.as_view({'get': 'dict_details_list'})),
re_path('config/configKey/(?P<pk>.*)/', ConfigSettingsModelViewSet.as_view({'get': 'get_config_key'})), re_path('config/configKey/(?P<pk>.*)/', ConfigSettingsModelViewSet.as_view({'get': 'get_config_key'})),
# 下载文件
re_path('savefile/(?P<pk>.*)/', SaveFileModelViewSet.as_view({'get': 'download_file'})),
] ]
urlpatterns += router.urls urlpatterns += router.urls

View File

@ -1,11 +1,15 @@
import os
from django.conf import settings
from django.http import HttpResponse
from rest_framework.request import Request from rest_framework.request import Request
from apps.op_drf.viewsets import CustomModelViewSet from apps.op_drf.viewsets import CustomModelViewSet
from apps.system.filters import DictDetailsFilter, DictDataFilter, ConfigSettingsFilter from apps.system.filters import DictDetailsFilter, DictDataFilter, ConfigSettingsFilter
from apps.system.models import DictData, DictDetails, ConfigSettings from apps.system.models import DictData, DictDetails, ConfigSettings, SaveFile
from apps.system.serializers import DictDataSerializer, DictDataCreateUpdateSerializer, DictDetailsSerializer, \ from apps.system.serializers import DictDataSerializer, DictDataCreateUpdateSerializer, DictDetailsSerializer, \
DictDetailsCreateUpdateSerializer, DictDetailsListSerializer, ConfigSettingsSerializer, \ DictDetailsCreateUpdateSerializer, DictDetailsListSerializer, ConfigSettingsSerializer, \
ConfigSettingsCreateUpdateSerializer ConfigSettingsCreateUpdateSerializer, SaveFileSerializer, SaveFileCreateUpdateSerializer
from utils.response import SuccessResponse from utils.response import SuccessResponse
@ -84,3 +88,32 @@ class ConfigSettingsModelViewSet(CustomModelViewSet):
# if hasattr(self, 'handle_logging'): # if hasattr(self, 'handle_logging'):
# self.handle_logging(request, *args, **kwargs) # self.handle_logging(request, *args, **kwargs)
return SuccessResponse(msg=queryset.configValue if queryset else '') return SuccessResponse(msg=queryset.configValue if queryset else '')
class SaveFileModelViewSet(CustomModelViewSet):
"""
参数设置 模型的CRUD视图
"""
queryset = SaveFile.objects.all()
serializer_class = SaveFileSerializer
create_serializer_class = SaveFileCreateUpdateSerializer
update_serializer_class = SaveFileCreateUpdateSerializer
# filter_class = ConfigSettingsFilter
search_fields = ('configName',)
ordering = 'id' # 默认排序
def download_file(self, request: Request, *args, **kwargs):
"""
下载文件
:param request:
:param args:
:param kwargs:
:return:
"""
instance = self.get_object()
file_path = os.path.join(settings.MEDIA_ROOT,str(instance.file))
with open(file_path, "rb") as f:
res = HttpResponse(f)
res["Content-Type"] = instance.type # 注意格式
res["Content-Disposition"] = 'filename="{}"'.format(instance.name)
return res

View File

@ -8,6 +8,6 @@ def getSql(filename):
:return: :return:
""" """
pwd = os.path.join(os.getcwd(), 'scripts', filename) pwd = os.path.join(os.getcwd(), 'scripts', filename)
with open(pwd) as fp: with open(pwd,'rb') as fp:
content = fp.read() content = fp.read().decode('utf8')
return [ele for ele in content.split('\n') if not ele.startswith('--') and ele] return [ele for ele in content.split('\n') if not ele.startswith('--') and ele]

View File

@ -0,0 +1,36 @@
import request from '@/utils/request'
// 查询文件列表
export function listSaveFile(query) {
return request({
url: '/system/savefile/',
method: 'get',
params: query
})
}
// 新增文件
export function addSaveFile(data) {
return request({
url: '/system/savefile/',
method: 'post',
data: data
})
}
// 删除文件
export function delSaveFile(menuId) {
return request({
url: '/system/savefile/' + menuId + '/',
method: 'delete'
})
}
// 清理废弃文件
export function clearSaveFile() {
return request({
url: '/system/clearsavefile/',
method: 'get'
})
}

View File

@ -61,9 +61,10 @@ export default {
default: true default: true
} }
}, },
name:'FileUpload',
data() { data() {
return { return {
uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload", // uploadFileUrl: process.env.VUE_APP_BASE_API + "/system/savefile/", //
headers: { headers: {
Authorization: "Bearer " + getToken(), Authorization: "Bearer " + getToken(),
}, },
@ -99,7 +100,7 @@ export default {
// //
handleBeforeUpload(file) { handleBeforeUpload(file) {
// //
if (this.fileType) { if (this.fileType && this.fileType[0] !== 'ALL') {
let fileExtension = ""; let fileExtension = "";
if (file.name.lastIndexOf(".") > -1) { if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1); fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);

View File

@ -179,8 +179,8 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" v-if="form.menuType != '2'"> <el-col :span="12" v-if="form.menuType != '2'">
<el-form-item :label="form.isFrame !== '0'?'组件路径':'跳转路由'" prop="component_path"> <el-form-item label="组件路径" prop="component_path">
<el-input v-model="form.component_path" :placeholder="form.isFrame !== '0'?'请输入前端组件路径':'请输入前端跳转路由'"/> <el-input v-model="form.component_path" placeholder="请输入前端组件路径" @change="ComponentPathChange"/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@ -325,6 +325,10 @@
let res = this.form.interface_path + ":" + this.form.interface_method let res = this.form.interface_path + ":" + this.form.interface_method
this.form.perms = res.toLocaleLowerCase().replace(/(\/)/g,':').replace(/(::)/g,':').replace(/(^:)|(:$)/g, "") this.form.perms = res.toLocaleLowerCase().replace(/(\/)/g,':').replace(/(::)/g,':').replace(/(^:)|(:$)/g, "")
}, },
/** 组件路径变化,替换斜杠开头 */
ComponentPathChange(){
this.form.component_path = this.form.component_path.replace(/(^\/)/g,'')
},
/** 查询菜单列表 */ /** 查询菜单列表 */
getList() { getList() {
this.loading = true; this.loading = true;

View File

@ -405,7 +405,7 @@ export default {
// //
headers: { Authorization: "Bearer " + getToken() }, headers: { Authorization: "Bearer " + getToken() },
// //
url: process.env.VUE_APP_BASE_API + "/permission/user/importData" url: process.env.VUE_APP_BASE_API + "/system/savefile/"
}, },
// //
queryParams: { queryParams: {

View File

@ -0,0 +1,277 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="文件名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入文件名称"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="文件类型" prop="type">
<el-input
v-model="queryParams.type"
placeholder="请输入文件类型(待完善)"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-upload"
size="mini"
@click="handleAdd"
v-hasPermi="['system:config:add']"
>文件上传
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:config:remove']"
>批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleClear"
v-hasPermi="['system:post:export']"
>清理废弃文件
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="fileList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="文件编号" width="90" align="center" prop="id"/>
<el-table-column label="文件名称" width="200" align="center" prop="name" :show-overflow-tooltip="true"/>
<el-table-column label="文件类型" width="150" align="center" prop="type"/>
<el-table-column label="文件大小" width="90" align="center" prop="size"/>
<el-table-column label="存储位置" align="center" prop="address"/>
<el-table-column label="文件本地地址" align="center" prop="file" :show-overflow-tooltip="true"/>
<el-table-column label="OSS地址" align="center" prop="oss_url" :show-overflow-tooltip="true"/>
<el-table-column label="创建时间" align="center" prop="create_datetime" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.create_datetime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="130">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-download"
@click="handleDownload(scope.row)"
v-hasPermi="['system:post:edit']"
>下载
</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:post:remove']"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 对话框 -->
<el-dialog :title="title" :visible.sync="open" width="400px" append-to-body @close="submitForm">
<FileUpload ref="saveFile" :file-type="['ALL']" @input="submitForm"></FileUpload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {clearSaveFile, listSaveFile} from "@/api/system/savefile";
import {delSaveFile} from "../../../api/system/savefile";
import FileUpload from "../../../components/FileUpload/index";
/**
* 保存
* @param {Blob} blob
* @param {String} filename 想要保存的文件名称
*/
function saveAs(blob, filename) {
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, filename);
} else {
var link = document.createElement('a');
var body = document.querySelector('body');
link.href = window.URL.createObjectURL(blob);
link.download = filename;
// fix Firefox
link.style.display = 'none';
body.appendChild(link);
link.click();
body.removeChild(link);
window.URL.revokeObjectURL(link.href);
}
;
}
/**
* 获取 blob
* @param {String} url 目标文件地址
* @return {cb}
*/
function getBlob(url, cb) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function () {
if (xhr.status === 200) {
cb(xhr.response);
}
};
xhr.send();
}
export default {
name: "Savefile",
components: {FileUpload},
data() {
return {
//
loading: true,
//
showSearch: true,
//
single: true,
//
multiple: true,
//
title: "",
//
open: false,
//
queryParams: {
name: undefined,
visible: undefined,
},
//
total: 0,
//
fileList: [],
}
},
created() {
this.getList();
},
mounted() {
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
listSaveFile(this.queryParams).then(response => {
this.fileList = response.data.results;
this.total = response.data.count;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 上传按钮操作 */
handleAdd(row) {
this.open = true;
this.title = "上传文件";
},
/** 文件下载 **/
handleDownload(row) {
getBlob(process.env.VUE_APP_BASE_API + row.file_url, function (blob) {
saveAs(blob, row.name);
});
},
/** 删除按钮操作 */
handleDelete(row) {
this.$confirm('是否确认删除名称为"' + row.name + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function () {
return delSaveFile(row.id);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 清理废弃文件 */
handleClear() {
this.$confirm('此项操作会把所有数据库中不存在的文件删除包括OSS中的文件是否确认要清理废弃文件?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function () {
return clearSaveFile();
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
/** 完成按钮 */
submitForm: function () {
this.getList();
this.open = false;
this.$refs.saveFile.fileList = []
},
}
}
</script>
<style scoped>
</style>