From 6d8ed5ee3c6163a44cdb985d35b8a9293cdc75d4 Mon Sep 17 00:00:00 2001 From: fengjingtao <fessor@139.com> Date: Tue, 21 Mar 2023 23:33:40 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=87=8D=E5=86=99=E5=B0=9D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/user/index.vue | 1385 ++++++++++++++++++------------- 1 file changed, 825 insertions(+), 560 deletions(-) diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 2f9ba9b0..c0f922e8 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -1,576 +1,841 @@ <template> - <div class="flex"> - <el-card class="w-1/5 user" :gutter="12" shadow="always"> - <template #header> - <div class="card-header"> - <span>部门列表</span> - <XTextButton title="修改部门" @click="handleDeptEdit()" /> + <div class="app-container"> + <!-- 搜索工作栏 --> + <el-row :gutter="20"> + <!--部门数据--> + <el-col :span="4" :xs="24"> + <div class="head-container"> + <el-input + v-model="deptName" + placeholder="请输入部门名称" + clearable + size="small" + prefix-icon="el-icon-search" + style="margin-bottom: 20px" + /> </div> - </template> - <el-input v-model="filterText" placeholder="搜索部门" /> - <el-scrollbar height="650"> - <el-tree - ref="treeRef" - node-key="id" - default-expand-all - :data="deptOptions" - :props="defaultProps" - :highlight-current="true" - :filter-node-method="filterNode" - :expand-on-click-node="false" - @node-click="handleDeptNodeClick" - /> - </el-scrollbar> - </el-card> - <el-card class="w-4/5 user" style="margin-left: 10px" :gutter="12" shadow="hover"> - <template #header> - <div class="card-header"> - <span>{{ tableTitle }}</span> + <div class="head-container"> + <el-tree + :data="deptOptions" + :props="defaultProps" + :expand-on-click-node="false" + :filter-node-method="filterNode" + ref="tree" + default-expand-all + highlight-current + @node-click="handleNodeClick" + /> </div> - </template> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:user:create']" - @click="handleCreate()" - /> - <!-- 操作:导入用户 --> - <XButton - type="warning" - preIcon="ep:upload" - :title="t('action.import')" - v-hasPermi="['system:user:import']" - @click="importDialogVisible = true" - /> - <!-- 操作:导出用户 --> - <XButton - type="warning" - preIcon="ep:download" - :title="t('action.export')" - v-hasPermi="['system:user:export']" - @click="exportList('用户数据.xls')" - /> - </template> - <template #status_default="{ row }"> - <el-switch - v-model="row.status" - :active-value="0" - :inactive-value="1" - @change="handleStatusChange(row)" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:编辑 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:user:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:user:update']" - @click="handleDetail(row.id)" - /> - <el-dropdown - class="p-0.5" - v-hasPermi="[ - 'system:user:update-password', - 'system:permission:assign-user-role', - 'system:user:delete' - ]" - > - <XTextButton :title="t('action.more')" postIcon="ep:arrow-down" /> - <template #dropdown> - <el-dropdown-menu> - <el-dropdown-item> - <!-- 操作:重置密码 --> - <XTextButton - preIcon="ep:key" - title="重置密码" - v-hasPermi="['system:user:update-password']" - @click="handleResetPwd(row)" - /> - </el-dropdown-item> - <el-dropdown-item> - <!-- 操作:分配角色 --> - <XTextButton - preIcon="ep:key" - title="分配角色" - v-hasPermi="['system:permission:assign-user-role']" - @click="handleRole(row)" - /> - </el-dropdown-item> - <el-dropdown-item> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:user:delete']" - @click="deleteData(row.id)" - /> - </el-dropdown-item> - </el-dropdown-menu> - </template> - </el-dropdown> - </template> - </XTable> - </el-card> - </div> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :rules="rules" - :schema="allSchemas.formSchema" - ref="formRef" - > - <template #deptId="form"> - <el-tree-select - node-key="id" - v-model="form['deptId']" - :props="defaultProps" - :data="deptOptions" - check-strictly - /> - </template> - <template #postIds="form"> - <el-select v-model="form['postIds']" multiple :placeholder="t('common.selectText')"> - <el-option - v-for="item in postOptions" - :key="item.id" - :label="item.name" - :value="(item.id as unknown as number)" - /> - </el-select> - </template> - </Form> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - > - <template #deptId="{ row }"> - <el-tag>{{ dataFormater(row.deptId) }}</el-tag> - </template> - <template #postIds="{ row }"> - <template v-if="row.postIds !== ''"> - <el-tag v-for="(post, index) in row.postIds" :key="index" index=""> - <template v-for="postObj in postOptions"> - {{ post === postObj.id ? postObj.name : '' }} - </template> - </el-tag> - </template> - <template v-else> </template> - </template> - </Descriptions> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="loading" - @click="submitForm()" - /> - <!-- 按钮:关闭 --> - <XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> - <!-- 分配用户角色 --> - <XModal v-model="roleDialogVisible" title="分配角色"> - <el-form :model="userRole" label-width="140px" :inline="true"> - <el-form-item label="用户名称"> - <el-tag>{{ userRole.username }}</el-tag> - </el-form-item> - <el-form-item label="用户昵称"> - <el-tag>{{ userRole.nickname }}</el-tag> - </el-form-item> - <el-form-item label="角色"> - <el-transfer - v-model="userRole.roleIds" - :titles="['角色列表', '已选择']" - :props="{ - key: 'id', - label: 'name' - }" - :data="roleOptions" - /> - </el-form-item> - </el-form> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton type="primary" :title="t('action.save')" :loading="loading" @click="submitRole()" /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="roleDialogVisible = false" /> - </template> - </XModal> - <!-- 导入 --> - <XModal v-model="importDialogVisible" :title="importDialogTitle"> - <el-form class="drawer-multiColumn-form" label-width="150px"> - <el-form-item label="模板下载 :"> - <XButton type="primary" prefix="ep:download" title="点击下载" @click="handleImportTemp()" /> - </el-form-item> - <el-form-item label="文件上传 :"> - <el-upload - ref="uploadRef" - :action="updateUrl + '?updateSupport=' + updateSupport" - :headers="uploadHeaders" - :drag="true" - :limit="1" - :multiple="true" - :show-file-list="true" - :disabled="uploadDisabled" - :before-upload="beforeExcelUpload" - :on-exceed="handleExceed" - :on-success="handleFileSuccess" - :on-error="excelUploadError" - :auto-upload="false" - accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + </el-col> + <!--用户数据--> + <el-col :span="20" :xs="24"> + <el-form + :model="queryParams" + ref="queryForm" + size="small" + :inline="true" + v-show="showSearch" + label-width="68px" > - <Icon icon="ep:upload-filled" /> - <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> - <template #tip> - <div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div> - </template> - </el-upload> - </el-form-item> - <el-form-item label="是否更新已经存在的用户数据:"> - <el-checkbox v-model="updateSupport" /> - </el-form-item> - </el-form> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="warning" - preIcon="ep:upload-filled" - :title="t('action.save')" - @click="submitFileForm()" - /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="importDialogVisible = false" /> - </template> - </XModal> + <el-form-item label="用户名称" prop="username"> + <el-input + v-model="queryParams.username" + placeholder="请输入用户名称" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="手机号码" prop="mobile"> + <el-input + v-model="queryParams.mobile" + placeholder="请输入手机号码" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select + v-model="queryParams.status" + placeholder="用户状态" + clearable + style="width: 240px" + > + <el-option + v-for="dict in statusDictDatas" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + style="width: 240px" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="['00:00:00', '23:59:59']" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" @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-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['system:user:create']" + >新增</el-button + > + </el-col> + <el-col :span="1.5"> + <el-button + type="info" + icon="el-icon-upload2" + size="mini" + @click="handleImport" + v-hasPermi="['system:user:import']" + >导入</el-button + > + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + icon="el-icon-download" + size="mini" + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:user:export']" + >导出</el-button + > + </el-col> + <right-toolbar + :showSearch.sync="showSearch" + @queryTable="getList" + :columns="columns" + ></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="userList"> + <el-table-column + label="用户编号" + align="center" + key="id" + prop="id" + v-if="columns[0].visible" + /> + <el-table-column + label="用户名称" + align="center" + key="username" + prop="username" + v-if="columns[1].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="用户昵称" + align="center" + key="nickname" + prop="nickname" + v-if="columns[2].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="部门" + align="center" + key="deptName" + prop="dept.name" + v-if="columns[3].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="手机号码" + align="center" + key="mobile" + prop="mobile" + v-if="columns[4].visible" + width="120" + /> + <el-table-column label="状态" key="status" v-if="columns[5].visible" align="center"> + <template v-slot="scope"> + <el-switch + v-model="scope.row.status" + :active-value="0" + :inactive-value="1" + @change="handleStatusChange(scope.row)" + /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + v-if="columns[6].visible" + width="160" + > + <template v-slot="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column + label="操作" + align="center" + width="160" + class-name="small-padding fixed-width" + > + <template v-slot="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['system:user:update']" + >修改</el-button + > + <el-dropdown + @command="(command) => handleCommand(command, scope.$index, scope.row)" + v-hasPermi="[ + 'system:user:delete', + 'system:user:update-password', + 'system:permission:assign-user-role' + ]" + > + <el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button> + <el-dropdown-menu slot="dropdown"> + <el-dropdown-item + command="handleDelete" + v-if="scope.row.id !== 1" + size="mini" + type="text" + icon="el-icon-delete" + v-hasPermi="['system:user:delete']" + >删除</el-dropdown-item + > + <el-dropdown-item + command="handleResetPwd" + size="mini" + type="text" + icon="el-icon-key" + v-hasPermi="['system:user:update-password']" + >重置密码</el-dropdown-item + > + <el-dropdown-item + command="handleRole" + size="mini" + type="text" + icon="el-icon-circle-check" + v-hasPermi="['system:permission:assign-user-role']" + >分配角色</el-dropdown-item + > + </el-dropdown-menu> + </el-dropdown> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total > 0" + :total="total" + :page.sync="queryParams.pageNo" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + </el-col> + </el-row> + + <!-- 添加或修改参数配置对话框 --> + <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> + <el-row> + <el-col :span="12"> + <el-form-item label="用户昵称" prop="nickname"> + <el-input v-model="form.nickname" placeholder="请输入用户昵称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="归属部门" prop="deptId"> + <treeselect + v-model="form.deptId" + :options="deptOptions" + :show-count="true" + :clearable="false" + placeholder="请选择归属部门" + :normalizer="normalizer" + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="手机号码" prop="mobile"> + <el-input v-model="form.mobile" placeholder="请输入手机号码" maxlength="11" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item v-if="form.id === undefined" label="用户名称" prop="username"> + <el-input v-model="form.username" placeholder="请输入用户名称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item v-if="form.id === undefined" label="用户密码" prop="password"> + <el-input + v-model="form.password" + placeholder="请输入用户密码" + type="password" + show-password + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="用户性别"> + <el-select v-model="form.sex" placeholder="请选择"> + <el-option + v-for="dict in sexDictDatas" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="岗位"> + <el-select v-model="form.postIds" multiple placeholder="请选择"> + <el-option + v-for="item in postOptions" + :key="item.id" + :label="item.name" + :value="item.id" + ></el-option> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="备注"> + <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> + </el-form-item> + </el-col> + </el-row> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + + <!-- 用户导入对话框 --> + <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body> + <el-upload + ref="upload" + :limit="1" + accept=".xlsx, .xls" + :headers="upload.headers" + :action="upload.url + '?updateSupport=' + upload.updateSupport" + :disabled="upload.isUploading" + :on-progress="handleFileUploadProgress" + :on-success="handleFileSuccess" + :auto-upload="false" + drag + > + <i class="el-icon-upload"></i> + <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> + <div class="el-upload__tip text-center" slot="tip"> + <div class="el-upload__tip" slot="tip"> + <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 + </div> + <span>仅允许导入xls、xlsx格式文件。</span> + <el-link + type="primary" + :underline="false" + style="font-size: 12px; vertical-align: baseline" + @click="importTemplate" + >下载模板</el-link + > + </div> + </el-upload> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitFileForm">确 定</el-button> + <el-button @click="upload.open = false">取 消</el-button> + </div> + </el-dialog> + + <!-- 分配角色 --> + <el-dialog title="分配角色" :visible.sync="openRole" width="500px" append-to-body> + <el-form :model="form" label-width="80px"> + <el-form-item label="用户名称"> + <el-input v-model="form.username" :disabled="true" /> + </el-form-item> + <el-form-item label="用户昵称"> + <el-input v-model="form.nickname" :disabled="true" /> + </el-form-item> + <el-form-item label="角色"> + <el-select v-model="form.roleIds" multiple placeholder="请选择"> + <el-option + v-for="item in roleOptions" + :key="parseInt(item.id)" + :label="item.name" + :value="parseInt(item.id)" + ></el-option> + </el-select> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitRole">确 定</el-button> + <el-button @click="cancelRole">取 消</el-button> + </div> + </el-dialog> + </div> </template> -<script setup lang="ts" name="User"> -import type { ElTree, UploadRawFile, UploadInstance } from 'element-plus' -import { handleTree, defaultProps } from '@/utils/tree' -import download from '@/utils/download' -import { CommonStatusEnum } from '@/utils/constants' -import { getAccessToken, getTenantId } from '@/utils/auth' -import type { FormExpose } from '@/components/Form' -import { rules, allSchemas } from './user.data' -import * as UserApi from '@/api/system/user' -import { listSimpleDeptApi } from '@/api/system/dept' -import { listSimpleRolesApi } from '@/api/system/role' -import { listSimplePostsApi, PostVO } from '@/api/system/post' + +<script> import { - aassignUserRoleApi, - listUserRolesApi, - PermissionAssignUserRoleReqVO -} from '@/api/system/permission' + createUserApi as addUser, + updateUserStatusApi as changeUserStatus, + deleteUserApi as delUser, + exportUserApi as exportUser, + // TODO: praseStrEmpty(id) + getUserApi as getUser, + importUserTemplateApi as importTemplate, + getUserPageApi as listUser, + resetUserPwdApi as resetUserPwd, + updateUserApi as updateUser +} from '@/api/system/user' +// TODO: change +import Treeselect from '@riophae/vue-treeselect' +// TODO: change??? +import '@riophae/vue-treeselect/dist/vue-treeselect.css' -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 +import { listSimpleDeptApi } from '@/api/system/dept' +import { listSimplePostsApi } from '@/api/system/post' -const queryParams = reactive({ - deptId: null -}) -// ========== 列表相关 ========== -const tableTitle = ref('用户列表') -// 列表相关的变量 -const [registerTable, { reload, deleteData, exportList }] = useXTable({ - allSchemas: allSchemas, - params: queryParams, - getListApi: UserApi.getUserPageApi, - deleteApi: UserApi.deleteUserApi, - exportListApi: UserApi.exportUserApi -}) -// ========== 创建部门树结构 ========== -const filterText = ref('') -const deptOptions = ref<Tree[]>([]) // 树形结构 -const treeRef = ref<InstanceType<typeof ElTree>>() -const getTree = async () => { - const res = await listSimpleDeptApi() - deptOptions.value.push(...handleTree(res)) -} -const filterNode = (value: string, data: Tree) => { - if (!value) return true - return data.name.includes(value) -} -const handleDeptNodeClick = async (row: { [key: string]: any }) => { - queryParams.deptId = row.id - await reload() -} -const { push } = useRouter() -const handleDeptEdit = () => { - push('/system/dept') -} -watch(filterText, (val) => { - treeRef.value!.filter(val) -}) -// ========== CRUD 相关 ========== -const loading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref -const postOptions = ref<PostVO[]>([]) //岗位列表 +import { CommonStatusEnum } from '@/utils/constants' +import { DICT_TYPE, getDictDatas } from '@/utils/dict' +import { assignUserRole, listUserRoles } from '@/api/system/permission' +import { listSimpleRoles } from '@/api/system/role' +import { getBaseHeader } from '@/utils/request' -// 获取岗位列表 -const getPostOptions = async () => { - const res = await listSimplePostsApi() - postOptions.value.push(...res) -} -const dataFormater = (val) => { - return deptFormater(deptOptions.value, val) -} -//部门回显 -const deptFormater = (ary, val: any) => { - var o = '' - if (ary && val) { - for (const v of ary) { - if (v.id == val) { - o = v.name - if (o) return o - } else if (v.children?.length) { - o = deptFormater(v.children, val) - if (o) return o - } +export default { + name: 'User', + components: { Treeselect }, + data() { + return { + // 遮罩层 + loading: true, + // 导出遮罩层 + exportLoading: false, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 用户表格数据 + userList: null, + // 弹出层标题 + title: '', + // 部门树选项 + deptOptions: undefined, + // 是否显示弹出层 + open: false, + // 部门名称 + deptName: undefined, + // 默认密码 + initPassword: undefined, + // 性别状态字典 + sexOptions: [], + // 岗位选项 + postOptions: [], + // 角色选项 + roleOptions: [], + // 表单参数 + form: {}, + defaultProps: { + children: 'children', + label: 'name' + }, + // 用户导入参数 + upload: { + // 是否显示弹出层(用户导入) + open: false, + // 弹出层标题(用户导入) + title: '', + // 是否禁用上传 + isUploading: false, + // 是否更新已经存在的用户数据 + updateSupport: 0, + // 设置上传的请求头部 + headers: getBaseHeader(), + // 上传的地址 + url: process.env.VUE_APP_BASE_API + '/admin-api/system/user/import' + }, + // 查询参数 + queryParams: { + pageNo: 1, + pageSize: 10, + username: undefined, + mobile: undefined, + status: undefined, + deptId: undefined, + createTime: [] + }, + // 列信息 + columns: [ + { key: 0, label: `用户编号`, visible: true }, + { key: 1, label: `用户名称`, visible: true }, + { key: 2, label: `用户昵称`, visible: true }, + { key: 3, label: `部门`, visible: true }, + { key: 4, label: `手机号码`, visible: true }, + { key: 5, label: `状态`, visible: true }, + { key: 6, label: `创建时间`, visible: true } + ], + // 表单校验 + rules: { + username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], + nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], + password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], + email: [ + { + type: 'email', + message: "'请输入正确的邮箱地址", + trigger: ['blur', 'change'] + } + ], + mobile: [ + { + pattern: + /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/, + message: '请输入正确的手机号码', + trigger: 'blur' + } + ] + }, + // 是否显示弹出层(角色权限) + openRole: false, + + // 枚举 + SysCommonStatusEnum: CommonStatusEnum, + // 数据字典 + statusDictDatas: getDictDatas(DICT_TYPE.COMMON_STATUS), + sexDictDatas: getDictDatas(DICT_TYPE.SYSTEM_USER_SEX) } - return o - } else { - return val - } -} - -// 设置标题 -const setDialogTile = async (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} - -// 新增操作 -const handleCreate = async () => { - setDialogTile('create') - // 重置表单 - await nextTick() - if (allSchemas.formSchema[0].field !== 'username') { - unref(formRef)?.addSchema( - { - field: 'username', - label: '用户账号', - component: 'Input' - }, - 0 - ) - unref(formRef)?.addSchema( - { - field: 'password', - label: '用户密码', - component: 'InputPassword' - }, - 1 - ) - } -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - await nextTick() - unref(formRef)?.delSchema('username') - unref(formRef)?.delSchema('password') - // 设置数据 - const res = await UserApi.getUserApi(rowId) - unref(formRef)?.setValues(res) -} -const detailData = ref() - -// 详情操作 -const handleDetail = async (rowId: number) => { - // 设置数据 - const res = await UserApi.getUserApi(rowId) - detailData.value = res - await setDialogTile('detail') -} - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - // 提交请求 - try { - const data = unref(formRef)?.formModel as UserApi.UserVO - if (actionType.value === 'create') { - loading.value = true - await UserApi.createUserApi(data) - message.success(t('common.createSuccess')) - } else { - loading.value = true - await UserApi.updateUserApi(data) - message.success(t('common.updateSuccess')) + }, + watch: { + // 根据名称筛选部门树 + deptName(val) { + this.$refs.tree.filter(val) + } + }, + created() { + this.getList() + this.getTreeselect() + // this.getConfigKey("sys.user.init-password").then(response => { + // this.initPassword = response.msg; + // }); + }, + methods: { + // 更多操作 + handleCommand(command, index, row) { + switch (command) { + case 'handleUpdate': + this.handleUpdate(row) //修改客户信息 + break + case 'handleDelete': + this.handleDelete(row) //红号变更 + break + case 'handleResetPwd': + this.handleResetPwd(row) + break + case 'handleRole': + this.handleRole(row) + break + default: + break + } + }, + /** 查询用户列表 */ + getList() { + this.loading = true + listUser(this.queryParams).then((response) => { + this.userList = response.data.list + this.total = response.data.total + this.loading = false + }) + }, + /** 查询部门下拉树结构 + 岗位下拉 */ + getTreeselect() { + listSimpleDeptApi().then((response) => { + // 处理 deptOptions 参数 + this.deptOptions = [] + this.deptOptions.push(...this.handleTree(response.data, 'id')) + }) + listSimplePostsApi().then((response) => { + // 处理 postOptions 参数 + this.postOptions = [] + this.postOptions.push(...response.data) + }) + }, + // 筛选节点 + filterNode(value, data) { + if (!value) return true + return data.name.indexOf(value) !== -1 + }, + // 节点单击事件 + handleNodeClick(data) { + this.queryParams.deptId = data.id + this.getList() + }, + // 用户状态修改 + handleStatusChange(row) { + let text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' + this.$modal + .confirm('确认要"' + text + '""' + row.username + '"用户吗?') + .then(function () { + return changeUserStatus(row.id, row.status) + }) + .then(() => { + this.$modal.msgSuccess(text + '成功') + }) + .catch(function () { + row.status = + row.status === CommonStatusEnum.ENABLE + ? CommonStatusEnum.DISABLE + : CommonStatusEnum.ENABLE + }) + }, + // 取消按钮 + cancel() { + this.open = false + this.reset() + }, + // 取消按钮(角色权限) + cancelRole() { + this.openRole = false + this.reset() + }, + // 表单重置 + reset() { + this.form = { + id: undefined, + deptId: undefined, + username: undefined, + nickname: undefined, + password: undefined, + mobile: undefined, + email: undefined, + sex: undefined, + status: '0', + remark: undefined, + postIds: [], + roleIds: [] + } + this.resetForm('form') + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNo = 1 + this.getList() + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm('queryForm') + this.handleQuery() + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset() + // 获得下拉数据 + this.getTreeselect() + // 打开表单,并设置初始化 + this.open = true + this.title = '添加用户' + this.form.password = this.initPassword + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset() + this.getTreeselect() + const id = row.id + getUser(id).then((response) => { + this.form = response.data + this.open = true + this.title = '修改用户' + this.form.password = '' + }) + }, + /** 重置密码按钮操作 */ + handleResetPwd(row) { + this.$prompt('请输入"' + row.username + '"的新密码', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消' + }) + .then(({ value }) => { + resetUserPwd(row.id, value).then((response) => { + this.$modal.msgSuccess('修改成功,新密码是:' + value) + }) + }) + .catch(() => {}) + }, + /** 分配用户角色操作 */ + handleRole(row) { + this.reset() + const id = row.id + // 处理了 form 的用户 username 和 nickname 的展示 + this.form.id = id + this.form.username = row.username + this.form.nickname = row.nickname + // 打开弹窗 + this.openRole = true + // 获得角色列表 + listSimpleRoles().then((response) => { + // 处理 roleOptions 参数 + this.roleOptions = [] + this.roleOptions.push(...response.data) + }) + // 获得角色拥有的菜单集合 + listUserRoles(id).then((response) => { + // 设置选中 + this.form.roleIds = response.data + }) + }, + /** 提交按钮 */ + submitForm: function () { + this.$refs['form'].validate((valid) => { + if (valid) { + if (this.form.id !== undefined) { + updateUser(this.form).then((response) => { + this.$modal.msgSuccess('修改成功') + this.open = false + this.getList() + }) + } else { + addUser(this.form).then((response) => { + this.$modal.msgSuccess('新增成功') + this.open = false + this.getList() + }) + } } - dialogVisible.value = false - } finally { - // unref(formRef)?.setSchema(allSchemas.formSchema) - // 刷新列表 - await reload() - loading.value = false + }) + }, + /** 提交按钮(角色权限) */ + submitRole: function () { + if (this.form.id !== undefined) { + assignUserRole({ + userId: this.form.id, + roleIds: this.form.roleIds + }).then((response) => { + this.$modal.msgSuccess('分配角色成功') + this.openRole = false + this.getList() + }) + } + }, + /** 删除按钮操作 */ + handleDelete(row) { + const ids = row.id || this.ids + this.$modal + .confirm('是否确认删除用户编号为"' + ids + '"的数据项?') + .then(function () { + return delUser(ids) + }) + .then(() => { + this.getList() + this.$modal.msgSuccess('删除成功') + }) + .catch(() => {}) + }, + /** 导出按钮操作 */ + handleExport() { + this.$modal + .confirm('是否确认导出所有用户数据项?') + .then(() => { + // 处理查询参数 + let params = { ...this.queryParams } + params.pageNo = undefined + params.pageSize = undefined + this.exportLoading = true + return exportUser(params) + }) + .then((response) => { + this.$download.excel(response, '用户数据.xls') + this.exportLoading = false + }) + .catch(() => {}) + }, + /** 导入按钮操作 */ + handleImport() { + this.upload.title = '用户导入' + this.upload.open = true + }, + /** 下载模板操作 */ + importTemplate() { + importTemplate().then((response) => { + this.$download.excel(response, '用户导入模板.xls') + }) + }, + // 文件上传中处理 + handleFileUploadProgress(event, file, fileList) { + this.upload.isUploading = true + }, + // 文件上传成功处理 + handleFileSuccess(response, file, fileList) { + if (response.code !== 0) { + this.$modal.msgError(response.msg) + return + } + this.upload.open = false + this.upload.isUploading = false + this.$refs.upload.clearFiles() + // 拼接提示语 + let data = response.data + let text = '创建成功数量:' + data.createUsernames.length + for (const username of data.createUsernames) { + text += '<br /> ' + username + } + text += '<br />更新成功数量:' + data.updateUsernames.length + for (const username of data.updateUsernames) { + text += '<br /> ' + username + } + text += '<br />更新失败数量:' + Object.keys(data.failureUsernames).length + for (const username in data.failureUsernames) { + text += '<br /> ' + username + ':' + data.failureUsernames[username] + } + this.$alert(text, '导入结果', { dangerouslyUseHTMLString: true }) + this.getList() + }, + // 提交上传文件 + submitFileForm() { + this.$refs.upload.submit() + }, + // 格式化部门的下拉框 + normalizer(node) { + return { + id: node.id, + label: node.name, + children: node.children } } - }) -} -// 改变用户状态操作 -const handleStatusChange = async (row: UserApi.UserVO) => { - const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' - message - .confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder')) - .then(async () => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE - await UserApi.updateUserStatusApi(row.id, row.status) - message.success(text + '成功') - // 刷新列表 - await reload() - }) - .catch(() => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE - }) -} -// 重置密码 -const handleResetPwd = (row: UserApi.UserVO) => { - message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { - UserApi.resetUserPwdApi(row.id, value).then(() => { - message.success('修改成功,新密码是:' + value) - }) - }) -} -// 分配角色 -const roleDialogVisible = ref(false) -const roleOptions = ref() -const userRole = reactive({ - id: 0, - username: '', - nickname: '', - roleIds: [] -}) -const handleRole = async (row: UserApi.UserVO) => { - userRole.id = row.id - userRole.username = row.username - userRole.nickname = row.nickname - // 获得角色拥有的权限集合 - const roles = await listUserRolesApi(row.id) - userRole.roleIds = roles - // 获取角色列表 - const roleOpt = await listSimpleRolesApi() - roleOptions.value = roleOpt - roleDialogVisible.value = true -} -// 提交 -const submitRole = async () => { - const data = ref<PermissionAssignUserRoleReqVO>({ - userId: userRole.id, - roleIds: userRole.roleIds - }) - await aassignUserRoleApi(data.value) - message.success(t('common.updateSuccess')) - roleDialogVisible.value = false -} -// ========== 导入相关 ========== -// TODO @星语:这个要不要把导入用户,封装成一个小组件?可选哈 -const importDialogVisible = ref(false) -const uploadDisabled = ref(false) -const importDialogTitle = ref('用户导入') -const updateSupport = ref(0) -let updateUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' -const uploadHeaders = ref() -// 下载导入模版 -const handleImportTemp = async () => { - const res = await UserApi.importUserTemplateApi() - download.excel(res, '用户导入模版.xls') -} -// 文件上传之前判断 -const beforeExcelUpload = (file: UploadRawFile) => { - const isExcel = - file.type === 'application/vnd.ms-excel' || - file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - const isLt5M = file.size / 1024 / 1024 < 5 - if (!isExcel) message.error('上传文件只能是 xls / xlsx 格式!') - if (!isLt5M) message.error('上传文件大小不能超过 5MB!') - return isExcel && isLt5M -} -// 文件上传 -const uploadRef = ref<UploadInstance>() -const submitFileForm = () => { - uploadHeaders.value = { - Authorization: 'Bearer ' + getAccessToken(), - 'tenant-id': getTenantId() } - uploadDisabled.value = true - uploadRef.value!.submit() } -// 文件上传成功 -const handleFileSuccess = async (response: any): Promise<void> => { - if (response.code !== 0) { - message.error(response.msg) - return - } - importDialogVisible.value = false - uploadDisabled.value = false - const data = response.data - let text = '上传成功数量:' + data.createUsernames.length + ';' - for (let username of data.createUsernames) { - text += '< ' + username + ' >' - } - text += '更新成功数量:' + data.updateUsernames.length + ';' - for (const username of data.updateUsernames) { - text += '< ' + username + ' >' - } - text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' - for (const username in data.failureUsernames) { - text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' - } - message.alert(text) - await reload() -} -// 文件数超出提示 -const handleExceed = (): void => { - message.error('最多只能上传一个文件!') -} -// 上传错误提示 -const excelUploadError = (): void => { - message.error('导入数据失败,请您重新上传!') -} -// ========== 初始化 ========== -onMounted(async () => { - await getPostOptions() - await getTree() -}) </script> - -<style scoped> -.user { - height: 780px; - max-height: 800px; -} -.card-header { - display: flex; - justify-content: space-between; - align-items: center; -} -</style> From 80d540790e2f58b19480d2e10d9794c9ca027cbd Mon Sep 17 00:00:00 2001 From: fengjingtao <fessor@139.com> Date: Wed, 22 Mar 2023 23:37:29 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=87=8D=E5=86=99-=E9=83=A8=E9=97=A8=E6=A0=91?= =?UTF-8?q?=EF=BC=8C=E8=A1=A8=E6=A0=BC=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/user/index.vue | 817 ++++++++------------------------ 1 file changed, 185 insertions(+), 632 deletions(-) diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index c0f922e8..2b4bcc41 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -10,9 +10,12 @@ placeholder="请输入部门名称" clearable size="small" - prefix-icon="el-icon-search" style="margin-bottom: 20px" - /> + > + <template #prefix> + <Icon icon="ep:search" /> + </template> + </el-input> </div> <div class="head-container"> <el-tree @@ -20,10 +23,11 @@ :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" - ref="tree" + ref="treeRef" + node-key="id" default-expand-all highlight-current - @node-click="handleNodeClick" + @node-click="handleDeptNodeClick" /> </div> </el-col> @@ -43,7 +47,7 @@ placeholder="请输入用户名称" clearable style="width: 240px" - @keyup.enter.native="handleQuery" + @keyup.enter="handleQuery" /> </el-form-item> <el-form-item label="手机号码" prop="mobile"> @@ -52,7 +56,7 @@ placeholder="请输入手机号码" clearable style="width: 240px" - @keyup.enter.native="handleQuery" + @keyup.enter="handleQuery" /> </el-form-item> <el-form-item label="状态" prop="status"> @@ -83,8 +87,8 @@ /> </el-form-item> <el-form-item> - <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> - <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> + <el-button type="primary" @click="handleQuery"><Icon icon="ep:search" />搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button> </el-form-item> </el-form> @@ -93,41 +97,37 @@ <el-button type="primary" plain - icon="el-icon-plus" - size="mini" + size="small" @click="handleAdd" v-hasPermi="['system:user:create']" - >新增</el-button + ><Icon icon="ep:plus" />新增</el-button > </el-col> <el-col :span="1.5"> <el-button type="info" - icon="el-icon-upload2" - size="mini" + size="small" @click="handleImport" v-hasPermi="['system:user:import']" - >导入</el-button + ><Icon icon="ep:upload" />导入</el-button > </el-col> <el-col :span="1.5"> <el-button type="warning" - icon="el-icon-download" - size="mini" + size="small" @click="handleExport" :loading="exportLoading" v-hasPermi="['system:user:export']" - >导出</el-button + ><Icon icon="ep:download" />导出</el-button > </el-col> - <right-toolbar + <!-- <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns" - ></right-toolbar> + ></right-toolbar> --> </el-row> - <el-table v-loading="loading" :data="userList"> <el-table-column label="用户编号" @@ -169,7 +169,7 @@ width="120" /> <el-table-column label="状态" key="status" v-if="columns[5].visible" align="center"> - <template v-slot="scope"> + <template #default="scope"> <el-switch v-model="scope.row.status" :active-value="0" @@ -185,7 +185,7 @@ v-if="columns[6].visible" width="160" > - <template v-slot="scope"> + <template #default="scope"> <span>{{ parseTime(scope.row.createTime) }}</span> </template> </el-table-column> @@ -195,14 +195,13 @@ width="160" class-name="small-padding fixed-width" > - <template v-slot="scope"> + <template #default="scope"> <el-button - size="mini" + size="small" type="text" - icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:user:update']" - >修改</el-button + ><Icon icon="ep:edit" />修改</el-button > <el-dropdown @command="(command) => handleCommand(command, scope.$index, scope.row)" @@ -212,630 +211,184 @@ 'system:permission:assign-user-role' ]" > - <el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button> - <el-dropdown-menu slot="dropdown"> - <el-dropdown-item - command="handleDelete" - v-if="scope.row.id !== 1" - size="mini" - type="text" - icon="el-icon-delete" - v-hasPermi="['system:user:delete']" - >删除</el-dropdown-item - > - <el-dropdown-item - command="handleResetPwd" - size="mini" - type="text" - icon="el-icon-key" - v-hasPermi="['system:user:update-password']" - >重置密码</el-dropdown-item - > - <el-dropdown-item - command="handleRole" - size="mini" - type="text" - icon="el-icon-circle-check" - v-hasPermi="['system:permission:assign-user-role']" - >分配角色</el-dropdown-item - > - </el-dropdown-menu> + <el-button size="small" type="text"><Icon icon="ep:d-arrow-right" />更多</el-button> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item + command="handleDelete" + v-if="scope.row.id !== 1" + size="small" + type="text" + v-hasPermi="['system:user:delete']" + ><Icon icon="ep:delete" />删除</el-dropdown-item + > + <el-dropdown-item + command="handleResetPwd" + size="small" + type="text" + v-hasPermi="['system:user:update-password']" + ><Icon icon="ep:key" />重置密码</el-dropdown-item + > + <el-dropdown-item + command="handleRole" + size="small" + type="text" + v-hasPermi="['system:permission:assign-user-role']" + ><Icon icon="ep:circle-check" />分配角色</el-dropdown-item + > + </el-dropdown-menu> + </template> </el-dropdown> </template> </el-table-column> </el-table> - <pagination v-show="total > 0" :total="total" - :page.sync="queryParams.pageNo" - :limit.sync="queryParams.pageSize" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" @pagination="getList" /> </el-col> </el-row> - - <!-- 添加或修改参数配置对话框 --> - <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body> - <el-form ref="form" :model="form" :rules="rules" label-width="80px"> - <el-row> - <el-col :span="12"> - <el-form-item label="用户昵称" prop="nickname"> - <el-input v-model="form.nickname" placeholder="请输入用户昵称" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="归属部门" prop="deptId"> - <treeselect - v-model="form.deptId" - :options="deptOptions" - :show-count="true" - :clearable="false" - placeholder="请选择归属部门" - :normalizer="normalizer" - /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item label="手机号码" prop="mobile"> - <el-input v-model="form.mobile" placeholder="请输入手机号码" maxlength="11" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="邮箱" prop="email"> - <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item v-if="form.id === undefined" label="用户名称" prop="username"> - <el-input v-model="form.username" placeholder="请输入用户名称" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item v-if="form.id === undefined" label="用户密码" prop="password"> - <el-input - v-model="form.password" - placeholder="请输入用户密码" - type="password" - show-password - /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item label="用户性别"> - <el-select v-model="form.sex" placeholder="请选择"> - <el-option - v-for="dict in sexDictDatas" - :key="parseInt(dict.value)" - :label="dict.label" - :value="parseInt(dict.value)" - /> - </el-select> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="岗位"> - <el-select v-model="form.postIds" multiple placeholder="请选择"> - <el-option - v-for="item in postOptions" - :key="item.id" - :label="item.name" - :value="item.id" - ></el-option> - </el-select> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="24"> - <el-form-item label="备注"> - <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> - </el-form-item> - </el-col> - </el-row> - </el-form> - <div slot="footer" class="dialog-footer"> - <el-button type="primary" @click="submitForm">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> - </el-dialog> - - <!-- 用户导入对话框 --> - <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body> - <el-upload - ref="upload" - :limit="1" - accept=".xlsx, .xls" - :headers="upload.headers" - :action="upload.url + '?updateSupport=' + upload.updateSupport" - :disabled="upload.isUploading" - :on-progress="handleFileUploadProgress" - :on-success="handleFileSuccess" - :auto-upload="false" - drag - > - <i class="el-icon-upload"></i> - <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> - <div class="el-upload__tip text-center" slot="tip"> - <div class="el-upload__tip" slot="tip"> - <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 - </div> - <span>仅允许导入xls、xlsx格式文件。</span> - <el-link - type="primary" - :underline="false" - style="font-size: 12px; vertical-align: baseline" - @click="importTemplate" - >下载模板</el-link - > - </div> - </el-upload> - <div slot="footer" class="dialog-footer"> - <el-button type="primary" @click="submitFileForm">确 定</el-button> - <el-button @click="upload.open = false">取 消</el-button> - </div> - </el-dialog> - - <!-- 分配角色 --> - <el-dialog title="分配角色" :visible.sync="openRole" width="500px" append-to-body> - <el-form :model="form" label-width="80px"> - <el-form-item label="用户名称"> - <el-input v-model="form.username" :disabled="true" /> - </el-form-item> - <el-form-item label="用户昵称"> - <el-input v-model="form.nickname" :disabled="true" /> - </el-form-item> - <el-form-item label="角色"> - <el-select v-model="form.roleIds" multiple placeholder="请选择"> - <el-option - v-for="item in roleOptions" - :key="parseInt(item.id)" - :label="item.name" - :value="parseInt(item.id)" - ></el-option> - </el-select> - </el-form-item> - </el-form> - <div slot="footer" class="dialog-footer"> - <el-button type="primary" @click="submitRole">确 定</el-button> - <el-button @click="cancelRole">取 消</el-button> - </div> - </el-dialog> </div> </template> -<script> -import { - createUserApi as addUser, - updateUserStatusApi as changeUserStatus, - deleteUserApi as delUser, - exportUserApi as exportUser, - // TODO: praseStrEmpty(id) - getUserApi as getUser, - importUserTemplateApi as importTemplate, - getUserPageApi as listUser, - resetUserPwdApi as resetUserPwd, - updateUserApi as updateUser -} from '@/api/system/user' -// TODO: change -import Treeselect from '@riophae/vue-treeselect' -// TODO: change??? -import '@riophae/vue-treeselect/dist/vue-treeselect.css' - +<script setup lang="ts" name="User"> +import type { ElTree } from 'element-plus' +import { handleTree, defaultProps } from '@/utils/tree' import { listSimpleDeptApi } from '@/api/system/dept' -import { listSimplePostsApi } from '@/api/system/post' +import { listSimplePostsApi, PostVO } from '@/api/system/post' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { UserVO } from '@/api/system/user' +import { + // createUserApi, + // updateUserStatusApi, + // deleteUserApi, + // exportUserApi, + // getUserApi, + // importUserTemplateApi, + getUserPageApi + // resetUserPwdApid, + // updateUserApi +} from '@/api/system/user' -import { CommonStatusEnum } from '@/utils/constants' -import { DICT_TYPE, getDictDatas } from '@/utils/dict' -import { assignUserRole, listUserRoles } from '@/api/system/permission' -import { listSimpleRoles } from '@/api/system/role' -import { getBaseHeader } from '@/utils/request' +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + username: undefined, + mobile: undefined, + status: undefined, + deptId: undefined, + createTime: [] +}) +const showSearch = ref(true) +// 数据字典 +const statusDictDatas = getDictOptions(DICT_TYPE.COMMON_STATUS) +// const sexDictDatas = getDictOptions(DICT_TYPE.SYSTEM_USER_SEX) -export default { - name: 'User', - components: { Treeselect }, - data() { - return { - // 遮罩层 - loading: true, - // 导出遮罩层 - exportLoading: false, - // 显示搜索条件 - showSearch: true, - // 总条数 - total: 0, - // 用户表格数据 - userList: null, - // 弹出层标题 - title: '', - // 部门树选项 - deptOptions: undefined, - // 是否显示弹出层 - open: false, - // 部门名称 - deptName: undefined, - // 默认密码 - initPassword: undefined, - // 性别状态字典 - sexOptions: [], - // 岗位选项 - postOptions: [], - // 角色选项 - roleOptions: [], - // 表单参数 - form: {}, - defaultProps: { - children: 'children', - label: 'name' - }, - // 用户导入参数 - upload: { - // 是否显示弹出层(用户导入) - open: false, - // 弹出层标题(用户导入) - title: '', - // 是否禁用上传 - isUploading: false, - // 是否更新已经存在的用户数据 - updateSupport: 0, - // 设置上传的请求头部 - headers: getBaseHeader(), - // 上传的地址 - url: process.env.VUE_APP_BASE_API + '/admin-api/system/user/import' - }, - // 查询参数 - queryParams: { - pageNo: 1, - pageSize: 10, - username: undefined, - mobile: undefined, - status: undefined, - deptId: undefined, - createTime: [] - }, - // 列信息 - columns: [ - { key: 0, label: `用户编号`, visible: true }, - { key: 1, label: `用户名称`, visible: true }, - { key: 2, label: `用户昵称`, visible: true }, - { key: 3, label: `部门`, visible: true }, - { key: 4, label: `手机号码`, visible: true }, - { key: 5, label: `状态`, visible: true }, - { key: 6, label: `创建时间`, visible: true } - ], - // 表单校验 - rules: { - username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], - nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], - password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], - email: [ - { - type: 'email', - message: "'请输入正确的邮箱地址", - trigger: ['blur', 'change'] - } - ], - mobile: [ - { - pattern: - /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/, - message: '请输入正确的手机号码', - trigger: 'blur' - } - ] - }, - // 是否显示弹出层(角色权限) - openRole: false, +// ========== 创建部门树结构 ========== +const deptName = ref('') +const deptOptions = ref<Tree[]>([]) // 树形结构 +const treeRef = ref<InstanceType<typeof ElTree>>() +const getTree = async () => { + const res = await listSimpleDeptApi() + deptOptions.value.push(...handleTree(res)) +} +const filterNode = (value: string, data: Tree) => { + if (!value) return true + return data.name.includes(value) +} +const handleDeptNodeClick = async (row: { [key: string]: any }) => { + queryParams.deptId = row.id + // await reload() + getList() +} +// 获取岗位列表 +const postOptions = ref<PostVO[]>([]) //岗位列表 +const getPostOptions = async () => { + const res = await listSimplePostsApi() + postOptions.value.push(...res) +} +// 用户列表 +const userList = ref<UserVO[]>([]) +const loading = ref(false) +const total = ref(0) +const columns = ref([ + { key: 0, label: `用户编号`, visible: true }, + { key: 1, label: `用户名称`, visible: true }, + { key: 2, label: `用户昵称`, visible: true }, + { key: 3, label: `部门`, visible: true }, + { key: 4, label: `手机号码`, visible: true }, + { key: 5, label: `状态`, visible: true }, + { key: 6, label: `创建时间`, visible: true } +]) +const getList = () => { + loading.value = true + getUserPageApi(queryParams).then((response) => { + userList.value = response.list + total.value = response.total + loading.value = false + }) +} +const handleQuery = () => {} +const resetQuery = () => {} +const handleAdd = () => {} +const handleImport = () => {} +const exportLoading = ref(false) +const handleExport = () => {} +const handleStatusChange = () => {} +const handleUpdate = () => {} +const handleCommand = () => {} +// ========== 初始化 ========== +onMounted(async () => { + getList() + await getPostOptions() + await getTree() +}) - // 枚举 - SysCommonStatusEnum: CommonStatusEnum, - // 数据字典 - statusDictDatas: getDictDatas(DICT_TYPE.COMMON_STATUS), - sexDictDatas: getDictDatas(DICT_TYPE.SYSTEM_USER_SEX) - } - }, - watch: { - // 根据名称筛选部门树 - deptName(val) { - this.$refs.tree.filter(val) - } - }, - created() { - this.getList() - this.getTreeselect() - // this.getConfigKey("sys.user.init-password").then(response => { - // this.initPassword = response.msg; - // }); - }, - methods: { - // 更多操作 - handleCommand(command, index, row) { - switch (command) { - case 'handleUpdate': - this.handleUpdate(row) //修改客户信息 - break - case 'handleDelete': - this.handleDelete(row) //红号变更 - break - case 'handleResetPwd': - this.handleResetPwd(row) - break - case 'handleRole': - this.handleRole(row) - break - default: - break - } - }, - /** 查询用户列表 */ - getList() { - this.loading = true - listUser(this.queryParams).then((response) => { - this.userList = response.data.list - this.total = response.data.total - this.loading = false - }) - }, - /** 查询部门下拉树结构 + 岗位下拉 */ - getTreeselect() { - listSimpleDeptApi().then((response) => { - // 处理 deptOptions 参数 - this.deptOptions = [] - this.deptOptions.push(...this.handleTree(response.data, 'id')) - }) - listSimplePostsApi().then((response) => { - // 处理 postOptions 参数 - this.postOptions = [] - this.postOptions.push(...response.data) - }) - }, - // 筛选节点 - filterNode(value, data) { - if (!value) return true - return data.name.indexOf(value) !== -1 - }, - // 节点单击事件 - handleNodeClick(data) { - this.queryParams.deptId = data.id - this.getList() - }, - // 用户状态修改 - handleStatusChange(row) { - let text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' - this.$modal - .confirm('确认要"' + text + '""' + row.username + '"用户吗?') - .then(function () { - return changeUserStatus(row.id, row.status) - }) - .then(() => { - this.$modal.msgSuccess(text + '成功') - }) - .catch(function () { - row.status = - row.status === CommonStatusEnum.ENABLE - ? CommonStatusEnum.DISABLE - : CommonStatusEnum.ENABLE - }) - }, - // 取消按钮 - cancel() { - this.open = false - this.reset() - }, - // 取消按钮(角色权限) - cancelRole() { - this.openRole = false - this.reset() - }, - // 表单重置 - reset() { - this.form = { - id: undefined, - deptId: undefined, - username: undefined, - nickname: undefined, - password: undefined, - mobile: undefined, - email: undefined, - sex: undefined, - status: '0', - remark: undefined, - postIds: [], - roleIds: [] - } - this.resetForm('form') - }, - /** 搜索按钮操作 */ - handleQuery() { - this.queryParams.pageNo = 1 - this.getList() - }, - /** 重置按钮操作 */ - resetQuery() { - this.resetForm('queryForm') - this.handleQuery() - }, - /** 新增按钮操作 */ - handleAdd() { - this.reset() - // 获得下拉数据 - this.getTreeselect() - // 打开表单,并设置初始化 - this.open = true - this.title = '添加用户' - this.form.password = this.initPassword - }, - /** 修改按钮操作 */ - handleUpdate(row) { - this.reset() - this.getTreeselect() - const id = row.id - getUser(id).then((response) => { - this.form = response.data - this.open = true - this.title = '修改用户' - this.form.password = '' - }) - }, - /** 重置密码按钮操作 */ - handleResetPwd(row) { - this.$prompt('请输入"' + row.username + '"的新密码', '提示', { - confirmButtonText: '确定', - cancelButtonText: '取消' - }) - .then(({ value }) => { - resetUserPwd(row.id, value).then((response) => { - this.$modal.msgSuccess('修改成功,新密码是:' + value) - }) - }) - .catch(() => {}) - }, - /** 分配用户角色操作 */ - handleRole(row) { - this.reset() - const id = row.id - // 处理了 form 的用户 username 和 nickname 的展示 - this.form.id = id - this.form.username = row.username - this.form.nickname = row.nickname - // 打开弹窗 - this.openRole = true - // 获得角色列表 - listSimpleRoles().then((response) => { - // 处理 roleOptions 参数 - this.roleOptions = [] - this.roleOptions.push(...response.data) - }) - // 获得角色拥有的菜单集合 - listUserRoles(id).then((response) => { - // 设置选中 - this.form.roleIds = response.data - }) - }, - /** 提交按钮 */ - submitForm: function () { - this.$refs['form'].validate((valid) => { - if (valid) { - if (this.form.id !== undefined) { - updateUser(this.form).then((response) => { - this.$modal.msgSuccess('修改成功') - this.open = false - this.getList() - }) - } else { - addUser(this.form).then((response) => { - this.$modal.msgSuccess('新增成功') - this.open = false - this.getList() - }) - } - } - }) - }, - /** 提交按钮(角色权限) */ - submitRole: function () { - if (this.form.id !== undefined) { - assignUserRole({ - userId: this.form.id, - roleIds: this.form.roleIds - }).then((response) => { - this.$modal.msgSuccess('分配角色成功') - this.openRole = false - this.getList() - }) - } - }, - /** 删除按钮操作 */ - handleDelete(row) { - const ids = row.id || this.ids - this.$modal - .confirm('是否确认删除用户编号为"' + ids + '"的数据项?') - .then(function () { - return delUser(ids) - }) - .then(() => { - this.getList() - this.$modal.msgSuccess('删除成功') - }) - .catch(() => {}) - }, - /** 导出按钮操作 */ - handleExport() { - this.$modal - .confirm('是否确认导出所有用户数据项?') - .then(() => { - // 处理查询参数 - let params = { ...this.queryParams } - params.pageNo = undefined - params.pageSize = undefined - this.exportLoading = true - return exportUser(params) - }) - .then((response) => { - this.$download.excel(response, '用户数据.xls') - this.exportLoading = false - }) - .catch(() => {}) - }, - /** 导入按钮操作 */ - handleImport() { - this.upload.title = '用户导入' - this.upload.open = true - }, - /** 下载模板操作 */ - importTemplate() { - importTemplate().then((response) => { - this.$download.excel(response, '用户导入模板.xls') - }) - }, - // 文件上传中处理 - handleFileUploadProgress(event, file, fileList) { - this.upload.isUploading = true - }, - // 文件上传成功处理 - handleFileSuccess(response, file, fileList) { - if (response.code !== 0) { - this.$modal.msgError(response.msg) - return - } - this.upload.open = false - this.upload.isUploading = false - this.$refs.upload.clearFiles() - // 拼接提示语 - let data = response.data - let text = '创建成功数量:' + data.createUsernames.length - for (const username of data.createUsernames) { - text += '<br /> ' + username - } - text += '<br />更新成功数量:' + data.updateUsernames.length - for (const username of data.updateUsernames) { - text += '<br /> ' + username - } - text += '<br />更新失败数量:' + Object.keys(data.failureUsernames).length - for (const username in data.failureUsernames) { - text += '<br /> ' + username + ':' + data.failureUsernames[username] - } - this.$alert(text, '导入结果', { dangerouslyUseHTMLString: true }) - this.getList() - }, - // 提交上传文件 - submitFileForm() { - this.$refs.upload.submit() - }, - // 格式化部门的下拉框 - normalizer(node) { - return { - id: node.id, - label: node.name, - children: node.children - } - } +const parseTime = (time) => { + if (!time) { + return null } + const format = '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if (typeof time === 'string' && /^[0-9]+$/.test(time)) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time + .replace(new RegExp(/-/gm), '/') + .replace('T', ' ') + .replace(new RegExp(/\.[\d]{3}/gm), '') + } + if (typeof time === 'number' && time.toString().length === 10) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { + return ['日', '一', '二', '三', '四', '五', '六'][value] + } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str } </script> From ca1b6df5ca3a48ef17f347135c218f3d1e15c8bc Mon Sep 17 00:00:00 2001 From: fengjingtao <fessor@139.com> Date: Wed, 29 Mar 2023 20:21:58 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86review=E4=BB=A5=E5=90=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 22 +- src/types/auto-components.d.ts | 11 - src/views/system/user/index0.vue | 576 +++++++++++++++++++++++++++++++ 3 files changed, 597 insertions(+), 12 deletions(-) create mode 100644 src/views/system/user/index0.vue diff --git a/.vscode/settings.json b/.vscode/settings.json index 38cc3052..3e9f1774 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,5 +40,25 @@ "i18n-ally.displayLanguage": "zh-CN", "i18n-ally.enabledFrameworks": ["vue", "react"], "god.tsconfig": "./tsconfig.json", - "vue-i18n.i18nPaths": "src/locales" + "vue-i18n.i18nPaths": "src/locales", + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#65c89b", + "activityBar.background": "#65c89b", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#945bc4", + "activityBarBadge.foreground": "#e7e7e7", + "commandCenter.border": "#15202b99", + "sash.hoverBorder": "#65c89b", + "statusBar.background": "#42b883", + "statusBar.foreground": "#15202b", + "statusBarItem.hoverBackground": "#359268", + "statusBarItem.remoteBackground": "#42b883", + "statusBarItem.remoteForeground": "#15202b", + "titleBar.activeBackground": "#42b883", + "titleBar.activeForeground": "#15202b", + "titleBar.inactiveBackground": "#42b88399", + "titleBar.inactiveForeground": "#15202b99" + }, + "peacock.color": "#42b883" } diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index be71517c..3607ce04 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -23,8 +23,6 @@ declare module '@vue/runtime-core' { DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] - ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer'] - ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElBadge: typeof import('element-plus/es')['ElBadge'] ElButton: typeof import('element-plus/es')['ElButton'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] @@ -54,31 +52,22 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] - ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] - ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] - ElRadio: typeof import('element-plus/es')['ElRadio'] - ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] - ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] - ElSpace: typeof import('element-plus/es')['ElSpace'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] - ElTableV2: typeof import('element-plus/es')['ElTableV2'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] diff --git a/src/views/system/user/index0.vue b/src/views/system/user/index0.vue new file mode 100644 index 00000000..2f9ba9b0 --- /dev/null +++ b/src/views/system/user/index0.vue @@ -0,0 +1,576 @@ +<template> + <div class="flex"> + <el-card class="w-1/5 user" :gutter="12" shadow="always"> + <template #header> + <div class="card-header"> + <span>部门列表</span> + <XTextButton title="修改部门" @click="handleDeptEdit()" /> + </div> + </template> + <el-input v-model="filterText" placeholder="搜索部门" /> + <el-scrollbar height="650"> + <el-tree + ref="treeRef" + node-key="id" + default-expand-all + :data="deptOptions" + :props="defaultProps" + :highlight-current="true" + :filter-node-method="filterNode" + :expand-on-click-node="false" + @node-click="handleDeptNodeClick" + /> + </el-scrollbar> + </el-card> + <el-card class="w-4/5 user" style="margin-left: 10px" :gutter="12" shadow="hover"> + <template #header> + <div class="card-header"> + <span>{{ tableTitle }}</span> + </div> + </template> + <!-- 列表 --> + <XTable @register="registerTable"> + <template #toolbar_buttons> + <!-- 操作:新增 --> + <XButton + type="primary" + preIcon="ep:zoom-in" + :title="t('action.add')" + v-hasPermi="['system:user:create']" + @click="handleCreate()" + /> + <!-- 操作:导入用户 --> + <XButton + type="warning" + preIcon="ep:upload" + :title="t('action.import')" + v-hasPermi="['system:user:import']" + @click="importDialogVisible = true" + /> + <!-- 操作:导出用户 --> + <XButton + type="warning" + preIcon="ep:download" + :title="t('action.export')" + v-hasPermi="['system:user:export']" + @click="exportList('用户数据.xls')" + /> + </template> + <template #status_default="{ row }"> + <el-switch + v-model="row.status" + :active-value="0" + :inactive-value="1" + @change="handleStatusChange(row)" + /> + </template> + <template #actionbtns_default="{ row }"> + <!-- 操作:编辑 --> + <XTextButton + preIcon="ep:edit" + :title="t('action.edit')" + v-hasPermi="['system:user:update']" + @click="handleUpdate(row.id)" + /> + <!-- 操作:详情 --> + <XTextButton + preIcon="ep:view" + :title="t('action.detail')" + v-hasPermi="['system:user:update']" + @click="handleDetail(row.id)" + /> + <el-dropdown + class="p-0.5" + v-hasPermi="[ + 'system:user:update-password', + 'system:permission:assign-user-role', + 'system:user:delete' + ]" + > + <XTextButton :title="t('action.more')" postIcon="ep:arrow-down" /> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item> + <!-- 操作:重置密码 --> + <XTextButton + preIcon="ep:key" + title="重置密码" + v-hasPermi="['system:user:update-password']" + @click="handleResetPwd(row)" + /> + </el-dropdown-item> + <el-dropdown-item> + <!-- 操作:分配角色 --> + <XTextButton + preIcon="ep:key" + title="分配角色" + v-hasPermi="['system:permission:assign-user-role']" + @click="handleRole(row)" + /> + </el-dropdown-item> + <el-dropdown-item> + <!-- 操作:删除 --> + <XTextButton + preIcon="ep:delete" + :title="t('action.del')" + v-hasPermi="['system:user:delete']" + @click="deleteData(row.id)" + /> + </el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> + </template> + </XTable> + </el-card> + </div> + <XModal v-model="dialogVisible" :title="dialogTitle"> + <!-- 对话框(添加 / 修改) --> + <Form + v-if="['create', 'update'].includes(actionType)" + :rules="rules" + :schema="allSchemas.formSchema" + ref="formRef" + > + <template #deptId="form"> + <el-tree-select + node-key="id" + v-model="form['deptId']" + :props="defaultProps" + :data="deptOptions" + check-strictly + /> + </template> + <template #postIds="form"> + <el-select v-model="form['postIds']" multiple :placeholder="t('common.selectText')"> + <el-option + v-for="item in postOptions" + :key="item.id" + :label="item.name" + :value="(item.id as unknown as number)" + /> + </el-select> + </template> + </Form> + <!-- 对话框(详情) --> + <Descriptions + v-if="actionType === 'detail'" + :schema="allSchemas.detailSchema" + :data="detailData" + > + <template #deptId="{ row }"> + <el-tag>{{ dataFormater(row.deptId) }}</el-tag> + </template> + <template #postIds="{ row }"> + <template v-if="row.postIds !== ''"> + <el-tag v-for="(post, index) in row.postIds" :key="index" index=""> + <template v-for="postObj in postOptions"> + {{ post === postObj.id ? postObj.name : '' }} + </template> + </el-tag> + </template> + <template v-else> </template> + </template> + </Descriptions> + <!-- 操作按钮 --> + <template #footer> + <!-- 按钮:保存 --> + <XButton + v-if="['create', 'update'].includes(actionType)" + type="primary" + :title="t('action.save')" + :loading="loading" + @click="submitForm()" + /> + <!-- 按钮:关闭 --> + <XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" /> + </template> + </XModal> + <!-- 分配用户角色 --> + <XModal v-model="roleDialogVisible" title="分配角色"> + <el-form :model="userRole" label-width="140px" :inline="true"> + <el-form-item label="用户名称"> + <el-tag>{{ userRole.username }}</el-tag> + </el-form-item> + <el-form-item label="用户昵称"> + <el-tag>{{ userRole.nickname }}</el-tag> + </el-form-item> + <el-form-item label="角色"> + <el-transfer + v-model="userRole.roleIds" + :titles="['角色列表', '已选择']" + :props="{ + key: 'id', + label: 'name' + }" + :data="roleOptions" + /> + </el-form-item> + </el-form> + <!-- 操作按钮 --> + <template #footer> + <!-- 按钮:保存 --> + <XButton type="primary" :title="t('action.save')" :loading="loading" @click="submitRole()" /> + <!-- 按钮:关闭 --> + <XButton :title="t('dialog.close')" @click="roleDialogVisible = false" /> + </template> + </XModal> + <!-- 导入 --> + <XModal v-model="importDialogVisible" :title="importDialogTitle"> + <el-form class="drawer-multiColumn-form" label-width="150px"> + <el-form-item label="模板下载 :"> + <XButton type="primary" prefix="ep:download" title="点击下载" @click="handleImportTemp()" /> + </el-form-item> + <el-form-item label="文件上传 :"> + <el-upload + ref="uploadRef" + :action="updateUrl + '?updateSupport=' + updateSupport" + :headers="uploadHeaders" + :drag="true" + :limit="1" + :multiple="true" + :show-file-list="true" + :disabled="uploadDisabled" + :before-upload="beforeExcelUpload" + :on-exceed="handleExceed" + :on-success="handleFileSuccess" + :on-error="excelUploadError" + :auto-upload="false" + accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + > + <Icon icon="ep:upload-filled" /> + <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> + <template #tip> + <div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div> + </template> + </el-upload> + </el-form-item> + <el-form-item label="是否更新已经存在的用户数据:"> + <el-checkbox v-model="updateSupport" /> + </el-form-item> + </el-form> + <template #footer> + <!-- 按钮:保存 --> + <XButton + type="warning" + preIcon="ep:upload-filled" + :title="t('action.save')" + @click="submitFileForm()" + /> + <!-- 按钮:关闭 --> + <XButton :title="t('dialog.close')" @click="importDialogVisible = false" /> + </template> + </XModal> +</template> +<script setup lang="ts" name="User"> +import type { ElTree, UploadRawFile, UploadInstance } from 'element-plus' +import { handleTree, defaultProps } from '@/utils/tree' +import download from '@/utils/download' +import { CommonStatusEnum } from '@/utils/constants' +import { getAccessToken, getTenantId } from '@/utils/auth' +import type { FormExpose } from '@/components/Form' +import { rules, allSchemas } from './user.data' +import * as UserApi from '@/api/system/user' +import { listSimpleDeptApi } from '@/api/system/dept' +import { listSimpleRolesApi } from '@/api/system/role' +import { listSimplePostsApi, PostVO } from '@/api/system/post' +import { + aassignUserRoleApi, + listUserRolesApi, + PermissionAssignUserRoleReqVO +} from '@/api/system/permission' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const queryParams = reactive({ + deptId: null +}) +// ========== 列表相关 ========== +const tableTitle = ref('用户列表') +// 列表相关的变量 +const [registerTable, { reload, deleteData, exportList }] = useXTable({ + allSchemas: allSchemas, + params: queryParams, + getListApi: UserApi.getUserPageApi, + deleteApi: UserApi.deleteUserApi, + exportListApi: UserApi.exportUserApi +}) +// ========== 创建部门树结构 ========== +const filterText = ref('') +const deptOptions = ref<Tree[]>([]) // 树形结构 +const treeRef = ref<InstanceType<typeof ElTree>>() +const getTree = async () => { + const res = await listSimpleDeptApi() + deptOptions.value.push(...handleTree(res)) +} +const filterNode = (value: string, data: Tree) => { + if (!value) return true + return data.name.includes(value) +} +const handleDeptNodeClick = async (row: { [key: string]: any }) => { + queryParams.deptId = row.id + await reload() +} +const { push } = useRouter() +const handleDeptEdit = () => { + push('/system/dept') +} +watch(filterText, (val) => { + treeRef.value!.filter(val) +}) +// ========== CRUD 相关 ========== +const loading = ref(false) // 遮罩层 +const actionType = ref('') // 操作按钮的类型 +const dialogVisible = ref(false) // 是否显示弹出层 +const dialogTitle = ref('edit') // 弹出层标题 +const formRef = ref<FormExpose>() // 表单 Ref +const postOptions = ref<PostVO[]>([]) //岗位列表 + +// 获取岗位列表 +const getPostOptions = async () => { + const res = await listSimplePostsApi() + postOptions.value.push(...res) +} +const dataFormater = (val) => { + return deptFormater(deptOptions.value, val) +} +//部门回显 +const deptFormater = (ary, val: any) => { + var o = '' + if (ary && val) { + for (const v of ary) { + if (v.id == val) { + o = v.name + if (o) return o + } else if (v.children?.length) { + o = deptFormater(v.children, val) + if (o) return o + } + } + return o + } else { + return val + } +} + +// 设置标题 +const setDialogTile = async (type: string) => { + dialogTitle.value = t('action.' + type) + actionType.value = type + dialogVisible.value = true +} + +// 新增操作 +const handleCreate = async () => { + setDialogTile('create') + // 重置表单 + await nextTick() + if (allSchemas.formSchema[0].field !== 'username') { + unref(formRef)?.addSchema( + { + field: 'username', + label: '用户账号', + component: 'Input' + }, + 0 + ) + unref(formRef)?.addSchema( + { + field: 'password', + label: '用户密码', + component: 'InputPassword' + }, + 1 + ) + } +} + +// 修改操作 +const handleUpdate = async (rowId: number) => { + setDialogTile('update') + await nextTick() + unref(formRef)?.delSchema('username') + unref(formRef)?.delSchema('password') + // 设置数据 + const res = await UserApi.getUserApi(rowId) + unref(formRef)?.setValues(res) +} +const detailData = ref() + +// 详情操作 +const handleDetail = async (rowId: number) => { + // 设置数据 + const res = await UserApi.getUserApi(rowId) + detailData.value = res + await setDialogTile('detail') +} + +// 提交按钮 +const submitForm = async () => { + const elForm = unref(formRef)?.getElFormRef() + if (!elForm) return + elForm.validate(async (valid) => { + if (valid) { + // 提交请求 + try { + const data = unref(formRef)?.formModel as UserApi.UserVO + if (actionType.value === 'create') { + loading.value = true + await UserApi.createUserApi(data) + message.success(t('common.createSuccess')) + } else { + loading.value = true + await UserApi.updateUserApi(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + } finally { + // unref(formRef)?.setSchema(allSchemas.formSchema) + // 刷新列表 + await reload() + loading.value = false + } + } + }) +} +// 改变用户状态操作 +const handleStatusChange = async (row: UserApi.UserVO) => { + const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' + message + .confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder')) + .then(async () => { + row.status = + row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE + await UserApi.updateUserStatusApi(row.id, row.status) + message.success(text + '成功') + // 刷新列表 + await reload() + }) + .catch(() => { + row.status = + row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE + }) +} +// 重置密码 +const handleResetPwd = (row: UserApi.UserVO) => { + message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { + UserApi.resetUserPwdApi(row.id, value).then(() => { + message.success('修改成功,新密码是:' + value) + }) + }) +} +// 分配角色 +const roleDialogVisible = ref(false) +const roleOptions = ref() +const userRole = reactive({ + id: 0, + username: '', + nickname: '', + roleIds: [] +}) +const handleRole = async (row: UserApi.UserVO) => { + userRole.id = row.id + userRole.username = row.username + userRole.nickname = row.nickname + // 获得角色拥有的权限集合 + const roles = await listUserRolesApi(row.id) + userRole.roleIds = roles + // 获取角色列表 + const roleOpt = await listSimpleRolesApi() + roleOptions.value = roleOpt + roleDialogVisible.value = true +} +// 提交 +const submitRole = async () => { + const data = ref<PermissionAssignUserRoleReqVO>({ + userId: userRole.id, + roleIds: userRole.roleIds + }) + await aassignUserRoleApi(data.value) + message.success(t('common.updateSuccess')) + roleDialogVisible.value = false +} +// ========== 导入相关 ========== +// TODO @星语:这个要不要把导入用户,封装成一个小组件?可选哈 +const importDialogVisible = ref(false) +const uploadDisabled = ref(false) +const importDialogTitle = ref('用户导入') +const updateSupport = ref(0) +let updateUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' +const uploadHeaders = ref() +// 下载导入模版 +const handleImportTemp = async () => { + const res = await UserApi.importUserTemplateApi() + download.excel(res, '用户导入模版.xls') +} +// 文件上传之前判断 +const beforeExcelUpload = (file: UploadRawFile) => { + const isExcel = + file.type === 'application/vnd.ms-excel' || + file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + const isLt5M = file.size / 1024 / 1024 < 5 + if (!isExcel) message.error('上传文件只能是 xls / xlsx 格式!') + if (!isLt5M) message.error('上传文件大小不能超过 5MB!') + return isExcel && isLt5M +} +// 文件上传 +const uploadRef = ref<UploadInstance>() +const submitFileForm = () => { + uploadHeaders.value = { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + } + uploadDisabled.value = true + uploadRef.value!.submit() +} +// 文件上传成功 +const handleFileSuccess = async (response: any): Promise<void> => { + if (response.code !== 0) { + message.error(response.msg) + return + } + importDialogVisible.value = false + uploadDisabled.value = false + const data = response.data + let text = '上传成功数量:' + data.createUsernames.length + ';' + for (let username of data.createUsernames) { + text += '< ' + username + ' >' + } + text += '更新成功数量:' + data.updateUsernames.length + ';' + for (const username of data.updateUsernames) { + text += '< ' + username + ' >' + } + text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' + for (const username in data.failureUsernames) { + text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' + } + message.alert(text) + await reload() +} +// 文件数超出提示 +const handleExceed = (): void => { + message.error('最多只能上传一个文件!') +} +// 上传错误提示 +const excelUploadError = (): void => { + message.error('导入数据失败,请您重新上传!') +} +// ========== 初始化 ========== +onMounted(async () => { + await getPostOptions() + await getTree() +}) +</script> + +<style scoped> +.user { + height: 780px; + max-height: 800px; +} +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} +</style> From 66460539ea8337acc1ce83527bb7346898bb6683 Mon Sep 17 00:00:00 2001 From: fengjingtao <fessor@139.com> Date: Wed, 29 Mar 2023 22:07:53 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/auto-components.d.ts | 4 - .../UserAssignRoleForm.vue} | 197 +++--- .../system/user/components/UserDeptTree.vue | 51 ++ .../{AddForm.vue => components/UserForm.vue} | 460 +++++++------- .../UserImportForm.vue} | 307 +++++----- src/views/system/user/index.vue | 252 ++------ src/views/system/user/index0.vue | 576 ------------------ src/views/system/user/utils.ts | 44 -- 8 files changed, 596 insertions(+), 1295 deletions(-) rename src/views/system/user/{RoleForm.vue => components/UserAssignRoleForm.vue} (62%) create mode 100644 src/views/system/user/components/UserDeptTree.vue rename src/views/system/user/{AddForm.vue => components/UserForm.vue} (76%) rename src/views/system/user/{ImportForm.vue => components/UserImportForm.vue} (89%) delete mode 100644 src/views/system/user/index0.vue delete mode 100644 src/views/system/user/utils.ts diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 52d25d1e..8fcd30dd 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -67,11 +67,7 @@ declare module '@vue/runtime-core' { ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] - ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] - ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] diff --git a/src/views/system/user/RoleForm.vue b/src/views/system/user/components/UserAssignRoleForm.vue similarity index 62% rename from src/views/system/user/RoleForm.vue rename to src/views/system/user/components/UserAssignRoleForm.vue index 0cb82df8..bbf37739 100644 --- a/src/views/system/user/RoleForm.vue +++ b/src/views/system/user/components/UserAssignRoleForm.vue @@ -1,89 +1,108 @@ -<template> - <el-dialog title="分配角色" :modelValue="show" width="500px" append-to-body @close="closeDialog"> - <el-form :model="formData" label-width="80px" ref="formRef"> - <el-form-item label="用户名称"> - <el-input v-model="formData.username" :disabled="true" /> - </el-form-item> - <el-form-item label="用户昵称"> - <el-input v-model="formData.nickname" :disabled="true" /> - </el-form-item> - <el-form-item label="角色"> - <el-select v-model="formData.roleIds" multiple placeholder="请选择角色"> - <el-option - v-for="item in roleOptions" - :key="item.id" - :label="item.name" - :value="item.id" - /> - </el-select> - </el-form-item> - </el-form> - <template #footer> - <div class="dialog-footer"> - <el-button type="primary" @click="submit">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> - </template> - </el-dialog> -</template> - -<script setup lang="ts"> -// TODO el-dialog 用 Dialog 组件 -import { assignUserRoleApi, PermissionAssignUserRoleReqVO } from '@/api/system/permission' - -interface Props { - show: boolean - roleOptions: any[] - formInitValue?: Recordable & Partial<typeof initParams> -} - -const props = withDefaults(defineProps<Props>(), { - show: false, - roleOptions: () => [], - formInitValue: () => ({}) -}) -const emits = defineEmits(['update:show', 'success']) - -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -// 表单初始化参数 -const initParams = { - nickname: '', - id: 0, - username: '', - roleIds: [] as number[] -} -const formData = ref<Recordable>({ ...initParams }) -watch( - () => props.formInitValue, - (val) => { - formData.value = { ...val } - }, - { deep: true } -) -/* 弹框按钮操作 */ -// 点击取消 -const cancel = () => { - closeDialog() -} -// 关闭弹窗 -const closeDialog = () => { - emits('update:show', false) -} -// 提交 -const submit = async () => { - const data = ref<PermissionAssignUserRoleReqVO>({ - userId: formData.value.id, - roleIds: formData.value.roleIds - }) - try { - await assignUserRoleApi(data.value) - message.success(t('common.updateSuccess')) - emits('success', true) - closeDialog() - } catch (error) { - console.error(error) - } -} -</script> +<template> + <Dialog + title="分配角色" + :modelValue="showDialog" + width="500px" + append-to-body + @close="closeDialog" + > + <el-form :model="formData" label-width="80px" ref="formRef"> + <el-form-item label="用户名称"> + <el-input v-model="formData.username" :disabled="true" /> + </el-form-item> + <el-form-item label="用户昵称"> + <el-input v-model="formData.nickname" :disabled="true" /> + </el-form-item> + <el-form-item label="角色"> + <el-select v-model="formData.roleIds" multiple placeholder="请选择角色"> + <el-option + v-for="item in roleOptions" + :key="item.id" + :label="item.name" + :value="item.id" + /> + </el-select> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submit">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </template> + </Dialog> +</template> + +<script setup lang="ts"> +import { + assignUserRoleApi, + listUserRolesApi, + PermissionAssignUserRoleReqVO +} from '@/api/system/permission' +import { UserVO } from '@/api/system/user' +import * as RoleApi from '@/api/system/role' + +const emits = defineEmits(['success']) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +// 表单初始化参数 +const initParams = { + nickname: '', + id: 0, + username: '', + roleIds: [] as number[] +} +const formData = ref<Recordable>({ ...initParams }) + +/* 弹框按钮操作 */ +// 点击取消 +const cancel = () => { + closeDialog() +} +// 关闭弹窗 +const closeDialog = () => { + showDialog.value = false +} +// 提交 +const submit = async () => { + const data = ref<PermissionAssignUserRoleReqVO>({ + userId: formData.value.id, + roleIds: formData.value.roleIds + }) + try { + await assignUserRoleApi(data.value) + message.success(t('common.updateSuccess')) + emits('success', true) + closeDialog() + } catch (error) { + console.error(error) + } +} + +const roleOptions = ref() +const userRole = reactive(initParams) +const showDialog = ref(false) +const formRef = ref() +const openForm = async (row: UserVO) => { + formRef.value?.resetFields() + userRole.id = row.id + userRole.username = row.username + userRole.nickname = row.nickname + + // 获得角色列表 + const roleOpt = await RoleApi.getSimpleRoleList() + roleOptions.value = [...roleOpt] + + // 获得角色拥有的菜单集合 + const roles = await listUserRolesApi(row.id) + userRole.roleIds = roles + formData.value = { ...userRole } + + showDialog.value = true +} +defineExpose({ + openForm +}) +</script> diff --git a/src/views/system/user/components/UserDeptTree.vue b/src/views/system/user/components/UserDeptTree.vue new file mode 100644 index 00000000..59004a92 --- /dev/null +++ b/src/views/system/user/components/UserDeptTree.vue @@ -0,0 +1,51 @@ +<template> + <div class="head-container"> + <el-input v-model="deptName" placeholder="请输入部门名称" clearable style="margin-bottom: 20px"> + <template #prefix> + <Icon icon="ep:search" /> + </template> + </el-input> + </div> + <div class="head-container"> + <el-tree + :data="deptOptions" + :props="defaultProps" + :expand-on-click-node="false" + :filter-node-method="filterNode" + ref="treeRef" + node-key="id" + default-expand-all + highlight-current + @node-click="handleDeptNodeClick" + /> + </div> +</template> + +<script setup lang="ts" name="UserDeptTree"> +import { ElTree } from 'element-plus' +import * as DeptApi from '@/api/system/dept' +import { defaultProps, handleTree } from '@/utils/tree' + +const emits = defineEmits(['node-click']) +const deptName = ref('') +const deptOptions = ref<Tree[]>([]) // 树形结构 +const treeRef = ref<InstanceType<typeof ElTree>>() +const getTree = async () => { + const res = await DeptApi.getSimpleDeptList() + deptOptions.value = [] + deptOptions.value.push(...handleTree(res)) +} + +const filterNode = (value: string, data: Tree) => { + if (!value) return true + return data.name.includes(value) +} + +const handleDeptNodeClick = async (row: { [key: string]: any }) => { + emits('node-click', row) +} + +onMounted(async () => { + await getTree() +}) +</script> diff --git a/src/views/system/user/AddForm.vue b/src/views/system/user/components/UserForm.vue similarity index 76% rename from src/views/system/user/AddForm.vue rename to src/views/system/user/components/UserForm.vue index 9a4d6029..4ea21607 100644 --- a/src/views/system/user/AddForm.vue +++ b/src/views/system/user/components/UserForm.vue @@ -1,223 +1,237 @@ -<template> - <!-- 添加或修改参数配置对话框 --> - <el-dialog - :title="title" - :modelValue="modelValue" - width="600px" - append-to-body - @close="closeDialog" - > - <el-form ref="formRef" :model="formData" :rules="rules" label-width="80px"> - <el-row> - <el-col :span="12"> - <el-form-item label="用户昵称" prop="nickname"> - <el-input v-model="formData.nickname" placeholder="请输入用户昵称" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="归属部门" prop="deptId"> - <el-tree-select - node-key="id" - v-model="formData.deptId" - :data="deptOptions" - :props="defaultProps" - check-strictly - placeholder="请选择归属部门" - /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item label="手机号码" prop="mobile"> - <el-input v-model="formData.mobile" placeholder="请输入手机号码" maxlength="11" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="邮箱" prop="email"> - <el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username"> - <el-input v-model="formData.username" placeholder="请输入用户名称" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password"> - <el-input - v-model="formData.password" - placeholder="请输入用户密码" - type="password" - show-password - /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item label="用户性别"> - <el-select v-model="formData.sex" placeholder="请选择"> - <el-option - v-for="dict in sexDictDatas" - :key="parseInt(dict.value)" - :label="dict.label" - :value="parseInt(dict.value)" - /> - </el-select> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="岗位"> - <el-select v-model="formData.postIds" multiple placeholder="请选择"> - <el-option - v-for="item in postOptions" - :key="item.id" - :label="item.name" - :value="item.id" - /> - </el-select> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="24"> - <el-form-item label="备注"> - <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" /> - </el-form-item> - </el-col> - </el-row> - </el-form> - <template #footer> - <div class="dialog-footer"> - <el-button type="primary" @click="submitForm">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> - </template> - </el-dialog> -</template> -<script lang="ts" setup> -import { PostVO } from '@/api/system/post' -import { createUserApi, updateUserApi } from '@/api/system/user' -import { DICT_TYPE, getDictOptions } from '@/utils/dict' -import { defaultProps } from '@/utils/tree' -import { ElForm, FormItemRule } from 'element-plus' -import { Arrayable } from 'element-plus/es/utils' - -type Form = InstanceType<typeof ElForm> -interface Props { - deptOptions?: Tree[] - postOptions?: PostVO[] //岗位列表 - modelValue: boolean - formInitValue?: Recordable & Partial<typeof initParams> -} - -const props = withDefaults(defineProps<Props>(), { - deptOptions: () => [], - postOptions: () => [], - modelValue: false, - formInitValue: () => ({}) -}) -const emits = defineEmits(['update:modelValue', 'success']) - -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 -// 弹出层标题 -const title = computed(() => { - return formData.value?.id ? '修改用户' : '添加用户' -}) - -// 性别字典 -const sexDictDatas = getDictOptions(DICT_TYPE.SYSTEM_USER_SEX) - -// 表单初始化参数 -const initParams = { - nickname: '', - deptId: '', - mobile: '', - email: '', - id: undefined, - username: '', - password: '', - sex: 1, - postIds: [], - remark: '', - status: '0', - roleIds: [] -} - -// 校验规则 -const rules = { - username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], - nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], - password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], - email: [ - { - type: 'email', - message: "'请输入正确的邮箱地址", - trigger: ['blur', 'change'] - } - ], - mobile: [ - { - pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/, - message: '请输入正确的手机号码', - trigger: 'blur' - } - ] -} as Partial<Record<string, Arrayable<FormItemRule>>> -const formRef = ref<Form | null>() -const formData = ref<Recordable>({ ...initParams }) -watch( - () => props.formInitValue, - (val) => { - formData.value = { ...val } - }, - { deep: true } -) - -const resetForm = () => { - let form = formRef?.value - if (!form) return - formData.value = { ...initParams } - form && (form as Form).resetFields() -} -const closeDialog = () => { - emits('update:modelValue', false) -} -// 操作成功 -const operateOk = () => { - emits('success', true) - closeDialog() -} -const submitForm = () => { - let form = formRef.value as Form - form.validate(async (valid) => { - let data = formData.value - if (valid) { - try { - if (data?.id !== undefined) { - await updateUserApi(data) - message.success(t('common.updateSuccess')) - operateOk() - } else { - await createUserApi(data) - message.success(t('common.createSuccess')) - operateOk() - } - } catch (err) { - console.error(err) - } - } - }) -} -const cancel = () => { - closeDialog() -} - -defineExpose({ - resetForm -}) -</script> +<template> + <!-- 添加或修改参数配置对话框 --> + <Dialog :title="title" :modelValue="showDialog" width="600px" append-to-body @close="closeDialog"> + <el-form ref="formRef" :model="formData" :rules="rules" label-width="80px"> + <el-row> + <el-col :span="12"> + <el-form-item label="用户昵称" prop="nickname"> + <el-input v-model="formData.nickname" placeholder="请输入用户昵称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="归属部门" prop="deptId"> + <el-tree-select + node-key="id" + v-model="formData.deptId" + :data="deptOptions" + :props="defaultProps" + check-strictly + placeholder="请选择归属部门" + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="手机号码" prop="mobile"> + <el-input v-model="formData.mobile" placeholder="请输入手机号码" maxlength="11" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username"> + <el-input v-model="formData.username" placeholder="请输入用户名称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password"> + <el-input + v-model="formData.password" + placeholder="请输入用户密码" + type="password" + show-password + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="用户性别"> + <el-select v-model="formData.sex" placeholder="请选择"> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)" + :key="dict.value as number" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="岗位"> + <el-select v-model="formData.postIds" multiple placeholder="请选择"> + <el-option + v-for="item in postOptions" + :key="item.id" + :label="item.name" + :value="item.id as number" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="备注"> + <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script lang="ts" setup> +import { PostVO } from '@/api/system/post' +import * as PostApi from '@/api/system/post' +import { createUserApi, getUserApi, updateUserApi } from '@/api/system/user' +import * as DeptApi from '@/api/system/dept' + +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { defaultProps, handleTree } from '@/utils/tree' +import { ElForm, FormItemRule } from 'element-plus' +import { Arrayable } from 'element-plus/es/utils' +import { UserVO } from '@/api/login/types' + +type Form = InstanceType<typeof ElForm> + +const emits = defineEmits(['success']) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const showDialog = ref(false) +// 弹出层标题 +const title = computed(() => { + return formData.value?.id ? '修改用户' : '添加用户' +}) + +const deptOptions = ref<Tree[]>([]) // 树形结构 +const getTree = async () => { + const res = await DeptApi.getSimpleDeptList() + deptOptions.value = [] + deptOptions.value.push(...handleTree(res)) +} +// 获取岗位列表 +const postOptions = ref<PostVO[]>([]) //岗位列表 +const getPostOptions = async () => { + const res = (await PostApi.getSimplePostList()) as PostVO[] + postOptions.value.push(...res) +} + +// 表单初始化参数 +const initParams = { + nickname: '', + deptId: '', + mobile: '', + email: '', + id: undefined, + username: '', + password: '', + sex: 1, + postIds: [], + remark: '', + status: '0', + roleIds: [] +} + +// 校验规则 +const rules = { + username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], + nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], + password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], + email: [ + { + type: 'email', + message: '请输入正确的邮箱地址', + trigger: ['blur', 'change'] + } + ], + mobile: [ + { + pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/, + message: '请输入正确的手机号码', + trigger: 'blur' + } + ] +} as Partial<Record<string, Arrayable<FormItemRule>>> +const formRef = ref<Form | null>() +const formData = ref<Recordable>({ ...initParams }) + +const resetForm = () => { + let form = formRef?.value + if (!form) return + formData.value = { ...initParams } + form && (form as Form).resetFields() +} +const closeDialog = () => { + showDialog.value = false +} +// 操作成功 +const operateOk = () => { + emits('success', true) + closeDialog() +} +const submitForm = () => { + let form = formRef.value as Form + form.validate(async (valid) => { + let data = formData.value + if (valid) { + try { + if (data?.id !== undefined) { + await updateUserApi(data) + message.success(t('common.updateSuccess')) + operateOk() + } else { + await createUserApi(data) + message.success(t('common.createSuccess')) + operateOk() + } + } catch (err) { + console.error(err) + } + } + }) +} +/* 取消 */ +const cancel = () => { + closeDialog() +} + +/* 打开弹框 */ +const openForm = (row: undefined | UserVO) => { + resetForm() + getTree() // 部门树 + if (row && row.id) { + const id = row.id + getUserApi(id).then((response) => { + formData.value = response + }) + } else { + formData.value = { ...initParams } + } + + showDialog.value = true +} + +// ========== 初始化 ========== +onMounted(async () => { + await getPostOptions() +}) + +defineExpose({ + resetForm, + openForm +}) +</script> diff --git a/src/views/system/user/ImportForm.vue b/src/views/system/user/components/UserImportForm.vue similarity index 89% rename from src/views/system/user/ImportForm.vue rename to src/views/system/user/components/UserImportForm.vue index 4bfa4631..f63936e2 100644 --- a/src/views/system/user/ImportForm.vue +++ b/src/views/system/user/components/UserImportForm.vue @@ -1,153 +1,154 @@ -<template> - <el-dialog - :title="upload.title" - :modelValue="modelValue" - width="400px" - append-to-body - @close="closeDialog" - > - <el-upload - ref="uploadRef" - accept=".xlsx, .xls" - :limit="1" - :headers="upload.headers" - :action="upload.url + '?updateSupport=' + upload.updateSupport" - :disabled="upload.isUploading" - :on-progress="handleFileUploadProgress" - :on-success="handleFileSuccess" - :on-exceed="handleExceed" - :on-error="excelUploadError" - :auto-upload="false" - drag - > - <Icon icon="ep:upload" /> - <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> - <template #tip> - <div class="el-upload__tip text-center"> - <div class="el-upload__tip"> - <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 - </div> - - <span>仅允许导入xls、xlsx格式文件。</span> - <el-link - type="primary" - :underline="false" - style="font-size: 12px; vertical-align: baseline" - @click="importTemplate" - >下载模板</el-link - > - </div> - </template> - </el-upload> - <template #footer> - <div class="dialog-footer"> - <el-button type="primary" @click="submitFileForm">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> - </template> - </el-dialog> -</template> - -<script lang="ts" setup> -import { importUserTemplateApi } from '@/api/system/user' -import { getAccessToken, getTenantId } from '@/utils/auth' -import download from '@/utils/download' - -interface Props { - modelValue: boolean -} - -// const props = -withDefaults(defineProps<Props>(), { - modelValue: false -}) - -const emits = defineEmits(['update:modelValue', 'success']) - -const message = useMessage() // 消息弹窗 - -const uploadRef = ref() - -// 用户导入参数 -const upload = reactive({ - // // 是否显示弹出层(用户导入) - // open: false, - // 弹出层标题(用户导入) - title: '用户导入', - // 是否禁用上传 - isUploading: false, - // 是否更新已经存在的用户数据 - updateSupport: 0, - // 设置上传的请求头部 - headers: { - Authorization: 'Bearer ' + getAccessToken(), - 'tenant-id': getTenantId() - }, - // 上传的地址 - url: import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' -}) - -// 文件上传中处理 -const handleFileUploadProgress = () => { - upload.isUploading = true -} -// 文件上传成功处理 -const handleFileSuccess = (response: any) => { - if (response.code !== 0) { - message.error(response.msg) - return - } - upload.isUploading = false - uploadRef.value?.clearFiles() - // 拼接提示语 - const data = response.data - let text = '上传成功数量:' + data.createUsernames.length + ';' - for (let username of data.createUsernames) { - text += '< ' + username + ' >' - } - text += '更新成功数量:' + data.updateUsernames.length + ';' - for (const username of data.updateUsernames) { - text += '< ' + username + ' >' - } - text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' - for (const username in data.failureUsernames) { - text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' - } - message.alert(text) - emits('success') - closeDialog() -} - -// 文件数超出提示 -const handleExceed = (): void => { - message.error('最多只能上传一个文件!') -} -// 上传错误提示 -const excelUploadError = (): void => { - message.error('导入数据失败,请您重新上传!') -} - -/** 下载模板操作 */ -const importTemplate = async () => { - try { - const res = await importUserTemplateApi() - download.excel(res, '用户导入模版.xls') - } catch (error) { - console.error(error) - } -} - -/* 弹框按钮操作 */ -// 点击取消 -const cancel = () => { - closeDialog() -} -// 关闭弹窗 -const closeDialog = () => { - emits('update:modelValue', false) -} -// 提交上传文件 -const submitFileForm = () => { - uploadRef.value?.submit() -} -</script> +<template> + <Dialog + :title="upload.title" + :modelValue="showDialog" + width="400px" + append-to-body + @close="closeDialog" + > + <el-upload + ref="uploadRef" + accept=".xlsx, .xls" + :limit="1" + :headers="upload.headers" + :action="upload.url + '?updateSupport=' + upload.updateSupport" + :disabled="upload.isUploading" + :on-progress="handleFileUploadProgress" + :on-success="handleFileSuccess" + :on-exceed="handleExceed" + :on-error="excelUploadError" + :auto-upload="false" + drag + > + <Icon icon="ep:upload" /> + <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> + <template #tip> + <div class="el-upload__tip text-center"> + <div class="el-upload__tip"> + <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 + </div> + + <span>仅允许导入xls、xlsx格式文件。</span> + <el-link + type="primary" + :underline="false" + style="font-size: 12px; vertical-align: baseline" + @click="importTemplate" + >下载模板</el-link + > + </div> + </template> + </el-upload> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitFileForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </template> + </Dialog> +</template> + +<script lang="ts" setup> +import { importUserTemplateApi } from '@/api/system/user' +import { getAccessToken, getTenantId } from '@/utils/auth' +import download from '@/utils/download' + +const emits = defineEmits(['success']) + +const message = useMessage() // 消息弹窗 + +const showDialog = ref(false) +const uploadRef = ref() + +// 用户导入参数 +const upload = reactive({ + // // 是否显示弹出层(用户导入) + // open: false, + // 弹出层标题(用户导入) + title: '用户导入', + // 是否禁用上传 + isUploading: false, + // 是否更新已经存在的用户数据 + updateSupport: 0, + // 设置上传的请求头部 + headers: { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + }, + // 上传的地址 + url: import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' +}) + +// 文件上传中处理 +const handleFileUploadProgress = () => { + upload.isUploading = true +} +// 文件上传成功处理 +const handleFileSuccess = (response: any) => { + if (response.code !== 0) { + message.error(response.msg) + return + } + upload.isUploading = false + uploadRef.value?.clearFiles() + // 拼接提示语 + const data = response.data + let text = '上传成功数量:' + data.createUsernames.length + ';' + for (let username of data.createUsernames) { + text += '< ' + username + ' >' + } + text += '更新成功数量:' + data.updateUsernames.length + ';' + for (const username of data.updateUsernames) { + text += '< ' + username + ' >' + } + text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' + for (const username in data.failureUsernames) { + text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' + } + message.alert(text) + emits('success') + closeDialog() +} + +// 文件数超出提示 +const handleExceed = (): void => { + message.error('最多只能上传一个文件!') +} +// 上传错误提示 +const excelUploadError = (e): void => { + console.log(e) + message.error('导入数据失败,请您重新上传!') +} + +/** 下载模板操作 */ +const importTemplate = async () => { + try { + const res = await importUserTemplateApi() + download.excel(res, '用户导入模版.xls') + } catch (error) { + console.error(error) + } +} + +/* 弹框按钮操作 */ +// 点击取消 +const cancel = () => { + closeDialog() +} +// 关闭弹窗 +const closeDialog = () => { + showDialog.value = false +} +// 提交上传文件 +const submitFileForm = () => { + uploadRef.value?.submit() +} + +const openForm = () => { + uploadRef.value?.clearFiles() + showDialog.value = true +} +defineExpose({ + openForm +}) +</script> diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 5b286cc7..1c36d376 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -5,31 +5,7 @@ <el-row :gutter="20"> <!--部门数据--> <el-col :span="4" :xs="24"> - <div class="head-container"> - <el-input - v-model="deptName" - placeholder="请输入部门名称" - clearable - style="margin-bottom: 20px" - > - <template #prefix> - <Icon icon="ep:search" /> - </template> - </el-input> - </div> - <div class="head-container"> - <el-tree - :data="deptOptions" - :props="defaultProps" - :expand-on-click-node="false" - :filter-node-method="filterNode" - ref="treeRef" - node-key="id" - default-expand-all - highlight-current - @node-click="handleDeptNodeClick" - /> - </div> + <UserDeptTree @node-click="handleDeptNodeClick" /> </el-col> <!--用户数据--> <el-col :span="20" :xs="24"> @@ -66,10 +42,10 @@ style="width: 240px" > <el-option - v-for="dict in statusDictDatas" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value as number" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value as number" /> </el-select> </el-form-item> @@ -244,51 +220,34 @@ </el-row> </content-wrap> <!-- 添加或修改用户对话框 --> - <AddForm - ref="addEditFormRef" - v-model="showAddDialog" - :dept-options="deptOptions" - :post-options="postOptions" - :form-init-value="addFormInitValue" - @success="getList" - /> + <UserForm ref="userFormRef" @success="getList" /> <!-- 用户导入对话框 --> - <ImportForm v-model="importDialogVisible" @success="getList" /> + <UserImportForm ref="userImportFormRef" @success="getList" /> <!-- 分配角色 --> - <RoleForm - ref="roleFormRef" - v-model:show="roleDialogVisible" - :role-options="roleOptions" - :form-init-value="userRole" - @success="getList" - /> + <UserAssignRoleForm ref="userAssignRoleFormRef" @success="getList" /> </div> </template> <script setup lang="ts" name="User"> -import type { ElTree } from 'element-plus' -import { handleTree, defaultProps } from '@/utils/tree' -// 原vue3版本api方法都是Api结尾觉得见名知义,个人觉得这个可以形成规范 -// TODO 使用 DeptApi 这种形式哈 -import { getSimpleDeptList as getSimpleDeptListApi } from '@/api/system/dept' -import { getSimplePostList as getSimplePostListApi, PostVO } from '@/api/system/post' -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import download from '@/utils/download' +import { parseTime } from '@/utils/formatTime' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' + import { deleteUserApi, exportUserApi, resetUserPwdApi, updateUserStatusApi, + getUserPageApi, UserVO } from '@/api/system/user' -import { parseTime } from './utils' // TODO 可以使用 formatTime 里的方法 -import AddForm from './AddForm.vue' // TODO 改成 UserForm -import ImportForm from './ImportForm.vue' // TODO 改成 UserImportForm -import RoleForm from './RoleForm.vue' // TODO 改成 UserAssignRoleForm -import { getUserApi, getUserPageApi } from '@/api/system/user' -import { getSimpleRoleList as getSimpleRoleListApi } from '@/api/system/role' -import { listUserRolesApi } from '@/api/system/permission' -import { CommonStatusEnum } from '@/utils/constants' -import download from '@/utils/download' + +import UserForm from './components/UserForm.vue' +import UserImportForm from './components/UserImportForm.vue' +import UserAssignRoleForm from './components/UserAssignRoleForm.vue' +import UserDeptTree from './components/UserDeptTree.vue' + const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -302,42 +261,12 @@ const queryParams = reactive({ createTime: [] }) const showSearch = ref(true) -const showAddDialog = ref(false) -// 数据字典- // TODO 可以直接 vue 那 getIntDictOptions,这样一方面少个变量,也可以 getIntDictOptions -const statusDictDatas = getDictOptions(DICT_TYPE.COMMON_STATUS) - -// ========== 创建部门树结构 ========== -// TODO 要不把部门树拆成一个左侧的组件,然后点击后触发 handleDeptNodeClick -const deptName = ref('') -watch( - () => deptName.value, - (val) => { - treeRef.value?.filter(val) - } -) -const deptOptions = ref<Tree[]>([]) // 树形结构 -const treeRef = ref<InstanceType<typeof ElTree>>() -const getTree = async () => { - const res = await getSimpleDeptListApi() - deptOptions.value = [] - deptOptions.value.push(...handleTree(res)) -} -const filterNode = (value: string, data: Tree) => { - if (!value) return true - return data.name.includes(value) -} const handleDeptNodeClick = async (row: { [key: string]: any }) => { queryParams.deptId = row.id getList() } -// 获取岗位列表 -const postOptions = ref<PostVO[]>([]) //岗位列表 -const getPostOptions = async () => { - const res = await getSimplePostListApi() - postOptions.value.push(...res) -} // 用户列表 const userList = ref<UserVO[]>([]) const loading = ref(false) @@ -374,37 +303,30 @@ const resetQuery = () => { } // 添加或编辑 -const addEditFormRef = ref() +const userFormRef = ref() // 添加用户 -// TODO 可以参考别的模块哈,openForm;然后 tree 和 position 可以里面在加载下,让组件自己维护自己哈。 const handleAdd = () => { - addEditFormRef?.value.resetForm() - // 获得下拉数据 - getTree() - // 打开表单,并设置初始化 - showAddDialog.value = true + userFormRef.value?.openForm() } // 用户导入 +const userImportFormRef = ref() const handleImport = () => { - importDialogVisible.value = true + userImportFormRef.value?.openForm() } // 用户导出 -// TODO 改成 await 的风格; const exportLoading = ref(false) const handleExport = () => { message .confirm('是否确认导出所有用户数据项?') - .then(() => { + .then(async () => { // 处理查询参数 let params = { ...queryParams } params.pageNo = 1 params.pageSize = 99999 exportLoading.value = true - return exportUserApi(params) - }) - .then((response) => { + const response = await exportUserApi(params) download.excel(response, '用户数据.xls') }) .catch(() => {}) @@ -435,17 +357,14 @@ const handleCommand = (command: string, index: number, row: UserVO) => { } // 用户状态修改 -// TODO 改成 await 的风格; const handleStatusChange = (row: UserVO) => { let text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' message .confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder')) - .then(function () { + .then(async () => { row.status = row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE - return updateUserStatusApi(row.id, row.status) - }) - .then(() => { + await updateUserStatusApi(row.id, row.status) message.success(text + '成功') // 刷新列表 getList() @@ -457,126 +376,47 @@ const handleStatusChange = (row: UserVO) => { } // 具体数据单行操作 -const addFormInitValue = ref<Recordable>({}) /** 修改按钮操作 */ const handleUpdate = (row: UserVO) => { - addEditFormRef.value?.resetForm() - getTree() - const id = row.id - getUserApi(id).then((response) => { - addFormInitValue.value = response - showAddDialog.value = true - }) + userFormRef.value?.openForm(row) } // 删除用户 -// TODO 改成 await 的风格; const handleDelete = (row: UserVO) => { const ids = row.id message .confirm('是否确认删除用户编号为"' + ids + '"的数据项?') - .then(() => { - return deleteUserApi(ids) - }) - .then(() => { - getList() + .then(async () => { + await deleteUserApi(ids) message.success('删除成功') + getList() + }) + .catch((e) => { + console.error(e) }) - .catch(() => {}) } // 重置密码 -// TODO 改成 await 的风格; const handleResetPwd = (row: UserVO) => { - message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { - resetUserPwdApi(row.id, value) - .then(() => { - message.success('修改成功,新密码是:' + value) - }) - .catch((e) => { - console.error(e) - }) - }) + message + .prompt('请输入"' + row.username + '"的新密码', t('common.reminder')) + .then(async ({ value }) => { + await resetUserPwdApi(row.id, value) + message.success('修改成功,新密码是:' + value) + }) + .catch((e) => { + console.error(e) + }) } // 分配角色 -const roleDialogVisible = ref(false) -const roleOptions = ref() -const userRole = reactive({ - id: 0, - username: '', - nickname: '', - roleIds: [] -}) -const handleRole = async (row: UserVO) => { - addEditFormRef.value?.resetForm() - userRole.id = row.id - userRole.username = row.username - userRole.nickname = row.nickname - - // 获得角色列表 - const roleOpt = await getSimpleRoleListApi() - roleOptions.value = [...roleOpt] - - // 获得角色拥有的菜单集合 - const roles = await listUserRolesApi(row.id) - userRole.roleIds = roles - - roleDialogVisible.value = true +const userAssignRoleFormRef = ref() +const handleRole = (row: UserVO) => { + userAssignRoleFormRef.value?.openForm(row) } -/* 用户导入 */ -const importDialogVisible = ref(false) - // ========== 初始化 ========== onMounted(async () => { getList() - await getPostOptions() - await getTree() }) - -const parseTime = (time) => { - if (!time) { - return null - } - const format = '{y}-{m}-{d} {h}:{i}:{s}' - let date - if (typeof time === 'object') { - date = time - } else { - if (typeof time === 'string' && /^[0-9]+$/.test(time)) { - time = parseInt(time) - } else if (typeof time === 'string') { - time = time - .replace(new RegExp(/-/gm), '/') - .replace('T', ' ') - .replace(new RegExp(/\.[\d]{3}/gm), '') - } - if (typeof time === 'number' && time.toString().length === 10) { - time = time * 1000 - } - date = new Date(time) - } - const formatObj = { - y: date.getFullYear(), - m: date.getMonth() + 1, - d: date.getDate(), - h: date.getHours(), - i: date.getMinutes(), - s: date.getSeconds(), - a: date.getDay() - } - const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { - let value = formatObj[key] - // Note: getDay() returns 0 on Sunday - if (key === 'a') { - return ['日', '一', '二', '三', '四', '五', '六'][value] - } - if (result.length > 0 && value < 10) { - value = '0' + value - } - return value || 0 - }) - return time_str -} </script> diff --git a/src/views/system/user/index0.vue b/src/views/system/user/index0.vue deleted file mode 100644 index 2f9ba9b0..00000000 --- a/src/views/system/user/index0.vue +++ /dev/null @@ -1,576 +0,0 @@ -<template> - <div class="flex"> - <el-card class="w-1/5 user" :gutter="12" shadow="always"> - <template #header> - <div class="card-header"> - <span>部门列表</span> - <XTextButton title="修改部门" @click="handleDeptEdit()" /> - </div> - </template> - <el-input v-model="filterText" placeholder="搜索部门" /> - <el-scrollbar height="650"> - <el-tree - ref="treeRef" - node-key="id" - default-expand-all - :data="deptOptions" - :props="defaultProps" - :highlight-current="true" - :filter-node-method="filterNode" - :expand-on-click-node="false" - @node-click="handleDeptNodeClick" - /> - </el-scrollbar> - </el-card> - <el-card class="w-4/5 user" style="margin-left: 10px" :gutter="12" shadow="hover"> - <template #header> - <div class="card-header"> - <span>{{ tableTitle }}</span> - </div> - </template> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:user:create']" - @click="handleCreate()" - /> - <!-- 操作:导入用户 --> - <XButton - type="warning" - preIcon="ep:upload" - :title="t('action.import')" - v-hasPermi="['system:user:import']" - @click="importDialogVisible = true" - /> - <!-- 操作:导出用户 --> - <XButton - type="warning" - preIcon="ep:download" - :title="t('action.export')" - v-hasPermi="['system:user:export']" - @click="exportList('用户数据.xls')" - /> - </template> - <template #status_default="{ row }"> - <el-switch - v-model="row.status" - :active-value="0" - :inactive-value="1" - @change="handleStatusChange(row)" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:编辑 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:user:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:user:update']" - @click="handleDetail(row.id)" - /> - <el-dropdown - class="p-0.5" - v-hasPermi="[ - 'system:user:update-password', - 'system:permission:assign-user-role', - 'system:user:delete' - ]" - > - <XTextButton :title="t('action.more')" postIcon="ep:arrow-down" /> - <template #dropdown> - <el-dropdown-menu> - <el-dropdown-item> - <!-- 操作:重置密码 --> - <XTextButton - preIcon="ep:key" - title="重置密码" - v-hasPermi="['system:user:update-password']" - @click="handleResetPwd(row)" - /> - </el-dropdown-item> - <el-dropdown-item> - <!-- 操作:分配角色 --> - <XTextButton - preIcon="ep:key" - title="分配角色" - v-hasPermi="['system:permission:assign-user-role']" - @click="handleRole(row)" - /> - </el-dropdown-item> - <el-dropdown-item> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:user:delete']" - @click="deleteData(row.id)" - /> - </el-dropdown-item> - </el-dropdown-menu> - </template> - </el-dropdown> - </template> - </XTable> - </el-card> - </div> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :rules="rules" - :schema="allSchemas.formSchema" - ref="formRef" - > - <template #deptId="form"> - <el-tree-select - node-key="id" - v-model="form['deptId']" - :props="defaultProps" - :data="deptOptions" - check-strictly - /> - </template> - <template #postIds="form"> - <el-select v-model="form['postIds']" multiple :placeholder="t('common.selectText')"> - <el-option - v-for="item in postOptions" - :key="item.id" - :label="item.name" - :value="(item.id as unknown as number)" - /> - </el-select> - </template> - </Form> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - > - <template #deptId="{ row }"> - <el-tag>{{ dataFormater(row.deptId) }}</el-tag> - </template> - <template #postIds="{ row }"> - <template v-if="row.postIds !== ''"> - <el-tag v-for="(post, index) in row.postIds" :key="index" index=""> - <template v-for="postObj in postOptions"> - {{ post === postObj.id ? postObj.name : '' }} - </template> - </el-tag> - </template> - <template v-else> </template> - </template> - </Descriptions> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="loading" - @click="submitForm()" - /> - <!-- 按钮:关闭 --> - <XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> - <!-- 分配用户角色 --> - <XModal v-model="roleDialogVisible" title="分配角色"> - <el-form :model="userRole" label-width="140px" :inline="true"> - <el-form-item label="用户名称"> - <el-tag>{{ userRole.username }}</el-tag> - </el-form-item> - <el-form-item label="用户昵称"> - <el-tag>{{ userRole.nickname }}</el-tag> - </el-form-item> - <el-form-item label="角色"> - <el-transfer - v-model="userRole.roleIds" - :titles="['角色列表', '已选择']" - :props="{ - key: 'id', - label: 'name' - }" - :data="roleOptions" - /> - </el-form-item> - </el-form> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton type="primary" :title="t('action.save')" :loading="loading" @click="submitRole()" /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="roleDialogVisible = false" /> - </template> - </XModal> - <!-- 导入 --> - <XModal v-model="importDialogVisible" :title="importDialogTitle"> - <el-form class="drawer-multiColumn-form" label-width="150px"> - <el-form-item label="模板下载 :"> - <XButton type="primary" prefix="ep:download" title="点击下载" @click="handleImportTemp()" /> - </el-form-item> - <el-form-item label="文件上传 :"> - <el-upload - ref="uploadRef" - :action="updateUrl + '?updateSupport=' + updateSupport" - :headers="uploadHeaders" - :drag="true" - :limit="1" - :multiple="true" - :show-file-list="true" - :disabled="uploadDisabled" - :before-upload="beforeExcelUpload" - :on-exceed="handleExceed" - :on-success="handleFileSuccess" - :on-error="excelUploadError" - :auto-upload="false" - accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - > - <Icon icon="ep:upload-filled" /> - <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> - <template #tip> - <div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div> - </template> - </el-upload> - </el-form-item> - <el-form-item label="是否更新已经存在的用户数据:"> - <el-checkbox v-model="updateSupport" /> - </el-form-item> - </el-form> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="warning" - preIcon="ep:upload-filled" - :title="t('action.save')" - @click="submitFileForm()" - /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="importDialogVisible = false" /> - </template> - </XModal> -</template> -<script setup lang="ts" name="User"> -import type { ElTree, UploadRawFile, UploadInstance } from 'element-plus' -import { handleTree, defaultProps } from '@/utils/tree' -import download from '@/utils/download' -import { CommonStatusEnum } from '@/utils/constants' -import { getAccessToken, getTenantId } from '@/utils/auth' -import type { FormExpose } from '@/components/Form' -import { rules, allSchemas } from './user.data' -import * as UserApi from '@/api/system/user' -import { listSimpleDeptApi } from '@/api/system/dept' -import { listSimpleRolesApi } from '@/api/system/role' -import { listSimplePostsApi, PostVO } from '@/api/system/post' -import { - aassignUserRoleApi, - listUserRolesApi, - PermissionAssignUserRoleReqVO -} from '@/api/system/permission' - -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -const queryParams = reactive({ - deptId: null -}) -// ========== 列表相关 ========== -const tableTitle = ref('用户列表') -// 列表相关的变量 -const [registerTable, { reload, deleteData, exportList }] = useXTable({ - allSchemas: allSchemas, - params: queryParams, - getListApi: UserApi.getUserPageApi, - deleteApi: UserApi.deleteUserApi, - exportListApi: UserApi.exportUserApi -}) -// ========== 创建部门树结构 ========== -const filterText = ref('') -const deptOptions = ref<Tree[]>([]) // 树形结构 -const treeRef = ref<InstanceType<typeof ElTree>>() -const getTree = async () => { - const res = await listSimpleDeptApi() - deptOptions.value.push(...handleTree(res)) -} -const filterNode = (value: string, data: Tree) => { - if (!value) return true - return data.name.includes(value) -} -const handleDeptNodeClick = async (row: { [key: string]: any }) => { - queryParams.deptId = row.id - await reload() -} -const { push } = useRouter() -const handleDeptEdit = () => { - push('/system/dept') -} -watch(filterText, (val) => { - treeRef.value!.filter(val) -}) -// ========== CRUD 相关 ========== -const loading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref -const postOptions = ref<PostVO[]>([]) //岗位列表 - -// 获取岗位列表 -const getPostOptions = async () => { - const res = await listSimplePostsApi() - postOptions.value.push(...res) -} -const dataFormater = (val) => { - return deptFormater(deptOptions.value, val) -} -//部门回显 -const deptFormater = (ary, val: any) => { - var o = '' - if (ary && val) { - for (const v of ary) { - if (v.id == val) { - o = v.name - if (o) return o - } else if (v.children?.length) { - o = deptFormater(v.children, val) - if (o) return o - } - } - return o - } else { - return val - } -} - -// 设置标题 -const setDialogTile = async (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} - -// 新增操作 -const handleCreate = async () => { - setDialogTile('create') - // 重置表单 - await nextTick() - if (allSchemas.formSchema[0].field !== 'username') { - unref(formRef)?.addSchema( - { - field: 'username', - label: '用户账号', - component: 'Input' - }, - 0 - ) - unref(formRef)?.addSchema( - { - field: 'password', - label: '用户密码', - component: 'InputPassword' - }, - 1 - ) - } -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - await nextTick() - unref(formRef)?.delSchema('username') - unref(formRef)?.delSchema('password') - // 设置数据 - const res = await UserApi.getUserApi(rowId) - unref(formRef)?.setValues(res) -} -const detailData = ref() - -// 详情操作 -const handleDetail = async (rowId: number) => { - // 设置数据 - const res = await UserApi.getUserApi(rowId) - detailData.value = res - await setDialogTile('detail') -} - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - // 提交请求 - try { - const data = unref(formRef)?.formModel as UserApi.UserVO - if (actionType.value === 'create') { - loading.value = true - await UserApi.createUserApi(data) - message.success(t('common.createSuccess')) - } else { - loading.value = true - await UserApi.updateUserApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - // unref(formRef)?.setSchema(allSchemas.formSchema) - // 刷新列表 - await reload() - loading.value = false - } - } - }) -} -// 改变用户状态操作 -const handleStatusChange = async (row: UserApi.UserVO) => { - const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' - message - .confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder')) - .then(async () => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE - await UserApi.updateUserStatusApi(row.id, row.status) - message.success(text + '成功') - // 刷新列表 - await reload() - }) - .catch(() => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE - }) -} -// 重置密码 -const handleResetPwd = (row: UserApi.UserVO) => { - message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { - UserApi.resetUserPwdApi(row.id, value).then(() => { - message.success('修改成功,新密码是:' + value) - }) - }) -} -// 分配角色 -const roleDialogVisible = ref(false) -const roleOptions = ref() -const userRole = reactive({ - id: 0, - username: '', - nickname: '', - roleIds: [] -}) -const handleRole = async (row: UserApi.UserVO) => { - userRole.id = row.id - userRole.username = row.username - userRole.nickname = row.nickname - // 获得角色拥有的权限集合 - const roles = await listUserRolesApi(row.id) - userRole.roleIds = roles - // 获取角色列表 - const roleOpt = await listSimpleRolesApi() - roleOptions.value = roleOpt - roleDialogVisible.value = true -} -// 提交 -const submitRole = async () => { - const data = ref<PermissionAssignUserRoleReqVO>({ - userId: userRole.id, - roleIds: userRole.roleIds - }) - await aassignUserRoleApi(data.value) - message.success(t('common.updateSuccess')) - roleDialogVisible.value = false -} -// ========== 导入相关 ========== -// TODO @星语:这个要不要把导入用户,封装成一个小组件?可选哈 -const importDialogVisible = ref(false) -const uploadDisabled = ref(false) -const importDialogTitle = ref('用户导入') -const updateSupport = ref(0) -let updateUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' -const uploadHeaders = ref() -// 下载导入模版 -const handleImportTemp = async () => { - const res = await UserApi.importUserTemplateApi() - download.excel(res, '用户导入模版.xls') -} -// 文件上传之前判断 -const beforeExcelUpload = (file: UploadRawFile) => { - const isExcel = - file.type === 'application/vnd.ms-excel' || - file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - const isLt5M = file.size / 1024 / 1024 < 5 - if (!isExcel) message.error('上传文件只能是 xls / xlsx 格式!') - if (!isLt5M) message.error('上传文件大小不能超过 5MB!') - return isExcel && isLt5M -} -// 文件上传 -const uploadRef = ref<UploadInstance>() -const submitFileForm = () => { - uploadHeaders.value = { - Authorization: 'Bearer ' + getAccessToken(), - 'tenant-id': getTenantId() - } - uploadDisabled.value = true - uploadRef.value!.submit() -} -// 文件上传成功 -const handleFileSuccess = async (response: any): Promise<void> => { - if (response.code !== 0) { - message.error(response.msg) - return - } - importDialogVisible.value = false - uploadDisabled.value = false - const data = response.data - let text = '上传成功数量:' + data.createUsernames.length + ';' - for (let username of data.createUsernames) { - text += '< ' + username + ' >' - } - text += '更新成功数量:' + data.updateUsernames.length + ';' - for (const username of data.updateUsernames) { - text += '< ' + username + ' >' - } - text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' - for (const username in data.failureUsernames) { - text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' - } - message.alert(text) - await reload() -} -// 文件数超出提示 -const handleExceed = (): void => { - message.error('最多只能上传一个文件!') -} -// 上传错误提示 -const excelUploadError = (): void => { - message.error('导入数据失败,请您重新上传!') -} -// ========== 初始化 ========== -onMounted(async () => { - await getPostOptions() - await getTree() -}) -</script> - -<style scoped> -.user { - height: 780px; - max-height: 800px; -} -.card-header { - display: flex; - justify-content: space-between; - align-items: center; -} -</style> diff --git a/src/views/system/user/utils.ts b/src/views/system/user/utils.ts deleted file mode 100644 index 6473c2c9..00000000 --- a/src/views/system/user/utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -export const parseTime = (time) => { - if (!time) { - return null - } - const format = '{y}-{m}-{d} {h}:{i}:{s}' - let date - if (typeof time === 'object') { - date = time - } else { - if (typeof time === 'string' && /^[0-9]+$/.test(time)) { - time = parseInt(time) - } else if (typeof time === 'string') { - time = time - .replace(new RegExp(/-/gm), '/') - .replace('T', ' ') - .replace(new RegExp(/\.[\d]{3}/gm), '') - } - if (typeof time === 'number' && time.toString().length === 10) { - time = time * 1000 - } - date = new Date(time) - } - const formatObj = { - y: date.getFullYear(), - m: date.getMonth() + 1, - d: date.getDate(), - h: date.getHours(), - i: date.getMinutes(), - s: date.getSeconds(), - a: date.getDay() - } - const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { - let value = formatObj[key] - // Note: getDay() returns 0 on Sunday - if (key === 'a') { - return ['日', '一', '二', '三', '四', '五', '六'][value] - } - if (result.length > 0 && value < 10) { - value = '0' + value - } - return value || 0 - }) - return time_str -} From 3c6d96d9f03f729be2930d857237c2c3d581539d Mon Sep 17 00:00:00 2001 From: fengjingtao <fessor@139.com> Date: Wed, 29 Mar 2023 23:28:23 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=E6=81=A2=E5=A4=8D=E6=9F=90=E4=BA=9B?= =?UTF-8?q?=E4=B8=8D=E4=B8=8A=E4=BC=A0git=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 22 +--------------------- src/types/auto-components.d.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3e9f1774..38cc3052 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,25 +40,5 @@ "i18n-ally.displayLanguage": "zh-CN", "i18n-ally.enabledFrameworks": ["vue", "react"], "god.tsconfig": "./tsconfig.json", - "vue-i18n.i18nPaths": "src/locales", - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#65c89b", - "activityBar.background": "#65c89b", - "activityBar.foreground": "#15202b", - "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#945bc4", - "activityBarBadge.foreground": "#e7e7e7", - "commandCenter.border": "#15202b99", - "sash.hoverBorder": "#65c89b", - "statusBar.background": "#42b883", - "statusBar.foreground": "#15202b", - "statusBarItem.hoverBackground": "#359268", - "statusBarItem.remoteBackground": "#42b883", - "statusBarItem.remoteForeground": "#15202b", - "titleBar.activeBackground": "#42b883", - "titleBar.activeForeground": "#15202b", - "titleBar.inactiveBackground": "#42b88399", - "titleBar.inactiveForeground": "#15202b99" - }, - "peacock.color": "#42b883" + "vue-i18n.i18nPaths": "src/locales" } diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 8fcd30dd..480691fc 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -52,12 +52,17 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] + ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] + ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] + ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] @@ -67,7 +72,11 @@ declare module '@vue/runtime-core' { ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTimeline: typeof import('element-plus/es')['ElTimeline'] + ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] + ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload']