123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660 |
- <!-- 用户管理 -->
- <template>
- <div class="app-container">
- <el-row :gutter="20">
- <!-- 部门树 -->
- <el-col :lg="4" :xs="24" class="mb-[12px]">
- <dept-tree v-model="queryParams.deptId" @node-click="handleQuery" />
- </el-col>
- <!-- 用户列表 -->
- <el-col :lg="20" :xs="24">
- <div class="search-container">
- <el-form ref="queryFormRef" :model="queryParams" :inline="true">
- <el-form-item label="关键字" prop="keywords">
- <el-input
- v-model="queryParams.keywords"
- placeholder="用户名/昵称/手机号"
- clearable
- style="width: 200px"
- @keyup.enter="handleQuery"
- />
- </el-form-item>
- <el-form-item label="状态" prop="status">
- <el-select
- v-model="queryParams.status"
- placeholder="全部"
- clearable
- class="!w-[100px]"
- >
- <el-option label="启用" value="1" />
- <el-option label="禁用" value="0" />
- </el-select>
- </el-form-item>
- <el-form-item label="创建时间">
- <el-date-picker
- class="!w-[240px]"
- v-model="dateTimeRange"
- type="daterange"
- range-separator="~"
- start-placeholder="开始时间"
- end-placeholder="截止时间"
- value-format="YYYY-MM-DD"
- />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleQuery"
- ><i-ep-search />搜索</el-button
- >
- <el-button @click="resetQuery">
- <i-ep-refresh />
- 重置</el-button
- >
- </el-form-item>
- </el-form>
- </div>
- <el-card shadow="never" class="table-container">
- <template #header>
- <div class="flex justify-between">
- <div>
- <el-button
- v-hasPerm="['sys:user:add']"
- type="success"
- @click="openDialog('user-form')"
- ><i-ep-plus />新增</el-button
- >
- <el-button
- v-hasPerm="['sys:user:delete']"
- type="danger"
- :disabled="removeIds.length === 0"
- @click="handleDelete()"
- ><i-ep-delete />删除</el-button
- >
- </div>
- <div>
- <el-dropdown split-button>
- 导入
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item @click="downloadTemplate">
- <i-ep-download />下载模板</el-dropdown-item
- >
- <el-dropdown-item @click="openDialog('user-import')">
- <i-ep-top />导入数据</el-dropdown-item
- >
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- <el-button class="ml-3" @click="handleExport"
- ><template #icon><i-ep-download /></template>导出</el-button
- >
- </div>
- </div>
- </template>
- <el-table
- v-loading="loading"
- :data="pageData"
- @selection-change="handleSelectionChange"
- >
- <el-table-column type="selection" width="50" align="center" />
- <el-table-column
- key="id"
- label="编号"
- align="center"
- prop="id"
- width="100"
- />
- <el-table-column
- key="username"
- label="用户名"
- align="center"
- prop="username"
- />
- <el-table-column
- label="用户昵称"
- width="120"
- align="center"
- prop="nickname"
- />
- <el-table-column
- label="性别"
- width="100"
- align="center"
- prop="genderLabel"
- />
- <el-table-column
- label="部门"
- width="120"
- align="center"
- prop="deptName"
- />
- <el-table-column
- label="手机号码"
- align="center"
- prop="mobile"
- width="120"
- />
- <el-table-column label="状态" align="center" prop="status">
- <template #default="scope">
- <el-tag :type="scope.row.status == 1 ? 'success' : 'info'">{{
- scope.row.status == 1 ? "启用" : "禁用"
- }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column
- label="创建时间"
- align="center"
- prop="createTime"
- width="180"
- />
- <el-table-column label="操作" fixed="right" width="220">
- <template #default="scope">
- <el-button
- v-hasPerm="['sys:user:reset_pwd']"
- type="primary"
- size="small"
- link
- @click="resetPassword(scope.row)"
- ><i-ep-refresh-left />重置密码</el-button
- >
- <el-button
- v-hasPerm="['sys:user:edit']"
- type="primary"
- link
- size="small"
- @click="openDialog('user-form', scope.row.id)"
- ><i-ep-edit />编辑</el-button
- >
- <el-button
- v-hasPerm="['sys:user:delete']"
- type="primary"
- link
- size="small"
- @click="handleDelete(scope.row.id)"
- ><i-ep-delete />删除</el-button
- >
- </template>
- </el-table-column>
- </el-table>
- <pagination
- v-if="total > 0"
- v-model:total="total"
- v-model:page="queryParams.pageNo"
- v-model:limit="queryParams.pageSize"
- @pagination="handleQuery"
- />
- </el-card>
- </el-col>
- </el-row>
- <!-- 弹窗 -->
- <el-dialog
- v-model="dialog.visible"
- :title="dialog.title"
- :width="dialog.width"
- append-to-body
- @close="closeDialog"
- >
- <!-- 用户新增/编辑表单 -->
- <el-form
- v-if="dialog.type === 'user-form'"
- ref="userFormRef"
- :model="formData"
- :rules="rules"
- label-width="80px"
- >
- <el-form-item label="用户名" prop="username">
- <el-input
- v-model="formData.username"
- :readonly="!!formData.id"
- placeholder="请输入用户名"
- />
- </el-form-item>
- <el-form-item label="用户昵称" prop="nickname">
- <el-input v-model="formData.nickname" placeholder="请输入用户昵称" />
- </el-form-item>
- <el-form-item label="所属部门" prop="deptId">
- <el-tree-select
- v-model="formData.deptId"
- placeholder="请选择所属部门"
- :data="deptList"
- filterable
- check-strictly
- :render-after-expand="false"
- />
- </el-form-item>
- <el-form-item label="性别" prop="gender">
- <dictionary v-model="formData.gender" type-code="gender" />
- </el-form-item>
- <el-form-item label="角色" prop="roleIds">
- <el-select v-model="formData.roleIds" multiple placeholder="请选择">
- <el-option
- v-for="item in roleList"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="手机号码" prop="mobile">
- <el-input
- v-model="formData.mobile"
- placeholder="请输入手机号码"
- maxlength="11"
- />
- </el-form-item>
- <el-form-item label="邮箱" prop="email">
- <el-input
- v-model="formData.email"
- placeholder="请输入邮箱"
- maxlength="50"
- />
- </el-form-item>
- <el-form-item label="状态" prop="status">
- <el-radio-group v-model="formData.status">
- <el-radio :value="1">正常</el-radio>
- <el-radio :value="0">禁用</el-radio>
- </el-radio-group>
- </el-form-item>
- </el-form>
- <!-- 用户导入表单 -->
- <el-form
- v-else-if="dialog.type === 'user-import'"
- :model="importData"
- label-width="100px"
- >
- <el-form-item label="部门">
- <el-tree-select
- v-model="importData.deptId"
- placeholder="请选择部门"
- :data="deptList"
- filterable
- check-strictly
- />
- </el-form-item>
- <el-form-item label="Excel文件">
- <el-upload
- ref="uploadRef"
- action=""
- drag
- accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
- :limit="1"
- :auto-upload="false"
- :file-list="importData.fileList"
- :on-change="handleFileChange"
- :on-exceed="handleFileExceed"
- >
- <el-icon class="el-icon--upload">
- <i-ep-upload-filled />
- </el-icon>
- <div class="el-upload__text">
- 将文件拖到此处,或
- <em>点击上传</em>
- </div>
- <template #tip>
- <div>xls/xlsx files</div>
- </template>
- </el-upload>
- </el-form-item>
- </el-form>
- <!-- 弹窗底部操作按钮 -->
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="handleSubmit">确 定</el-button>
- <el-button @click="closeDialog">取 消</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- defineOptions({
- name: "User",
- inheritAttrs: false,
- });
- import {
- getUserPage,
- getUserForm,
- deleteUsers,
- addUser,
- updateUser,
- updateUserPassword,
- downloadTemplateApi,
- exportUser,
- importUser,
- } from "@/api/system/user";
- import { treeList } from "@/api/system/dept";
- import { getRoleOptions } from "@/api/system/role";
- import { UserForm, UserQuery, UserPageVO } from "@/api/system/user/types";
- import type { UploadInstance } from "element-plus";
- import { genFileId } from "element-plus";
- const queryFormRef = ref(ElForm); // 查询表单
- const userFormRef = ref(ElForm); // 用户表单
- const uploadRef = ref<UploadInstance>(); // 上传组件
- const loading = ref(false); // 加载状态
- const removeIds = ref([]); // 删除用户ID集合 用于批量删除
- const queryParams = reactive<UserQuery>({
- pageNo: 1,
- pageSize: 10,
- });
- const dateTimeRange = ref("");
- const total = ref(0); // 数据总数
- const pageData = ref<UserPageVO[]>(); // 用户分页数据
- const deptList = ref<OptionType[]>(); // 部门下拉数据源
- const roleList = ref<OptionType[]>(); // 角色下拉数据源
- watch?.(dateTimeRange, (newVal) => {
- if (newVal) {
- queryParams.startTime = newVal[0];
- queryParams.endTime = newVal[1];
- }
- });
- // 弹窗对象
- const dialog = reactive({
- visible: false,
- type: "user-form",
- width: 800,
- title: "",
- });
- // 用户表单数据
- const formData = reactive<UserForm>({
- status: 1,
- });
- // 用户导入数据
- const importData = reactive({
- deptId: undefined,
- file: undefined,
- fileList: [],
- });
- // 校验规则
- const rules = reactive({
- username: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
- nickname: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
- deptId: [{ required: true, message: "所属部门不能为空", trigger: "blur" }],
- roleIds: [{ required: true, message: "用户角色不能为空", trigger: "blur" }],
- email: [
- {
- pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
- message: "请输入正确的邮箱地址",
- trigger: "blur",
- },
- ],
- mobile: [
- {
- pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
- message: "请输入正确的手机号码",
- trigger: "blur",
- },
- ],
- });
- /** 查询 */
- function handleQuery() {
- loading.value = true;
- getUserPage(queryParams)
- .then(({ data }) => {
- pageData.value = data.list;
- total.value = data.total;
- })
- .finally(() => {
- loading.value = false;
- });
- }
- /** 重置查询 */
- function resetQuery() {
- queryFormRef.value.resetFields();
- dateTimeRange.value = "";
- queryParams.pageNo = 1;
- queryParams.deptId = undefined;
- queryParams.startTime = undefined;
- queryParams.endTime = undefined;
- handleQuery();
- }
- /** 行选中 */
- function handleSelectionChange(selection: any) {
- removeIds.value = selection.map((item: any) => item.id);
- }
- /** 重置密码 */
- function resetPassword(row: { [key: string]: any }) {
- ElMessageBox.prompt(
- "请输入用户「" + row.username + "」的新密码",
- "重置密码",
- {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- }
- ).then(({ value }) => {
- if (!value) {
- ElMessage.warning("请输入新密码");
- return false;
- }
- updateUserPassword(row.id, value).then(() => {
- ElMessage.success("密码重置成功,新密码是:" + value);
- });
- });
- }
- /** 加载角色下拉数据源 */
- async function loadRoleOptions() {
- getRoleOptions().then((response) => {
- roleList.value = response.data;
- });
- }
- /** 加载部门下拉数据源 */
- async function loadDeptOptions() {
- treeList().then((response) => {
- deptList.value = response.data;
- });
- }
- /**
- * 打开弹窗
- *
- * @param type 弹窗类型 用户表单:user-form | 用户导入:user-import
- * @param id 用户ID
- */
- async function openDialog(type: string, id?: number) {
- dialog.visible = true;
- dialog.type = type;
- if (dialog.type === "user-form") {
- // 用户表单弹窗
- await loadDeptOptions();
- await loadRoleOptions();
- if (id) {
- dialog.title = "修改用户";
- getUserForm(id).then(({ data }) => {
- Object.assign(formData, { ...data });
- });
- } else {
- dialog.title = "新增用户";
- }
- } else if (dialog.type === "user-import") {
- // 用户导入弹窗
- dialog.title = "导入用户";
- dialog.width = 600;
- loadDeptOptions();
- }
- }
- /**
- * 关闭弹窗
- *
- * @param type 弹窗类型 用户表单:user-form | 用户导入:user-import
- */
- function closeDialog() {
- dialog.visible = false;
- if (dialog.type === "user-form") {
- userFormRef.value.resetFields();
- userFormRef.value.clearValidate();
- formData.id = undefined;
- formData.status = 1;
- } else if (dialog.type === "user-import") {
- importData.file = undefined;
- importData.fileList = [];
- }
- }
- /** 表单提交 */
- const handleSubmit = useThrottleFn(() => {
- if (dialog.type === "user-form") {
- userFormRef.value.validate((valid: any) => {
- if (valid) {
- const userId = formData.id;
- loading.value = true;
- if (userId) {
- updateUser(userId, formData)
- .then(() => {
- ElMessage.success("修改用户成功");
- closeDialog();
- resetQuery();
- })
- .finally(() => (loading.value = false));
- } else {
- addUser(formData)
- .then(() => {
- ElMessage.success("新增用户成功");
- closeDialog();
- resetQuery();
- })
- .finally(() => (loading.value = false));
- }
- }
- });
- } else if (dialog.type === "user-import") {
- if (!importData?.deptId) {
- ElMessage.warning("请选择部门");
- return false;
- }
- if (!importData?.file) {
- ElMessage.warning("上传Excel文件不能为空");
- return false;
- }
- importUser(importData?.deptId, importData?.file).then((response) => {
- ElMessage.success(response.data);
- closeDialog();
- resetQuery();
- });
- }
- }, 3000);
- /** 删除用户 */
- function handleDelete(id?: number) {
- const userIds = [id || removeIds.value].join(",");
- if (!userIds) {
- ElMessage.warning("请勾选删除项");
- return;
- }
- ElMessageBox.confirm("确认删除用户?", "警告", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning",
- }).then(function () {
- deleteUsers(userIds).then(() => {
- ElMessage.success("删除成功");
- resetQuery();
- });
- });
- }
- /** 下载导入模板 */
- function downloadTemplate() {
- downloadTemplateApi().then((response: any) => {
- const fileData = response.data;
- const fileName = decodeURI(
- response.headers["content-disposition"].split(";")[1].split("=")[1]
- );
- const fileType =
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
- const blob = new Blob([fileData], { type: fileType });
- const downloadUrl = window.URL.createObjectURL(blob);
- const downloadLink = document.createElement("a");
- downloadLink.href = downloadUrl;
- downloadLink.download = fileName;
- document.body.appendChild(downloadLink);
- downloadLink.click();
- document.body.removeChild(downloadLink);
- window.URL.revokeObjectURL(downloadUrl);
- });
- }
- /** Excel文件 Change */
- function handleFileChange(file: any) {
- importData.file = file.raw;
- }
- /** Excel文件 Exceed */
- function handleFileExceed(files: any) {
- uploadRef.value!.clearFiles();
- const file = files[0];
- file.uid = genFileId();
- uploadRef.value!.handleStart(file);
- importData.file = file;
- }
- /** 导出用户 */
- function handleExport() {
- exportUser(queryParams).then((response: any) => {
- const fileData = response.data;
- const fileName = decodeURI(
- response.headers["content-disposition"].split(";")[1].split("=")[1]
- );
- const fileType =
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
- const blob = new Blob([fileData], { type: fileType });
- const downloadUrl = window.URL.createObjectURL(blob);
- const downloadLink = document.createElement("a");
- downloadLink.href = downloadUrl;
- downloadLink.download = fileName;
- document.body.appendChild(downloadLink);
- downloadLink.click();
- document.body.removeChild(downloadLink);
- window.URL.revokeObjectURL(downloadUrl);
- });
- }
- onMounted?.(() => {
- handleQuery();
- });
- </script>
|