feat: 新增用户管理

This commit is contained in:
valarchie 2023-07-24 19:35:26 +08:00
parent 754447baa6
commit d076b403c1
10 changed files with 1108 additions and 0 deletions

122
src/api/system/user.ts Normal file
View File

@ -0,0 +1,122 @@
import { http } from "@/utils/http";
export interface UserQuery extends BasePageQuery {
deptId?: number;
phoneNumber?: string;
status?: number;
userId?: number;
username?: string;
}
/**
* UserDTO
*/
export interface UserDTO {
avatar?: string;
createTime?: Date;
creatorId?: number;
creatorName?: string;
deptId?: number;
deptName?: string;
email?: string;
loginDate?: Date;
loginIp?: string;
nickname?: string;
phoneNumber?: string;
postId?: number;
remark?: string;
roleId?: number;
roleName?: string;
sex?: number;
status?: number;
updaterId?: number;
updaterName?: string;
updateTime?: Date;
userId?: number;
username?: string;
userType?: number;
}
/**
* AddUserCommand
*/
export interface UserRequest {
userId: number;
avatar?: string;
deptId?: number;
email?: string;
nickname?: string;
phoneNumber?: string;
password: string;
postId?: number;
remark?: string;
roleId?: number;
sex?: number;
status?: number;
username?: string;
}
/**
*
*/
export interface PasswordRequest {
userId: number;
password: string;
}
/** 获取用户列表 */
export const getUserListApi = (params?: UserQuery) => {
return http.request<ResponseData<PageDTO<UserDTO>>>("get", "/system/users", {
params
});
};
/** 新增用户 */
export const addUserApi = (data?: UserRequest) => {
return http.request<ResponseData<void>>("post", "/system/users", {
data
});
};
/** 编辑用户 */
export const updateUserApi = (userId: number, data?: UserRequest) => {
return http.request<ResponseData<void>>("put", `/system/users/${userId}`, {
data
});
};
/** 更改用户密码 */
export const updateUserPasswordApi = (data?: PasswordRequest) => {
return http.request<ResponseData<void>>(
"put",
`/system/users/${data.userId}/password`,
{
data
}
);
};
/** 删除用户 */
export const deleteUserApi = (userId: number) => {
return http.request<ResponseData<void>>("delete", `/system/users/${userId}`);
};
/** 修改用户状态 */
export const updateUserStatusApi = (userId: number, status: number) => {
return http.request<ResponseData<PageDTO<UserDTO>>>(
"put",
`/system/users/${userId}/status`,
{
data: {
status: status
}
}
);
};
/** 批量导出用户 */
export const exportUserExcelApi = (params: UserQuery, fileName: string) => {
return http.download("/system/users/excel", fileName, {
params
});
};

View File

