feat: 🎉 导入导出
用户管理加入导入导出
This commit is contained in:
@@ -196,10 +196,11 @@ class ExportUserProfileSerializer(CustomModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class UserProfileImportSerializer(CustomModelSerializer):
|
class UserProfileImportSerializer(CustomModelSerializer):
|
||||||
|
password = serializers.CharField(read_only=True, required=False)
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
data = super().save(**kwargs)
|
data = super().save(**kwargs)
|
||||||
password = hashlib.new(
|
password = hashlib.new(
|
||||||
"md5", str(self.initial_data.get("password", "")).encode(encoding="UTF-8")
|
"md5", str(self.initial_data.get("password", "admin123456")).encode(encoding="UTF-8")
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
data.set_password(password)
|
data.set_password(password)
|
||||||
data.save()
|
data.save()
|
||||||
@@ -264,7 +265,6 @@ class UserViewSet(CustomModelViewSet):
|
|||||||
"data": {"启用": True, "禁用": False},
|
"data": {"启用": True, "禁用": False},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"password": "登录密码",
|
|
||||||
"dept": {"title": "部门", "choices": {"queryset": Dept.objects.filter(status=True), "values_name": "name"}},
|
"dept": {"title": "部门", "choices": {"queryset": Dept.objects.filter(status=True), "values_name": "name"}},
|
||||||
"role": {"title": "角色", "choices": {"queryset": Role.objects.filter(status=True), "values_name": "name"}},
|
"role": {"title": "角色", "choices": {"queryset": Role.objects.filter(status=True), "values_name": "name"}},
|
||||||
}
|
}
|
||||||
|
|||||||
146
web/src/components/importExcel/index.vue
Normal file
146
web/src/components/importExcel/index.vue
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<template>
|
||||||
|
<div style="display: inline-block">
|
||||||
|
<el-button size="default" type="success" @click="handleImport()">
|
||||||
|
<slot>导入</slot>
|
||||||
|
</el-button>
|
||||||
|
<el-dialog :title="props.upload.title" v-model="uploadShow" width="400px" append-to-body>
|
||||||
|
<div v-loading="loading">
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
:limit="1"
|
||||||
|
accept=".xlsx, .xls"
|
||||||
|
:headers="props.upload.headers"
|
||||||
|
:action="props.upload.url"
|
||||||
|
:disabled="isUploading"
|
||||||
|
:on-progress="handleFileUploadProgress"
|
||||||
|
:on-success="handleFileSuccess"
|
||||||
|
:auto-upload="false"
|
||||||
|
drag
|
||||||
|
>
|
||||||
|
<i class="el-icon-upload"/>
|
||||||
|
<div class="el-upload__text">
|
||||||
|
将文件拖到此处,或
|
||||||
|
<em>点击上传</em>
|
||||||
|
</div>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip" style="color:red">提示:仅允许导入“xls”或“xlsx”格式文件!</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
<div>
|
||||||
|
<el-button type="warning" style="font-size:14px;margin-top: 20px" @click="importTemplate">下载导入模板</el-button>
|
||||||
|
<el-button type="warning" style="font-size:14px;margin-top: 20px" @click="updateTemplate">批量更新模板</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button type="primary" :disabled="loading" @click="submitFileForm">确 定</el-button>
|
||||||
|
<el-button :disabled="loading" @click="uploadShow = false">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup name="importExcel">
|
||||||
|
import { request, downloadFile } from '/@/utils/service';
|
||||||
|
import {inject,ref} from "vue";
|
||||||
|
import { getBaseURL } from '/@/utils/baseUrl';
|
||||||
|
import { Session } from '/@/utils/storage';
|
||||||
|
import { ElMessageBox } from 'element-plus'
|
||||||
|
import type { Action } from 'element-plus'
|
||||||
|
const refreshView = inject('refreshView')
|
||||||
|
|
||||||
|
let props = defineProps({
|
||||||
|
upload: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
// 是否显示弹出层
|
||||||
|
open: true,
|
||||||
|
// 弹出层标题
|
||||||
|
title: '',
|
||||||
|
// 是否禁用上传
|
||||||
|
isUploading: false,
|
||||||
|
// 是否更新已经存在的用户数据
|
||||||
|
updateSupport: 0,
|
||||||
|
// 设置上传的请求头部
|
||||||
|
headers: { Authorization: 'JWT ' + Session.get('token') },
|
||||||
|
// 上传的地址
|
||||||
|
url: getBaseURL() + 'api/system/file/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
api: { // 导入接口地址
|
||||||
|
type: String,
|
||||||
|
default () {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let loading = ref(false)
|
||||||
|
const uploadRef = ref()
|
||||||
|
const uploadShow = ref(false)
|
||||||
|
const isUploading = ref(false)
|
||||||
|
/** 导入按钮操作 */
|
||||||
|
const handleImport = function () {
|
||||||
|
uploadShow.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 下载模板操作 */
|
||||||
|
const importTemplate=function () {
|
||||||
|
downloadFile({
|
||||||
|
url: props.api + 'import_data/',
|
||||||
|
params: {},
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/***
|
||||||
|
* 批量更新模板
|
||||||
|
*/
|
||||||
|
const updateTemplate=function () {
|
||||||
|
downloadFile({
|
||||||
|
url: props.api + 'update_template/',
|
||||||
|
params: {},
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 文件上传中处理
|
||||||
|
const handleFileUploadProgress=function (event:any, file:any, fileList:any) {
|
||||||
|
isUploading.value = true
|
||||||
|
}
|
||||||
|
// 文件上传成功处理
|
||||||
|
const handleFileSuccess=function (response:any, file:any, fileList:any) {
|
||||||
|
isUploading.value = false
|
||||||
|
loading.value = true
|
||||||
|
uploadRef.value.clearFiles()
|
||||||
|
// 是否更新已经存在的用户数据
|
||||||
|
return request({
|
||||||
|
url: props.api + 'import_data/',
|
||||||
|
method: 'post',
|
||||||
|
data: {
|
||||||
|
url: response.data.url
|
||||||
|
}
|
||||||
|
}).then((response:any) => {
|
||||||
|
loading.value = false
|
||||||
|
ElMessageBox.alert('导入成功', '导入完成', {
|
||||||
|
confirmButtonText: 'OK',
|
||||||
|
callback: (action: Action) => {
|
||||||
|
refreshView()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}).catch(()=>{
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
// 提交上传文件
|
||||||
|
const submitFileForm=function () {
|
||||||
|
uploadRef.value.submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -90,6 +90,9 @@ function createService() {
|
|||||||
return dataAxios;
|
return dataAxios;
|
||||||
}
|
}
|
||||||
return dataAxios;
|
return dataAxios;
|
||||||
|
case 4000:
|
||||||
|
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
|
||||||
|
return Promise.reject(dataAxios.msg);
|
||||||
default:
|
default:
|
||||||
// 不是正确的 code
|
// 不是正确的 code
|
||||||
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
|
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
|
||||||
@@ -187,3 +190,34 @@ export const request = createRequestFunction(service);
|
|||||||
// 用于模拟网络请求的实例和请求方法
|
// 用于模拟网络请求的实例和请求方法
|
||||||
export const serviceForMock = createService();
|
export const serviceForMock = createService();
|
||||||
export const requestForMock = createRequestFunction(serviceForMock);
|
export const requestForMock = createRequestFunction(serviceForMock);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件
|
||||||
|
* @param url
|
||||||
|
* @param params
|
||||||
|
* @param method
|
||||||
|
* @param filename
|
||||||
|
*/
|
||||||
|
export const downloadFile = function ({url,params,method,filename = '文件导出'}:any) {
|
||||||
|
request({
|
||||||
|
url: url,
|
||||||
|
method: method,
|
||||||
|
params: params,
|
||||||
|
responseType: 'blob'
|
||||||
|
// headers: {Accept: 'application/vnd.openxmlformats-officedocument'}
|
||||||
|
}).then((res:any) => {
|
||||||
|
const xlsxName = window.decodeURI(res.headers['content-disposition'].split('=')[1])
|
||||||
|
const fileName = xlsxName || `${filename}.xlsx`
|
||||||
|
if (res) {
|
||||||
|
const blob = new Blob([res.data], { type: 'charset=utf-8' })
|
||||||
|
const elink = document.createElement('a')
|
||||||
|
elink.download = fileName
|
||||||
|
elink.style.display = 'none'
|
||||||
|
elink.href = URL.createObjectURL(blob)
|
||||||
|
document.body.appendChild(elink)
|
||||||
|
elink.click()
|
||||||
|
URL.revokeObjectURL(elink.href) // 释放URL 对象0
|
||||||
|
document.body.removeChild(elink)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { request } from '/@/utils/service';
|
import { request,downloadFile } from '/@/utils/service';
|
||||||
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||||
|
|
||||||
export const apiPrefix = '/api/system/user/';
|
export const apiPrefix = '/api/system/user/';
|
||||||
@@ -48,3 +48,11 @@ export function DelObj(id: DelReq) {
|
|||||||
data: { id },
|
data: { id },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function exportData(params:any){
|
||||||
|
return downloadFile({
|
||||||
|
url: apiPrefix + 'export_data/',
|
||||||
|
params: params,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
return await api.AddObj(form);
|
return await api.AddObj(form);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportRequest = async (query: UserPageQuery) => {
|
||||||
|
return await api.exportData(query)
|
||||||
|
}
|
||||||
|
|
||||||
//权限判定
|
//权限判定
|
||||||
const hasPermissions:any = inject('$hasPermissions');
|
const hasPermissions:any = inject('$hasPermissions');
|
||||||
|
|
||||||
@@ -41,6 +45,13 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
add: {
|
add: {
|
||||||
show: hasPermissions('user:Create')
|
show: hasPermissions('user:Create')
|
||||||
// show:true
|
// show:true
|
||||||
|
},
|
||||||
|
export:{
|
||||||
|
text:"导出",//按钮文字
|
||||||
|
title:"导出",//鼠标停留显示的信息
|
||||||
|
click(){
|
||||||
|
return exportRequest(crudExpose.getSearchFormData())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,10 +30,15 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col xs="24" :sm="16" :md="18" :lg="20" :xl="20" class="p-1">
|
<el-col xs="24" :sm="16" :md="18" :lg="20" :xl="20" class="p-1">
|
||||||
<el-card :body-style="{ height: '100%' }">
|
<el-card :body-style="{ height: '100%' }">
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding"></fs-crud>
|
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||||
|
<template #actionbar-right>
|
||||||
|
<importExcel api="api/system/user/" >导入 </importExcel>
|
||||||
|
</template>
|
||||||
|
</fs-crud>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
</fs-page>
|
</fs-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -42,11 +47,9 @@ import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
|||||||
import { createCrudOptions } from './crud';
|
import { createCrudOptions } from './crud';
|
||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
import { ElTree } from 'element-plus';
|
import { ElTree } from 'element-plus';
|
||||||
import { ref, onMounted, watch, toRaw, defineAsyncComponent } from 'vue';
|
import { ref, onMounted, watch, toRaw } from 'vue';
|
||||||
import XEUtils from 'xe-utils';
|
import XEUtils from 'xe-utils';
|
||||||
import { errorMessage, successMessage } from '../../../utils/message';
|
import importExcel from '/@/components/importExcel/index.vue'
|
||||||
import { GetDept } from './api';
|
|
||||||
import { dictionary } from '/@/utils/dictionary';
|
|
||||||
|
|
||||||
interface Tree {
|
interface Tree {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user