refactor: user

This commit is contained in:
xingyu
2022-11-15 23:44:08 +08:00
parent 09e61f7ef3
commit 7db04cf066
4 changed files with 194 additions and 261 deletions

View File

@ -1,8 +1,38 @@
import request from '@/config/axios' import request from '@/config/axios'
import type { UserVO } from './types' export type UserVO = {
id: number
username: string
nickname: string
deptId: number
postIds: string[]
email: string
mobile: string
sex: number
avatar: string
loginIp: string
status: number
remark: string
loginDate: string
createTime: string
}
export interface UserPageReqVO extends PageParam {
deptId?: number
username?: string
mobile?: string
status?: number
createTime?: string[]
}
export interface UserExportReqVO {
code?: string
name?: string
status?: number
createTime?: string[]
}
// 查询用户管理列表 // 查询用户管理列表
export const getUserPageApi = (params) => { export const getUserPageApi = (params: UserPageReqVO) => {
return request.get({ url: '/system/user/page', params }) return request.get({ url: '/system/user/page', params })
} }
@ -27,7 +57,7 @@ export const deleteUserApi = (id: number) => {
} }
// 导出用户 // 导出用户
export const exportUserApi = (params) => { export const exportUserApi = (params: UserExportReqVO) => {
return request.download({ url: '/system/user/export', params }) return request.download({ url: '/system/user/export', params })
} }

View File

@ -1,16 +0,0 @@
export type UserVO = {
id: number
username: string
nickname: string
deptId: number
postIds: string[]
email: string
mobile: string
sex: number
avatar: string
loginIp: string
status: number
remark: string
loginDate: string
createTime: string
}

View File