@ -78,6 +78,11 @@ export function useNav() {
router.push("/login"); router.push("/login");
} }
/** 个人中心 */
function userProfile() {
router.push("/system/user/profile");
}
function backTopMenu() { function backTopMenu() {
router.push(getTopMenu()?.path); router.push(getTopMenu()?.path);
} }
@ -121,6 +126,7 @@ export function useNav() {
device, device,
layout, layout,
logout, logout,
userProfile,
routers, routers,
$storage, $storage,
backTopMenu, backTopMenu,

View File

@ -0,0 +1,209 @@
<script setup lang="ts">
import { ref } from "vue";
import ReCol from "@/components/ReCol";
import { formRules } from "./rule";
import { UserRequest } from "@/api/system/user";
import { PostPageResponse } from "@/api/system/post";
import { RoleDTO } from "@/api/system/role";
import { useUserStoreHook } from "@/store/modules/user";
interface FormProps {
formInline: UserRequest;
deptOptions: any[];
postOptions: PostPageResponse[];
roleOptions: RoleDTO[];
}
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
userId: 0,
username: "",
nickname: "",
deptId: 0,
phone: "",
email: "",
password: "",
sex: 0,
status: 1,
postId: 0,
roleId: 0,
remark: ""
}),
deptOptions: () => [],
postOptions: () => [],
roleOptions: () => []
});
const newFormInline = ref(props.formInline);
const deptOptions = ref(props.deptOptions);
const roleOptions = ref(props.roleOptions);
const postOptions = ref(props.postOptions);
const formRuleRef = ref();
function getFormRuleRef() {
return formRuleRef.value;
}
defineExpose({ getFormRuleRef });
</script>
<template>
<el-form
ref="formRuleRef"
:model="newFormInline"
:rules="formRules"
label-width="82px"
>
<el-row :gutter="30">
<re-col :value="12">
<el-form-item label="用户名" prop="username">
<el-input
v-model="newFormInline.username"
clearable
placeholder="请输入用户名"
/>
</el-form-item>
</re-col>
<re-col :value="12">
<el-form-item label="部门">
<el-tree-select
class="w-full"
v-model="newFormInline.deptId"
:data="deptOptions"
:show-all-levels="false"
value-key="id"
:props="{
value: 'id',
label: 'deptName',
emitPath: false,
checkStrictly: true
}"
clearable
placeholder="请选择部门"
/>
</el-form-item>
</re-col>
<re-col :value="12">
<el-form-item label="手机号码" prop="phoneNumber">
<el-input
v-model="newFormInline.phoneNumber"
clearable
placeholder="请输入手机号码"
/>
</el-form-item>
</re-col>
<re-col :value="12">
<el-form-item label="邮箱" prop="email">
<el-input
v-model="newFormInline.email"
clearable
placeholder="请输入邮箱"
/>
</el-form-item>
</re-col>
<re-col :value="12">
<el-form-item label="昵称" prop="nickname">
<el-input
v-model="newFormInline.nickname"
clearable
placeholder="请输入昵称"
/>
</el-form-item>
</re-col>
<re-col :value="12">
<el-form-item label="性别" prop="sex">
<el-select
class="w-full"
v-model="newFormInline.sex"
placeholder="请选择性别"
clearable
>
<el-option
v-for="dict in useUserStoreHook().dictionaryList['sysUser.sex']"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</re-col>
<re-col :value="12">
<el-form-item label="岗位" prop="postId">
<el-select
class="w-full"
v-model="newFormInline.postId"
placeholder="请选择岗位"
clearable
>
<el-option
v-for="item in postOptions"
:key="item.postId"
:label="item.postName"
:value="item.postId"
:disabled="item.status == 0"
/>
</el-select>
</el-form-item>
</re-col>
<re-col :value="12">
<el-form-item label="角色" prop="roleId">
<el-select
class="w-full"
v-model="newFormInline.roleId"
placeholder="请选择角色"
clearable
>
<el-option
v-for="item in roleOptions"
:key="item.roleId"
:label="item.roleName"
:value="item.roleId"
:disabled="item.status == 0"
/>
</el-select>
</el-form-item>
</re-col>
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="newFormInline.status">
<el-radio
v-for="item in useUserStoreHook().dictionaryList['common.status']"
:key="item.value"
:label="item.value"
>{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</re-col>
<re-col :value="12" v-if="newFormInline.password !== undefined">
<el-form-item label="密码" prop="password">
<el-input
v-model="newFormInline.password"
clearable
placeholder="请输入密码"
/>
</el-form-item>
</re-col>
<re-col :value="24">
<el-form-item label="备注" prop="remark">
<el-input
v-model="newFormInline.remark"
clearable
placeholder="请输入备注内容"
rows="6"
type="textarea"
/>
</el-form-item>
</re-col>
</el-row>
</el-form>
</template>

View File

@ -0,0 +1,389 @@
import dayjs from "dayjs";
import { message } from "@/utils/message";
import {
UserQuery,
getUserListApi,
addUserApi,
updateUserStatusApi,
updateUserApi,
exportUserExcelApi,
UserRequest,
deleteUserApi,
PasswordRequest,
updateUserPasswordApi
} from "@/api/system/user";
import editForm from "./form.vue";
import passwordForm from "./passwordForm.vue";
import uploadForm from "./uploadForm.vue";
import { ElMessageBox } from "element-plus";
import { type PaginationProps } from "@pureadmin/table";
import { reactive, ref, computed, onMounted, toRaw, h } from "vue";
import { CommonUtils } from "@/utils/common";
import { addDialog } from "@/components/ReDialog";
import { handleTree, setDisabledForTreeOptions } from "@/utils/tree";
import { getDeptListApi } from "@/api/system/dept";
import { getPostListApi } from "@/api/system/post";
import { getRoleListApi } from "@/api/system/role";
export function useHook() {
const searchFormParams = reactive<UserQuery>({
deptId: null,
phoneNumber: undefined,
status: undefined,
username: undefined,
timeRangeColumn: "createTime"
});
const formRef = ref();
const timeRange = ref<[string, string]>();
const dataList = ref([]);
const pageLoading = ref(true);
const switchLoadMap = ref({});
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 10,
currentPage: 1,
background: true
});
const deptTreeList = ref([]);
const postOptions = ref([]);
const roleOptions = ref([]);
const columns: TableColumnList = [
{
label: "用户编号",
prop: "userId",
width: 90,
fixed: "left"
},
{
label: "用户名",
prop: "username",
minWidth: 130
},
{
label: "昵称",
prop: "nickname",
minWidth: 130
},
{
label: "性别",
prop: "sex",
minWidth: 90,
cellRenderer: ({ row, props }) => (
<el-tag
size={props.size}
type={row.sex === 1 ? "" : "danger"}
effect="plain"
>
{row.sex === 1 ? "男" : "女"}
</el-tag>
)
},
{
label: "部门ID",
prop: "deptId",
minWidth: 130,
hide: true
},
{
label: "部门",
prop: "deptName",
minWidth: 130
},
{
label: "手机号码",
prop: "phoneNumber",
minWidth: 90
},
{
label: "角色ID",
prop: "roleId",
minWidth: 90,
hide: true
},
{
label: "角色",
prop: "roleName",
minWidth: 90
},
{
label: "状态",
prop: "status",
minWidth: 90,
cellRenderer: scope => (
<el-switch
size={scope.props.size === "small" ? "small" : "default"}
loading={switchLoadMap.value[scope.index]?.loading}
v-model={scope.row.status}
active-value={1}
inactive-value={0}
active-text="正常"
inactive-text="停用"
inline-prompt
onChange={() => onChange(scope as any)}
/>
)
},
{
label: "创建时间",
minWidth: 70,
prop: "createTime",
formatter: ({ createTime }) =>
dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
},
{
label: "操作",
fixed: "right",
width: 180,
slot: "operation"
}
];
const buttonClass = computed(() => {
return [
"!h-[20px]",
"reset-margin",
"!text-gray-500",
"dark:!text-white",
"dark:hover:!text-primary"
];
});
function onChange({ row, index }) {
ElMessageBox.confirm(
`确认要<strong>${
row.status === 0 ? "停用" : "启用"
}</strong><strong style='color:var(--el-color-primary)'>${
row.username
}</strong>?`,
"系统提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
dangerouslyUseHTMLString: true,
draggable: true
}
)
.then(async () => {
switchLoading(index, true);
await updateUserStatusApi(row.userId, row.status).finally(() => {
switchLoading(index, false);
});
message("已成功修改用户状态", {
type: "success"
});
})
.catch(() => {
message("取消操作", {
type: "info"
});
// 如果取消的话 恢复更改前的状态
row.status === 0 ? (row.status = 1) : (row.status = 0);
});
}
function switchLoading(index: number, loading: boolean) {
switchLoadMap.value[index] = Object.assign({}, switchLoadMap.value[index], {
loading: loading
});
}
async function exportAllExcel() {
CommonUtils.fillPaginationParams(searchFormParams, pagination);
exportUserExcelApi(toRaw(searchFormParams), "用户列表.xls");
}
async function handleAdd(row, done) {
await addUserApi(row as UserRequest).then(() => {
message(`您新增了用户${row.username}的这条数据`, {
type: "success"
});
// 关闭弹框
done();
// 刷新列表
getList();
});
}
async function handleUpdate(row, done) {
await updateUserApi(row.userId, row as UserRequest).then(() => {
message(`您修改了用户${row.username}的这条数据`, {
type: "success"
});
// 关闭弹框
done();
// 刷新列表
getList();
});
}
async function handleDelete(row) {
await deleteUserApi(row.userId).then(() => {
message(`您删除了用户${row.username}的这条数据`, { type: "success" });
// 刷新列表
getList();
});
}
async function handleResetPassword(row, request, done) {
await updateUserPasswordApi(request).then(() => {
message(`您修改了用户${row.username}的密码`, { type: "success" });
// 刷新列表
done();
getList();
});
}
async function onSearch() {
// 点击搜索的时候 需要重置分页
pagination.currentPage = 1;
getList();
}
async function openDialog(title = "新增", row?: UserRequest) {
// TODO 如果是编辑的话 通过获取用户详情接口来获取数据
addDialog({
title: `${title}用户`,
props: {
formInline: {
userId: row?.userId ?? 0,
username: row?.username ?? "",
nickname: row?.nickname ?? "",
deptId: row?.deptId ?? undefined,
phoneNumber: row?.phoneNumber ?? "",
email: row?.email ?? "",
password: title == "新增" ? "" : undefined,
sex: row?.sex ?? undefined,
status: row?.status ?? undefined,
postId: row?.postId ?? undefined,
roleId: row?.roleId ?? undefined,
remark: row?.remark ?? ""
},
deptOptions: deptTreeList,
postOptions: postOptions,
roleOptions: roleOptions
},
width: "40%",
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(editForm, { ref: formRef }),
beforeSure: (done, { options }) => {
const formRuleRef = formRef.value.getFormRuleRef();
const curData = options.props.formInline as UserRequest;
formRuleRef.validate(valid => {
if (valid) {
// 表单规则校验通过
if (title === "新增") {
handleAdd(curData, done);
} else {
handleUpdate(curData, done);
}
}
});
}
});
}
async function openResetPasswordDialog(row) {
const passwordFormRef = ref();
addDialog({
title: `重置密码`,
props: {
formInline: {
userId: row.userId ?? 0,
password: ""
}
},
width: "30%",
closeOnClickModal: false,
contentRenderer: () => h(passwordForm, { ref: passwordFormRef }),
beforeSure: (done, { options }) => {
const formRef = passwordFormRef.value.getFormRuleRef();
const curData = options.props.formInline as PasswordRequest;
formRef.validate(valid => {
if (valid) {
handleResetPassword(row, curData, done);
}
});
}
});
}
async function openUploadDialog() {
const uploadFormRef = ref();
addDialog({
title: `导入用户`,
props: {},
width: "30%",
closeOnClickModal: false,
contentRenderer: () => h(uploadForm, { ref: uploadFormRef }),
beforeSure: done => {
console.log("上传文件");
uploadFormRef.value.getFormRef().submit();
done();
getList();
}
});
}
async function getList() {
CommonUtils.fillPaginationParams(searchFormParams, pagination);
CommonUtils.fillTimeRangeParams(searchFormParams, timeRange.value);
pageLoading.value = true;
const { data } = await getUserListApi(toRaw(searchFormParams)).finally(
() => {
pageLoading.value = false;
}
);
dataList.value = data.rows;
pagination.total = data.total;
}
const resetForm = formEl => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
onMounted(async () => {
onSearch();
const deptResponse = getDeptListApi();
deptTreeList.value = await setDisabledForTreeOptions(
handleTree(deptResponse.data),
"status"
);
const postResponse = await getPostListApi({});
postOptions.value = postResponse.data.rows;
const roleResponse = await getRoleListApi({});
roleOptions.value = roleResponse.data.rows;
});
return {
searchFormParams,
pageLoading,
columns,
dataList,
pagination,
buttonClass,
onSearch,
openDialog,
exportAllExcel,
resetForm,
handleUpdate,
getList,
handleDelete,
openResetPasswordDialog,
openUploadDialog
};
}

View File

@ -0,0 +1,48 @@
<script setup lang="ts">
import { ref } from "vue";
import ReCol from "@/components/ReCol";
import { formRules } from "./rule";
import { PasswordRequest } from "@/api/system/user";
interface FormProps {
formInline: PasswordRequest;
}
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
userId: 0,
password: ""
})
});
const newFormInline = ref(props.formInline);
const formRuleRef = ref();
function getFormRuleRef() {
return formRuleRef.value;
}
defineExpose({ getFormRuleRef });
</script>
<template>
<el-form
ref="formRuleRef"
:model="newFormInline"
:rules="formRules"
label-width="82px"
>
<el-row :gutter="30">
<re-col :value="24">
<el-form-item label="新密码" prop="password">
<el-input
v-model="newFormInline.password"
clearable
placeholder="请输入新密码"
/>
</el-form-item>
</re-col>
</el-row>
</el-form>
</template>

View File

@ -0,0 +1,37 @@
import { reactive } from "vue";
import type { FormRules } from "element-plus";
import { isPhone, isEmail } from "@pureadmin/utils";
/** 自定义表单规则校验 */
export const formRules = reactive(<FormRules>{
name: [{ required: true, message: "部门名称为必填项", trigger: "blur" }],
phone: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback();
} else if (!isPhone(value)) {
callback(new Error("请输入正确的手机号码格式"));
} else {
callback();
}
},
trigger: "blur"
// trigger: "click" // 如果想在点击确定按钮时触发这个校验trigger 设置成 click 即可
}
],
email: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback();
} else if (!isEmail(value)) {
callback(new Error("请输入正确的邮箱格式"));
} else {
callback();
}
},
trigger: "blur"
}
]
});