@ -20,56 +20,38 @@
@node-click="handleDeptNodeClick" @node-click="handleDeptNodeClick"
/> />
</el-card> </el-card>
<!-- 搜索工作区 -->
<el-card class="w-4/5 user" style="margin-left: 10px" :gutter="12" shadow="hover"> <el-card class="w-4/5 user" style="margin-left: 10px" :gutter="12" shadow="hover">
<template #header> <template #header>
<div class="card-header"> <div class="card-header">
<span>{{ tableTitle }}</span> <span>{{ tableTitle }}</span>
</div> </div>
</template> </template>
<Search
:schema="allSchemas.searchSchema"
@search="setSearchParams"
@reset="setSearchParams"
ref="searchForm"
/>
<!-- 操作工具栏 -->
<div class="mb-10px">
<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')"
/>
</div>
<!-- 列表 --> <!-- 列表 -->
<Table <vxe-grid ref="xGrid" v-bind="gridOptions" class="xtable-scrollbar">
:columns="allSchemas.tableColumns" <!-- 操作新增 -->
:selection="false" <template #toolbar_buttons>
:data="tableObject.tableList" <XButton
:loading="tableObject.loading" type="primary"
:pagination="{ preIcon="ep:zoom-in"
total: tableObject.total :title="t('action.add')"
}" v-hasPermi="['system:user:create']"
v-model:pageSize="tableObject.pageSize" @click="handleCreate()"
v-model:currentPage="tableObject.currentPage" />
@register="register" <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="{ row }"> <template #status="{ row }">
<el-switch <el-switch
v-model="row.status" v-model="row.status"
@ -78,10 +60,7 @@
@change="handleStatusChange(row)" @change="handleStatusChange(row)"
/> />
</template> </template>
<template #loginDate="{ row }"> <template #actionbtns_default="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<XTextButton <XTextButton
preIcon="ep:edit" preIcon="ep:edit"
:title="t('action.edit')" :title="t('action.edit')"
@ -112,10 +91,10 @@
preIcon="ep:delete" preIcon="ep:delete"
:title="t('action.del')" :title="t('action.del')"
v-hasPermi="['system:user:delete']" v-hasPermi="['system:user:delete']"
@click="delList(row.id, false)" @click="handleDelete(row.id)"
/> />
</template> </template>
</Table> </vxe-grid>
</el-card> </el-card>
</div> </div>
<XModal v-model="dialogVisible" :title="dialogTitle"> <XModal v-model="dialogVisible" :title="dialogTitle">
@ -150,7 +129,7 @@
<Descriptions <Descriptions
v-if="actionType === 'detail'" v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema" :schema="allSchemas.detailSchema"
:data="detailRef" :data="detailData"
> >
<template #deptId="{ row }"> <template #deptId="{ row }">
<span>{{ row.dept?.name }}</span> <span>{{ row.dept?.name }}</span>
@ -200,10 +179,16 @@
</el-form> </el-form>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<template #footer> <template #footer>
<el-button type="primary" :loading="loading" @click="submitRole"> <!-- 按钮保存 -->
{{ t('action.save') }} <XButton
</el-button> v-if="['create', 'update'].includes(actionType)"
<el-button @click="roleDialogVisible = false">{{ t('dialog.close') }}</el-button> type="primary"
:title="t('action.save')"
:loading="loading"
@click="submitRole()"
/>
<!-- 按钮关闭 -->
<XButton :title="t('dialog.close')" @click="roleDialogVisible = false" />
</template> </template>
</XModal> </XModal>
<!-- 导入 --> <!-- 导入 -->
@ -244,17 +229,20 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button type="primary" @click="submitFileForm"> <!-- 按钮保存 -->
<Icon icon="ep:upload-filled" /> <XButton
{{ t('action.save') }} type="warning"
</el-button> preIcon="ep:upload-filled"
<el-button @click="importDialogVisible = false">{{ t('dialog.close') }}</el-button> :title="t('action.save')"
@click="submitFileForm()"
/>
<!-- 按钮关闭 -->
<XButton :title="t('dialog.close')" @click="importDialogVisible = false" />
</template> </template>
</XModal> </XModal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, onMounted, reactive, ref, unref, watch } from 'vue' import { onMounted, reactive, ref, unref, watch } from 'vue'
import dayjs from 'dayjs'
import { import {
ElTag, ElTag,
ElInput, ElInput,
@ -274,9 +262,7 @@ import {
} from 'element-plus' } from 'element-plus'
import { handleTree } from '@/utils/tree' import { handleTree } from '@/utils/tree'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { useTable } from '@/hooks/web/useTable'
import { FormExpose } from '@/components/Form' import { FormExpose } from '@/components/Form'
import type { UserVO } from '@/api/system/user/types'
import type { PostVO } from '@/api/system/post' import type { PostVO } from '@/api/system/post'
import type { PermissionAssignUserRoleReqVO } from '@/api/system/permission/types' import type { PermissionAssignUserRoleReqVO } from '@/api/system/permission/types'
import { listSimpleDeptApi } from '@/api/system/dept' import { listSimpleDeptApi } from '@/api/system/dept'
@ -290,29 +276,31 @@ import { useRouter } from 'vue-router'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import { getAccessToken, getTenantId } from '@/utils/auth' import { getAccessToken, getTenantId } from '@/utils/auth'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
import { VxeGridInstance } from 'vxe-table'
import { useVxeGrid } from '@/hooks/web/useVxeGrid'
const message = useMessage() const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const defaultProps = { const defaultProps = {
children: 'children', children: 'children',
label: 'name', label: 'name',
value: 'id' value: 'id'
} }
const { t } = useI18n() // 国际化
// ========== 列表相关 ========== // ========== 列表相关 ==========
const tableTitle = ref('用户列表') const tableTitle = ref('用户列表')
const { register, tableObject, methods } = useTable<UserVO>({ // 列表相关的变量
const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
const { gridOptions, reloadList, delList, exportList, getSearchData } = useVxeGrid<UserApi.UserVO>({
allSchemas: allSchemas,
getListApi: UserApi.getUserPageApi, getListApi: UserApi.getUserPageApi,
delListApi: UserApi.deleteUserApi, delListApi: UserApi.deleteUserApi,
exportListApi: UserApi.exportUserApi exportListApi: UserApi.exportUserApi
}) })
const { getList, setSearchParams, delList, exportList } = methods
// ========== 创建部门树结构 ========== // ========== 创建部门树结构 ==========
const filterText = ref('') const filterText = ref('')
const deptOptions = ref<any[]>([]) // 树形结构 const deptOptions = ref<any[]>([]) // 树形结构
const searchForm = ref<FormExpose>()
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
const getTree = async () => { const getTree = async () => {
const res = await listSimpleDeptApi() const res = await listSimpleDeptApi()
@ -322,12 +310,14 @@ const filterNode = (value: string, data: Tree) => {
if (!value) return true if (!value) return true
return data.name.includes(value) return data.name.includes(value)
} }
const handleDeptNodeClick = (data: { [key: string]: any }) => { const handleDeptNodeClick = async (row: { [key: string]: any }) => {
tableObject.params = { tableTitle.value = row.name
deptId: data.id console.log(getSearchData(xGrid))
} // gridOptions.formConfig?.data.push({
tableTitle.value = data.name // deptId: row.id
methods.getList() // })
// TODO 查询
await reloadList(xGrid)
} }
const { push } = useRouter() const { push } = useRouter()
const handleDeptEdit = () => { const handleDeptEdit = () => {
@ -366,8 +356,6 @@ const handleCreate = async () => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = t('action.create') dialogTitle.value = t('action.create')
actionType.value = 'create' actionType.value = 'create'
await nextTick()
unref(formRef)?.getElFormRef().resetFields()
} }
// 修改操作 // 修改操作
@ -381,13 +369,24 @@ const handleUpdate = async (rowId: number) => {
postIds.value = res.postIds postIds.value = res.postIds
unref(formRef)?.setValues(res) unref(formRef)?.setValues(res)
} }
const detailData = ref()
// 详情操作
const handleDetail = async (row: UserApi.UserVO) => {
// 设置数据
detailData.value = row
await setDialogTile('detail')
}
// 删除操作
const handleDelete = async (rowId: number) => {
await delList(xGrid, rowId)
}
// 提交按钮 // 提交按钮
const submitForm = async () => { const submitForm = async () => {
loading.value = true loading.value = true
// 提交请求 // 提交请求
try { try {
const data = unref(formRef)?.formModel as UserVO const data = unref(formRef)?.formModel as UserApi.UserVO
data.deptId = deptId.value data.deptId = deptId.value
data.postIds = postIds.value data.postIds = postIds.value
if (actionType.value === 'create') { if (actionType.value === 'create') {
@ -397,15 +396,15 @@ const submitForm = async () => {
await UserApi.updateUserApi(data) await UserApi.updateUserApi(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} }
// 操作成功,重新加载列表
dialogVisible.value = false dialogVisible.value = false
await getList()
} finally { } finally {
loading.value = false loading.value = false
// 刷新列表
reloadList(xGrid)
} }
} }
// 改变用户状态操作 // 改变用户状态操作
const handleStatusChange = async (row: UserVO) => { const handleStatusChange = async (row: UserApi.UserVO) => {
const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
message message
.confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder')) .confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder'))
@ -414,7 +413,8 @@ const handleStatusChange = async (row: UserVO) => {
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE
await UserApi.updateUserStatusApi(row.id, row.status) await UserApi.updateUserStatusApi(row.id, row.status)
message.success(text + '成功') message.success(text + '成功')
await getList() // 刷新列表
reloadList(xGrid)
}) })
.catch(() => { .catch(() => {
row.status = row.status =
@ -422,7 +422,7 @@ const handleStatusChange = async (row: UserVO) => {
}) })
} }
// 重置密码 // 重置密码
const handleResetPwd = (row: UserVO) => { const handleResetPwd = (row: UserApi.UserVO) => {
message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => {
UserApi.resetUserPwdApi(row.id, value).then(() => { UserApi.resetUserPwdApi(row.id, value).then(() => {
message.success('修改成功,新密码是:' + value) message.success('修改成功,新密码是:' + value)
@ -438,7 +438,7 @@ const userRole = reactive({
nickname: '', nickname: '',
roleIds: [] roleIds: []
}) })
const handleRole = async (row: UserVO) => { const handleRole = async (row: UserApi.UserVO) => {
userRole.id = row.id userRole.id = row.id
userRole.username = row.username userRole.username = row.username
userRole.nickname = row.nickname userRole.nickname = row.nickname
@ -460,15 +460,6 @@ const submitRole = async () => {
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
roleDialogVisible.value = false roleDialogVisible.value = false
} }
// ========== 详情相关 ==========
const detailRef = ref()
// 详情操作
const handleDetail = async (row: UserVO) => {
// 设置数据
detailRef.value = row
await setDialogTile('detail')
}
// ========== 导入相关 ========== // ========== 导入相关 ==========
const importDialogVisible = ref(false) const importDialogVisible = ref(false)
const uploadDisabled = ref(false) const uploadDisabled = ref(false)
@ -523,7 +514,7 @@ const handleFileSuccess = (response: any): void => {
text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
} }
message.alert(text) message.alert(text)
getList() reloadList(xGrid)
} }
// 文件数超出提示 // 文件数超出提示
const handleExceed = (): void => { const handleExceed = (): void => {
@ -536,7 +527,6 @@ const excelUploadError = (): void => {
// ========== 初始化 ========== // ========== 初始化 ==========
onMounted(async () => { onMounted(async () => {
await getPostOptions() await getPostOptions()
await getList()
await getTree() await getTree()
}) })
</script> </script>

View File

@ -2,7 +2,7 @@ import { reactive } from 'vue'
import { required } from '@/utils/formRules' import { required } from '@/utils/formRules'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas' import { VxeCrudSchema, useVxeCrudSchemas } from '@/hooks/web/useVxeCrudSchemas'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
// 表单校验 // 表单校验
@ -21,157 +21,86 @@ export const rules = reactive({
] ]
}) })
// crudSchemas // crudSchemas
const crudSchemas = reactive<CrudSchema[]>([ const crudSchemas = reactive<VxeCrudSchema>({
{ primaryKey: 'id',
label: t('common.index'), primaryType: 'seq',
field: 'id', action: true,
type: 'index', actionWidth: '400px',
form: { columns: [
show: false {
title: '用户账号',
field: 'username',
isSearch: true
}, },
detail: { {
show: false title: '用户密码',
} field: 'password',
}, form: {
{ component: 'InputPassword'
label: '用户账号', },
field: 'username', isDetail: false
search: {
show: true
}
},
{
label: '用户密码',
field: 'password',
form: {
component: 'InputPassword'
}, },
detail: { {
show: false title: '用户昵称',
} field: 'nickname'
},
{
label: '用户昵称',
field: 'nickname'
},
{
label: '用户邮箱',
field: 'email'
},
{
label: '手机号码',
field: 'mobile',
search: {
show: true
}
},
{
label: '部门',
field: 'deptId',
table: {
show: false
}
},
{
label: '岗位',
field: 'postIds',
table: {
show: false
}
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
search: {
show: true
}
},
{
label: '最后登录时间',
field: 'loginDate',
form: {
show: false
}
},
{
label: '最后登录IP',
field: 'loginIp',
table: {
show: false
}, },
form: { {
show: false title: '用户邮箱',
} field: 'email'
},
{
label: t('form.remark'),
field: 'remark',
table: {
show: false
}
},
{
label: t('common.createTime'),
field: 'createTime',
table: {
show: false
}, },
form: { {
show: false title: '手机号码',
field: 'mobile',
isSearch: true
}, },
detail: { {
show: false title: '部门',
field: 'deptId',
isTable: false,
search: {
visible: false
}
}, },
search: { {
show: true, title: '岗位',
component: 'DatePicker', field: 'postIds',
componentProps: { isTable: false
type: 'datetimerange', },
valueFormat: 'YYYY-MM-DD HH:mm:ss', {
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)], title: t('common.status'),
shortcuts: [ field: 'status',
{ dictType: DICT_TYPE.COMMON_STATUS,
text: '近一周', isSearch: true
value: () => { },
const end = new Date() {
const start = new Date() title: '最后登录时间',
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7) field: 'loginDate',
return [start, end] isForm: false
} },
}, {
{ title: '最后登录IP',
text: '近一个月', field: 'loginIp',
value: () => { isTable: false,
const end = new Date() isForm: false
const start = new Date() },
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30) {
return [start, end] title: t('form.remark'),
} field: 'remark',
}, isTable: false
{ },
text: '近三个月', {
value: () => { title: t('common.createTime'),
const end = new Date() field: 'createTime',
const start = new Date() formatter: 'formatDate',
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90) isTable: false,
return [start, end] isForm: false,
} search: {
} show: true,
] itemRender: {
name: 'XDataTimePicker'
}
} }
} }
}, ]
{ })
field: 'action', export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
width: '400px',
label: t('table.action'),
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)