View File

@ -0,0 +1 @@
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4h9Z"/></svg>

After

Width:  |  Height:  |  Size: 163 B

View File

@ -0,0 +1 @@
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 2H2v20h2v-9h14.17l-5.5 5.5l1.41 1.42L22 12l-7.92-7.92l-1.41 1.42l5.5 5.5H4V2Z"/></svg>

After

Width:  |  Height:  |  Size: 166 B

View File

@ -0,0 +1,212 @@
<script setup lang="ts">
import { handleTree } from "@/utils/tree";
import { getDeptListApi } from "@/api/system/dept";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { ref, computed, watch, onMounted, getCurrentInstance } from "vue";
import Dept from "@iconify-icons/ri/git-branch-line";
import Reset from "@iconify-icons/ri/restart-line";
import Search from "@iconify-icons/ep/search";
import More2Fill from "@iconify-icons/ri/more-2-fill";
import OfficeBuilding from "@iconify-icons/ep/office-building";
import LocationCompany from "@iconify-icons/ep/add-location";
import ExpandIcon from "./svg/expand.svg?component";
import UnExpandIcon from "./svg/unexpand.svg?component";
// TODO SideBar TreeSelect
interface Tree {
id: number;
deptName: string;
highlight?: boolean;
children?: Tree[];
}
defineProps({
modelValue: {
type: Number,
required: true
}
});
const treeRef = ref();
const treeData = ref([]);
const isExpand = ref(true);
const searchValue = ref("");
const highlightMap = ref({});
const { proxy } = getCurrentInstance();
const defaultProps = {
children: "children",
label: "deptName"
};
const buttonClass = computed(() => {
return [
"!h-[20px]",
"reset-margin",
"!text-gray-500",
"dark:!text-white",
"dark:hover:!text-primary"
];
});
const filterNode = (value: string, data: Tree) => {
if (!value) return true;
return data.deptName.includes(value);
};
function nodeClick(value) {
console.log(value);
const nodeId = value.$treeNodeId;
console.log(nodeId);
highlightMap.value[nodeId] = highlightMap.value[nodeId]?.highlight
? Object.assign({ id: nodeId }, highlightMap.value[nodeId], {
highlight: false
})
: Object.assign({ id: nodeId }, highlightMap.value[nodeId], {
highlight: true
});
Object.values(highlightMap.value).forEach((v: Tree) => {
if (v.id !== nodeId) {
v.highlight = false;
}
});
proxy.$emit("update:modelValue", value.id);
}
function toggleRowExpansionAll(status) {
isExpand.value = status;
const nodes = (proxy.$refs["treeRef"] as any).store._getAllNodes();
for (let i = 0; i < nodes.length; i++) {
nodes[i].expanded = status;
}
}
/** 重置状态(选中状态、搜索框值、树初始化) */
function onReset() {
highlightMap.value = {};
searchValue.value = "";
toggleRowExpansionAll(true);
}
watch(searchValue, val => {
treeRef.value!.filter(val);
});
onMounted(async () => {
const { data } = await getDeptListApi();
treeData.value = handleTree(data);
});
</script>
<template>
<div
class="h-full bg-bg_color overflow-auto"
:style="{ minHeight: `calc(100vh - 133px)` }"
>
<div class="flex items-center h-[56px]">
<p class="flex-1 ml-2 font-bold text-base truncate" title="部门列表">
部门列表
</p>
<el-input
style="flex: 2"
size="default"
v-model="searchValue"
placeholder="请输入部门名称"
clearable
>
<template #suffix>
<el-icon class="el-input__icon">
<IconifyIconOffline
v-show="searchValue.length === 0"
:icon="Search"
/>
</el-icon>
</template>
</el-input>
<el-dropdown :hide-on-click="false">
<IconifyIconOffline
class="w-[38px] cursor-pointer"
width="20px"
:icon="More2Fill"
/>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<el-button
:class="buttonClass"
link
type="primary"
:icon="useRenderIcon(isExpand ? ExpandIcon : UnExpandIcon)"
@click="toggleRowExpansionAll(isExpand ? false : true)"
>
{{ isExpand ? "折叠全部" : "展开全部" }}
</el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button
:class="buttonClass"
link
type="primary"
:icon="useRenderIcon(Reset)"
@click="onReset"
>
重置状态
</el-button>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<el-divider />
<el-tree
ref="treeRef"
:data="treeData"
node-key="id"
size="default"
:props="defaultProps"
default-expand-all
:expand-on-click-node="false"
:filter-node-method="filterNode"
@node-click="nodeClick"
>
<template #default="{ node, data }">
<span
:class="[
'text-base',
'flex',
'items-center',
'tracking-wider',
'gap-2',
'select-none',
searchValue.trim().length > 0 &&
node.label.includes(searchValue) &&
'text-red-500',
highlightMap[node.id]?.highlight ? 'dark:text-primary' : ''
]"
:style="{
background: highlightMap[node.id]?.highlight
? 'var(--el-color-primary-light-7)'
: 'transparent'
}"
>
<IconifyIconOffline
:icon="
data.parentId === 0
? OfficeBuilding
: data.type === 2
? LocationCompany
: Dept
"
/>
{{ node.label }}
</span>
</template>
</el-tree>
</div>
</template>
<style lang="scss" scoped>
:deep(.el-divider) {
margin: 0;
}
</style>

View File

@ -0,0 +1,83 @@
<script setup lang="ts">
import { reactive, ref } from "vue";
import { getToken } from "@/utils/auth";
import { http } from "@/utils/http";
import { message } from "@/utils/message";
import { useHook } from "./hook";
const { getList } = useHook();
/** * 用户导入参数 */
const upload = reactive({
//
open: false,
//
title: "",
//
loading: false,
//
headers: { Authorization: `Bearer ${getToken().token}` },
//
url: `${import.meta.env.VITE_APP_BASE_API}/system/users/excel`
});
/** 下载模板操作 */
function downloadTemplate() {
http.download(
"system/users/excelTemplate",
`user_template_${new Date().getTime()}.xls`
);
}
/** 文件上传中处理 */
const handleFileUploadProgress = () => {
upload.loading = true;
};
/** 文件上传成功处理 */
const handleFileSuccess = () => {
upload.open = false;
upload.loading = false;
formRef.value.clearFiles();
message("导入成功", { type: "success" });
getList();
};
const formRef = ref();
function getFormRef() {
return formRef.value;
}
defineExpose({ getFormRef });
</script>
<template>
<el-upload
ref="formRef"
:limit="1"
accept=".xlsx,.xls"
:headers="upload.headers"
:action="upload.url"
:disabled="upload.loading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<span>仅允许导入xlsxlsx格式文件</span>
<el-link
type="primary"
:underline="false"
style="font-size: 12px; vertical-align: baseline"
@click="downloadTemplate"
>下载模板</el-link
>
</div>
</template>
</el-upload>
</template>