feat 用户操作

This commit is contained in:
DaxPay
2024-07-12 18:02:46 +08:00
parent f7c28a29db
commit 85341d1a1c
23 changed files with 998 additions and 61 deletions

View File

@@ -13,6 +13,7 @@
ref="formRef"
:model="form"
:rules="rules"
validate-trigger="['blur', 'change']"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>

View File

@@ -13,6 +13,7 @@
ref="formRef"
:model="form"
:rules="rules"
validate-trigger="['blur', 'change']"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>

View File

@@ -0,0 +1,60 @@
import { defHttp } from '@/utils/http/axios'
import { PageResult, Result } from '#/axios'
import { BaseEntity } from '#/web'
/**
* 分页
*/
export const page = (params) => {
return defHttp.get<Result<PageResult<LoginLog>>>({
url: '/log/login/page',
params,
})
}
/**
* 获取单条
*/
export const get = (id) => {
return defHttp.get<Result<LoginLog>>({
url: '/log/login/findById',
params: { id },
})
}
/**
* 清除指定天数之前的日志
*/
export const deleteByDay = (deleteDay) => {
return defHttp.delete<Result>({
url: '/log/login/deleteByDay',
params: { deleteDay },
})
}
/**
* 登陆日志
*/
export interface LoginLog extends BaseEntity {
// 用户id
userId: number
// 用户名称
account: string
// 登录成功状态
login: boolean
// 终端
client: string
// 登录方式
loginType: string
// 登录IP地址
ip: string
// 登录地点
loginLocation: string
// 操作系统
os: string
// 浏览器类型
browser: string
// 提示消息
msg: string
// 访问时间
loginTime: string
}

View File

@@ -0,0 +1,68 @@
<template>
<basic-modal
v-bind="$attrs"
:loading="confirmLoading"
width="50%"
title="查看"
:open="visible"
@cancel="visible = false"
>
<description :column="2" :data="data" :schema="schema" />
<template #footer>
<a-button key="cancel" @click="visible = false">取消</a-button>
</template>
</basic-modal>
</template>
<script lang="ts" setup>
import { Description, DescItem } from '@/components/Description'
import { get, LoginLog } from './LoginLog.api'
import BasicModal from '@/components/Modal/src/BasicModal.vue'
import { findOneByField } from '@/utils/dataUtil'
import { Client, findAll } from '@/views/iam/client/Client.api'
import { onMounted, ref } from 'vue'
onMounted(() => {
initClients()
})
let clients = ref<Client[]>([])
let data = ref<LoginLog>()
let visible = ref(false)
let confirmLoading = ref(false)
let schema = [
{ field: 'account', label: '登录账号' },
{ field: 'login', label: '登录成功状态', render: (curVal) => (curVal ? '成功' : '失败') },
{ field: 'client', label: '登录终端', render: (curVal) => getClient(curVal) },
{ field: 'ip', label: '登录IP地址' },
{ field: 'browser', label: '浏览器类型' },
{ field: 'os', label: '操作系统' },
{ field: 'msg', label: '提示消息' },
{ field: 'loginTime', label: '访问时间' },
] as DescItem[]
function show(id) {
visible.value = true
confirmLoading.value = true
get(id).then((res) => {
data.value = res.data
confirmLoading.value = false
})
}
async function initClients() {
const { data } = await findAll()
clients.value = data
}
// 获取终端信息
function getClient(code) {
return findOneByField(clients.value, code, 'code')?.['name']
}
defineExpose({
show,
})
</script>
<style scoped></style>

View File

@@ -0,0 +1,185 @@
<template>
<div>
<div class="m-3 p-3 pt-5 bg-white">
<b-query
:query-params="model.queryParam"
:fields="fields"
@query="queryPage"
@reset="resetQueryParams"
/>
</div>
<div class="m-3 p-3 bg-white">
<vxe-toolbar ref="xToolbar" custom :refresh="{ queryMethod: queryPage }">
<template #buttons>
<a-space>
<a-select
style="width: 180px"
v-model:value="deleteDay"
:options="deleteDays"
allow-clear
placeholder="清除多久前的日志"
/>
<a-button v-if="deleteDay" @click="deleteLogs" type="primary">清理</a-button>
</a-space>
</template>
</vxe-toolbar>
<vxe-table row-id="id" ref="xTable" :data="pagination.records" :loading="loading">
<vxe-column type="seq" width="60" />
<vxe-column field="userId" title="用户id" />
<vxe-column field="account" title="用户名称" />
<vxe-column field="login" title="登录成功状态">
<template #default="{ row }">
<a-tag v-if="row.login" color="green">成功</a-tag>
<a-tag v-else color="red">失败</a-tag>
</template>
</vxe-column>
<vxe-column field="client" title="终端">
<template #default="{ row }">
{{ getClient(row.client) }}
</template>
</vxe-column>
<vxe-column field="ip" title="登录IP地址" />
<vxe-column field="os" title="操作系统" />
<vxe-column field="browser" title="浏览器类型" />
<vxe-column field="msg" title="提示消息" />
<vxe-column field="loginTime" title="访问时间" />
<vxe-column fixed="right" width="60" :showOverflow="false" title="操作">
<template #default="{ row }">
<span>
<a href="javascript:" @click="show(row)">查看</a>
</span>
</template>
</vxe-column>
</vxe-table>
<vxe-pager
size="medium"
:loading="loading"
:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
@page-change="handleTableChange"
/>
<LoginLogInfo :clients="clients" :login-types="loginTypes" ref="loginLogInfo" />
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import { deleteByDay, page } from './LoginLog.api'
import useTablePage from '@/hooks/bootx/useTablePage'
import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import { useMessage } from '@/hooks/web/useMessage'
import { QueryField } from '@/components/Bootx/Query/Query'
import { Client, findAll as findClients } from '@/views/iam/client/Client.api'
import { dropdownTranslate, findOneByField } from '@/utils/dataUtil'
import LoginLogInfo from './LoginLogInfo.vue'
import { LabeledValue } from 'ant-design-vue/lib/select'
// 使用hooks
const {
handleTableChange,
pageQueryResHandel,
resetQueryParams,
pagination,
pages,
model,
loading,
} = useTablePage(queryPage)
const { notification, createMessage, createConfirm } = useMessage()
let clients = ref<Client[]>()
const deleteDay = ref<number | undefined>(undefined)
const xTable = ref<VxeTableInstance>()
const xToolbar = ref<VxeToolbarInstance>()
const loginLogInfo = ref<any>()
// 删除条件
let deleteDays = ref<LabeledValue[]>([
{ label: '3天之前', value: '3' },
{ label: '7天之前', value: '7' },
{ label: '30天之前', value: '30' },
{ label: '60天之前', value: '60' },
{ label: '90天之前', value: '90' },
{ label: '180天之前', value: '180' },
{ label: '365天之前', value: '365' },
])
// 查询条件
const fields = computed(() => {
return [
{ field: 'code', type: 'string', name: '账号', placeholder: '请输入账号名称' },
{
field: 'client',
type: 'list',
name: '终端',
placeholder: '请选择终端',
selectList: dropdownTranslate(clients, 'name', 'code'),
},
] as QueryField[]
})
onMounted(() => {
vxeBind()
initClientAndLoginType()
queryPage()
})
/**
* 初始化 终端列表和登录方式列表
*/
function initClientAndLoginType() {
findClients().then(({ data }) => {
clients.value = data
})
}
function vxeBind() {
xTable.value?.connect(xToolbar.value as VxeToolbarInstance)
}
/**
* 分页查询
*/
function queryPage() {
loading.value = true
page({
...model.queryParam,
...pages,
}).then(({ data }) => {
pageQueryResHandel(data)
})
}
/**
* 查看
*/
function show(record) {
loginLogInfo.value.show(record.id)
}
/**
* 获取终端信息
*/
function getClient(code) {
return findOneByField(clients, code, 'code')?.['name']
}
/**
* 清理日志
*/
function deleteLogs() {
createConfirm({
iconType: 'warning',
title: '警告',
content: '是否清除指定日期前的日志,该操作不可撤回',
onOk: async () => {
createMessage.info('清理日志中...')
deleteByDay(deleteDay).then(() => {
createMessage.success('清理日志成功')
queryPage()
})
},
})
}
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,67 @@
import { defHttp } from '@/utils/http/axios'
import { PageResult, Result } from '#/axios'
import { BaseEntity } from '#/web'
/**
* 分页
*/
export const page = (params) => {
return defHttp.get<Result<PageResult<OperateLog>>>({
url: '/log/operate/page',
params,
})
}
/**
* 获取单条
*/
export const get = (id) => {
return defHttp.get<Result<OperateLog>>({
url: '/log/operate/findById',
params: { id },
})
}
/**
* 清除指定天数之前的日志
*/
export const deleteByDay = (deleteDay) => {
return defHttp.delete<Result>({
url: '/log/operate/deleteByDay',
params: { deleteDay },
})
}
/**
* 操作日志
*/
export interface OperateLog extends BaseEntity {
// 操作模块
title?: string
// 操作人员id
operateId?: number
// 操作人员账号
username?: string
// 业务类型
businessType?: string
// 请求方法
method?: string
// 请求方式
requestMethod?: string
// 请求url
operateUrl?: string
// 操作ip
operateIp?: string
// 操作地点
operateLocation?: string
// 请求参数
operateParam?: string
// 返回参数
operateReturn?: string
// 是否成功
success?: boolean
// 错误提示
errorMsg?: string
// 操作时间
operateTime?: string
}

View File

@@ -0,0 +1,64 @@
<template>
<basic-modal
v-bind="$attrs"
:loading="confirmLoading"
width="50%"
title="查看"
:open="visible"
@cancel="visible = false"
>
<description :column="2" :data="data" :schema="schema" />
<template #footer>
<a-space>
<a-button key="cancel" @click="visible = false">取消</a-button>
</a-space>
</template>
</basic-modal>
</template>
<script lang="ts" setup>
import { get, OperateLog } from './OperateLog.api'
import { BasicModal } from '@/components/Modal'
import { DescItem, Description } from '@/components/Description'
import { useDict } from '@/hooks/bootx/useDict'
import { ref } from 'vue'
const { dictConvert } = useDict()
let data = ref<OperateLog>({})
let visible = ref(false)
let confirmLoading = ref(false)
let schema = [
{ field: 'id', label: '主键' },
{ field: 'username', label: '操作人员账号' },
{ field: 'title', label: '操作模块' },
{
field: 'client',
label: '业务类型',
render: (businessType) => dictConvert('LogBusinessType', businessType),
},
{ field: 'requestMethod', label: '请求方式' },
{ field: 'operateUrl', label: '请求url' },
{ field: 'method', label: '操作方法' },
{ field: 'success', label: '操作状态', render: (success) => (success ? '成功' : '失败') },
{ field: 'errorMsg', label: '提示消息' },
{ field: 'operateParam', label: '请求参数' },
{ field: 'operateReturn', label: '响应参数' },
{ field: 'operateTime', label: '操作时间' },
] as DescItem[]
function show(id) {
visible.value = true
confirmLoading.value = true
get(id).then((res) => {
data.value = res.data
confirmLoading.value = false
})
}
defineExpose({
show,
})
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,158 @@
<template>
<div>
<div class="m-3 p-3 pt-5 bg-white">
<b-query
:query-params="model.queryParam"
:fields="fields"
@query="queryPage"
@reset="resetQueryParams"
/>
</div>
<div class="m-3 p-3 bg-white">
<vxe-toolbar ref="xToolbar" custom :refresh="{ queryMethod: queryPage }">
<template #buttons>
<a-space>
<a-select
style="width: 180px"
v-model:value="deleteDay"
:options="deleteDays"
allow-clear
placeholder="清除多久前的日志"
/>
<a-button v-if="deleteDay" @click="deleteLogs" type="primary">清理</a-button>
</a-space>
</template>
</vxe-toolbar>
<vxe-table row-id="id" ref="xTable" :data="pagination.records" :loading="loading">
<vxe-column type="seq" width="60" />
<vxe-column field="operateId" title="操作人员id" />
<vxe-column field="username" title="操作人员账号" />
<vxe-column field="title" title="操作模块" />
<vxe-column field="success" title="是否成功">
<template #default="{ row }">
<a-tag v-if="row.success" color="green">成功</a-tag>
<a-tag v-else color="red">失败</a-tag>
</template>
</vxe-column>
<vxe-column field="businessType" title="业务类型">
<template #default="{ row }">
{{ dictConvert('LogBusinessType', row.businessType) }}
</template>
</vxe-column>
<vxe-column field="operateIp" title="操作ip" />
<vxe-column field="errorMsg" title="错误提示" />
<vxe-column field="operateTime" title="操作时间" />
<vxe-column fixed="right" width="60" :showOverflow="false" title="操作">
<template #default="{ row }">
<span>
<a href="javascript:" @click="show(row)">查看</a>
</span>
</template>
</vxe-column>
</vxe-table>
<vxe-pager
size="medium"
:loading="loading"
:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
@page-change="handleTableChange"
/>
<OperateLogEdit ref="operateLogEdit" @ok="queryPage" />
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { page, deleteByDay } from './OperateLog.api'
import useTablePage from '@/hooks/bootx/useTablePage'
import OperateLogEdit from './OperateLogInfo.vue'
import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import { useMessage } from '@/hooks/web/useMessage'
import { QueryField } from '@/components/Bootx/Query/Query'
import { useDict } from '@/hooks/bootx/useDict'
import { LabeledValue } from 'ant-design-vue/lib/select'
// 使用hooks
const {
handleTableChange,
pageQueryResHandel,
resetQueryParams,
pagination,
pages,
model,
loading,
} = useTablePage(queryPage)
const { createMessage, createConfirm } = useMessage()
const { dictConvert } = useDict()
const deleteDay = ref<any>(undefined)
// 删除条件
let deleteDays = ref<LabeledValue[]>([
{ label: '3天之前', value: '3' },
{ label: '7天之前', value: '7' },
{ label: '30天之前', value: '30' },
{ label: '60天之前', value: '60' },
{ label: '90天之前', value: '90' },
{ label: '180天之前', value: '180' },
{ label: '365天之前', value: '365' },
])
// 查询条件
const fields = [
{ field: 'title', type: 'string', name: '操作模块', placeholder: '请输入操作模块' },
{ field: 'username', type: 'string', name: '账号', placeholder: '请输入账号名称' },
] as QueryField[]
const xTable = ref<VxeTableInstance>()
const xToolbar = ref<VxeToolbarInstance>()
const operateLogEdit = ref<any>()
onMounted(() => {
vxeBind()
queryPage()
})
function vxeBind() {
xTable.value?.connect(xToolbar.value as VxeToolbarInstance)
}
/**
* 分页查询
*/
function queryPage() {
loading.value = true
page({
...model.queryParam,
...pages,
}).then(({ data }) => {
pageQueryResHandel(data)
})
return Promise.resolve()
}
/**
* 查看
*/
function show(record) {
operateLogEdit.value.show(record.id)
}
/**
* 清理日志
*/
function deleteLogs() {
createConfirm({
iconType: 'warning',
title: '警告',
content: '是否清除指定日期前的日志,该操作不可撤回',
onOk: async () => {
createMessage.info('清理日志中...')
deleteByDay(deleteDay).then(() => {
createMessage.success('清理日志成功')
queryPage()
})
},
})
}
</script>
<style lang="less" scoped></style>

View File

@@ -13,6 +13,7 @@
ref="formRef"
:model="form"
:rules="rules"
validate-trigger="['blur', 'change']"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>

View File

@@ -56,7 +56,7 @@ export const del = (id) => {
* 查询全部
*/
export const findAll = () => {
return defHttp.get<Result<Array<Client>>>({
return defHttp.get<Result<Client[]>>({
url: '/client/findAll',
})
}

View File

@@ -21,7 +21,11 @@
<a-input v-model:value="form.id" :disabled="showable" />
</a-form-item>
<a-form-item label="编码" validate-first name="code">
<a-input v-model:value="form.code" :disabled="showable" placeholder="请输入编码" />
<a-input
v-model:value="form.code"
:disabled="showable || form.internal"
placeholder="请输入编码"
/>
</a-form-item>
<a-form-item label="名称" validate-first name="name">
<a-input v-model:value="form.name" :disabled="showable" placeholder="请输入名称" />

View File

@@ -12,6 +12,7 @@
class="small-from-item"
ref="formRef"
:model="form"
validate-trigger="['blur', 'change']"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"

View File

@@ -13,6 +13,7 @@
ref="formRef"
class="small-from-item"
:model="form"
validate-trigger="['blur', 'change']"
:rules="rules"
:labelCol="labelCol"
:wrapperCol="wrapperCol"

View File

@@ -99,7 +99,7 @@ export interface Role extends BaseEntity {
// 编码
code?: string
// 父ID
pid?: number
pid?: string
// 名称
name?: string
// 是否系统内置

View File

@@ -19,7 +19,7 @@
:checkable="true"
v-model:checkedKeys="checkedKeys"
v-model:expandedKeys="expandedKeys"
:auto-expand-parent="autoExpandParent"
:auto-expand-parent="true"
:tree-data="treeData"
@check="onCheck"
@expand="onExpand"
@@ -81,7 +81,6 @@
let expandedKeys = ref<string[]>([])
// 被选中的key
let checkedKeys = ref<string[]>([])
let autoExpandParent = ref(false)
//权限码树信息
let treeData = ref<Tree[]>([])
let treeList = ref<PermCodeTree[]>([])
@@ -182,7 +181,7 @@
expandedKeys.value = treeList.value
.map((node) => {
if (
searchName &&
searchName.value &&
node.pid &&
XEUtils.toValueString(node.name)?.toLowerCase()?.indexOf(value) > -1
) {
@@ -219,7 +218,6 @@
*/
function onExpand(keys) {
expandedKeys.value = keys
autoExpandParent.value = false
}
/**
* 点击复选框触发

View File

@@ -12,6 +12,7 @@
class="small-from-item"
:model="form"
ref="formRef"
validate-trigger="['blur', 'change']"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"

View File

@@ -20,7 +20,7 @@
v-model:checkedKeys="checkedKeys"
v-model:expandedKeys="expandedKeys"
:checkStrictly="checkStrictly"
:auto-expand-parent="autoExpandParent"
:auto-expand-parent="false"
:tree-data="treeData"
@check="onCheck"
@expand="onExpand"
@@ -94,7 +94,6 @@
let expandedKeys = ref<string[]>([])
// 被选中的key
let checkedKeys = ref<string[]>([])
let autoExpandParent = ref(false)
//菜单树信息
let treeData = ref<Tree[]>([])
let treeList = ref<MenuTree[]>([])
@@ -208,7 +207,7 @@
expandedKeys.value = treeList.value
.map((node) => {
if (
searchName &&
searchName.value &&
node.pid &&
XEUtils.toValueString(node.title).toLowerCase().indexOf(value) > -1
) {
@@ -245,7 +244,6 @@
*/
function onExpand(keys) {
expandedKeys.value = keys
autoExpandParent.value = false
}
/**
* 点击复选框触发

View File

@@ -19,7 +19,7 @@
:checkable="true"
v-model:checkedKeys="checkedKeys"
v-model:expandedKeys="expandedKeys"
:auto-expand-parent="autoExpandParent"
:auto-expand-parent="true"
:tree-data="treeData"
@check="onCheck"
@expand="onExpand"
@@ -91,7 +91,6 @@
let expandedKeys = ref<string[]>([])
// 被选中的key
let checkedKeys = ref<string[]>([])
let autoExpandParent = ref(false)
//请求路径树信息
let treeData = ref<Tree[]>([])
let treeList = ref<PermPathTree[]>([])
@@ -205,7 +204,7 @@
expandedKeys.value = treeList.value
.map((node) => {
if (
searchName &&
searchName.value &&
node.pid &&
XEUtils.toValueString(node.title).toLowerCase().indexOf(value) > -1
) {
@@ -213,7 +212,6 @@
}
})
.filter((item, i, self) => item && self.indexOf(item) === i) as string[]
console.log(expandedKeys.value)
}
/**
* 渲染搜索项目数据开始段
@@ -243,8 +241,6 @@
*/
function onExpand(keys) {
expandedKeys.value = keys
console.log(expandedKeys)
autoExpandParent.value = false
}
/**
* 点击复选框触发

View File

@@ -56,7 +56,7 @@ export function findAll() {
export function restartPassword(userId, newPassword) {
return defHttp.post({
url: '/user/admin/restartPassword',
params: { userId, newPassword },
data: { userId, newPassword },
})
}
@@ -66,8 +66,7 @@ export function restartPassword(userId, newPassword) {
export function restartPasswordBatch(userIds, newPassword) {
return defHttp.post({
url: '/user/admin/restartPasswordBatch',
data: userIds,
params: { newPassword },
data: { userIds, newPassword },
})
}

View File

@@ -5,20 +5,20 @@ import { Role } from '@/views/iam/role/Role.api'
/**
* 获取用户拥有角色id集合
*/
export function getRoleIds(id) {
export function getRoleIds(userId) {
return defHttp.get<Result<string[]>>({
url: `/user/role/findRoleIdsByUser`,
params: { id },
params: { userId },
})
}
/**
* 获取用户拥有角色集合
*/
export function getRoles(id) {
export function getRoles(userId) {
return defHttp.get<Result<Role[]>>({
url: `/user/role/findRolesByUser`,
params: { id },
params: { userId },
})
}

View File

@@ -22,12 +22,12 @@
<a-menu-item>
<a @click="assignRolesBatch()">角色分配</a>
</a-menu-item>
<a-menu-item>
<a @click="lockUserConfirmBatch(true)">锁定账号</a>
</a-menu-item>
<a-menu-item>
<a @click="lockUserConfirmBatch(false)">解锁账号</a>
</a-menu-item>
<!-- <a-menu-item>-->
<!-- <a @click="lockUserConfirmBatch(true)">锁定账号</a>-->
<!-- </a-menu-item>-->
<!-- <a-menu-item>-->
<!-- <a @click="lockUserConfirmBatch(false)">解锁账号</a>-->
<!-- </a-menu-item>-->
<a-menu-item>
<a @click="resetPwdBatch()">重置密码</a>
</a-menu-item>
@@ -74,12 +74,6 @@
<a-menu-item>
<a-link @click="assignRoles(row)">角色分配</a-link>
</a-menu-item>
<a-menu-item>
<a-link @click="assignDept(row)">部门分配</a-link>
</a-menu-item>
<a-menu-item>
<a-link @click="assignDataScope(row)">数据角色分配</a-link>
</a-menu-item>
<a-menu-item>
<a-link @click="resetPwd(row)">重置密码</a-link>
</a-menu-item>
@@ -108,6 +102,8 @@
<UserAdd ref="userAdd" @ok="queryPage" />
<UserEdit ref="userEdit" @ok="queryPage" />
<UserShow ref="userShow" />
<UserRoleAssign ref="userRoleAssign" />
<UserResetPwd ref="userResetPwd" />
</div>
</div>
</template>
@@ -125,7 +121,9 @@
import UserAdd from './UserAdd.vue'
import UserEdit from './UserEdit.vue'
import UserShow from './UserShow.vue'
import ALink from "@/components/Link/Link.vue";
import UserResetPwd from './UserResetPwd.vue'
import UserRoleAssign from './role/UserRoleAssign.vue'
import ALink from '@/components/Link/Link.vue'
// 使用hooks
const {
@@ -152,13 +150,11 @@
let userEdit = ref<any>()
let userShow = ref<any>()
let userRoleAssign = ref<any>()
let userRoleAssignBatch = ref<any>()
let userDataScopeAssign = ref<any>()
let userDataScopeAssignBatch = ref<any>()
let userDeptAssign = ref<any>()
let userDeptAssignBatch = ref<any>()
let userResetPwd = ref<any>()
let userResetPwdBatch = ref<any>()
onMounted(() => {
vxeBind()
@@ -235,30 +231,12 @@
}
// 分配角色
function assignRoles(record) {
userRoleAssign.value.init(record)
userRoleAssign.value.init(false, record.id)
}
// 批量分配角色
function assignRolesBatch() {
const userIds = xTable.value?.getCheckboxRecords().map((o) => o.id)
userRoleAssignBatch.value.init(userIds)
}
// 分配数据权限
function assignDataScope(record) {
userDataScopeAssign.value.init(record)
}
// 批量分配数据权限
function assignDataScopeBatch() {
const userIds = xTable.value?.getCheckboxRecords().map((o) => o.id)
userDataScopeAssignBatch.value.init(userIds)
}
// 分配部门
function assignDept(record) {
userDeptAssign.value.init(record.id)
}
// 批量分配部门
function assignDeptBatch() {
const userIds = xTable.value?.getCheckboxRecords().map((o) => o.id)
userDeptAssignBatch.value.init(userIds)
userRoleAssign.value.init(true, userIds)
}
function add() {
userAdd.value.init()
@@ -279,14 +257,14 @@
* 重置密码
*/
function resetPwd(record) {
userResetPwd.value.init(record.id)
userResetPwd.value.init(false, record.id)
}
/**
* 重置密码
*/
function resetPwdBatch() {
const userIds = xTable.value?.getCheckboxRecords().map((o) => o.id)
userResetPwdBatch.value.init(userIds)
userResetPwd.value.init(true,userIds)
}
</script>

View File

@@ -0,0 +1,87 @@
<template>
<basic-modal
v-bind="$attrs"
title="重置密码"
:width="modalWidth"
:open="visible"
:confirmLoading="confirmLoading"
:maskClosable="false"
@cancel="handleCancel"
@ok="handleOk"
>
<a-spin :spinning="confirmLoading">
<a-form
ref="formRef"
:model="form"
:rules="rules"
:validate-trigger="['blur', 'change']"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-item name="newPassword" label="重置密码">
<a-input-password v-model:value="form.newPassword" placeholder="请输入密码" />
</a-form-item>
</a-form>
</a-spin>
</basic-modal>
</template>
<script setup lang="ts">
import BasicModal from '@/components/Modal/src/BasicModal.vue'
import useFormEdit from '@/hooks/bootx/useFormEdit'
import { restartPassword, restartPasswordBatch } from './User.api'
import { useMessage } from '@/hooks/web/useMessage'
import { Rule } from 'ant-design-vue/lib/form'
import { ref } from 'vue'
const { handleCancel, labelCol, wrapperCol, modalWidth, confirmLoading, visible } = useFormEdit()
const { createConfirm, createMessage } = useMessage()
// 用户id
const userId = ref('')
// 用户ids
const userIds = ref<string[]>([])
const batch = ref(false)
let form = ref<any>({
newPassword: '',
})
let formRef = ref<any>()
const rules = {
newPassword: [{ required: true, message: '重置密码不得为空' }],
} as Record<string, Rule[]>
/**
* 初始化
*/
function init(batch: boolean, userIdOrIds: string | string[]) {
visible.value = true
if (!batch) {
userId.value = userIdOrIds as string
} else {
userIds.value = userIdOrIds as never[]
}
}
function handleOk() {
formRef.value?.validate().then(async () => {
createConfirm({
iconType: 'info',
title: '警告',
content: '是否重置用户的密码',
onOk: async () => {
confirmLoading.value = true
if (batch.value) {
await restartPasswordBatch(userIds.value, form.value.newPassword)
} else {
await restartPassword(userId.value, form.value.newPassword)
}
createMessage.success('保存成功')
handleCancel()
},
})
})
}
defineExpose({ init })
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,269 @@
<template>
<basic-drawer
showFooter
v-bind="$attrs"
:title="modal.title"
width="40%"
:open="modal.visible"
@close="handleCancel"
>
<a-spin :spinning="modal.loading">
<a-input
style="margin-bottom: 8px"
placeholder="筛选"
allowClear
v-model:value="modal.searchName"
@change="search"
/>
<a-tree
:checkable="true"
:checkStrictly="true"
v-model:checkedKeys="modal.checkedKeys"
v-model:expandedKeys="modal.expandedKeys"
:auto-expand-parent="true"
:tree-data="modal.treeData"
@check="onCheck"
@expand="onExpand"
>
<template #title="{ title }">
<span v-if="title?.toLowerCase()?.indexOf(modal.searchName.toLowerCase()) > -1">
{{ searchRenderStart(title, modal.searchName) }}
<span style="color: #f50">
{{ searchRenderMiddle(title, modal.searchName) }}
</span>
{{ searchRenderEnd(title, modal.searchName) }}
</span>
<span v-else>{{ title }}</span>
</template>
</a-tree>
</a-spin>
<template #footer>
<a-dropdown style="margin-left: 5px" :trigger="['click']" placement="top">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="checkALL">全部勾选</a-menu-item>
<a-menu-item key="2" @click="cancelCheckALL">取消全选</a-menu-item>
<a-menu-item key="3" @click="expandAll">展开所有</a-menu-item>
<a-menu-item key="4" @click="closeAll">合并所有</a-menu-item>
</a-menu>
</template>
<a-button post-icon="ant-design:up-outlined"> 操作 </a-button>
</a-dropdown>
<a-button @click="modal.visible = false" style="margin-right: 0.8rem">取消</a-button>
<a-button
@click="handleSubmit()"
type="primary"
:loading="modal.loading"
style="margin-right: 0.8rem"
>保存</a-button
>
</template>
</basic-drawer>
</template>
<script setup lang="ts">
import { BasicDrawer } from '@/components/Drawer'
import { reactive } from 'vue'
import { Tree, treeDataTranslate } from '@/utils/dataUtil'
import XEUtils from 'xe-utils'
import { RoleTree, tree as roleTree } from '@/views/iam/role/Role.api'
import { addUserRole, addUserRoleBatch, getRoleIds } from '@/views/iam/user/UserAssign.api'
import { useMessage } from '@/hooks/web/useMessage'
const { createConfirm, createMessage } = useMessage()
const modal = reactive({
title: '角色分配',
visible: false,
batch: false,
loading: false,
searchName: '',
// 所有的key
allTreeKeys: [] as string[],
// 展开的key
expandedKeys: [] as string[],
// 被选中的key
checkedKeys: [] as string[],
// 后台获取的角色树
treeData: [] as Tree[],
// 转换后的树数据
treeList: [] as RoleTree[],
// 用户id
userId: '',
// 用户ids
userIds: [],
// 角色Ids
roleIds: [],
})
/**
* 初始化
*/
async function init(batch: boolean, userIdOrIds: string | string[]) {
modal.visible = true
modal.batch = batch
modal.loading = true
// 初始化角色树
await initRoles()
if (!batch) {
modal.userId = userIdOrIds as string
// 初始化分配角色
await initAssign()
} else {
modal.userIds = userIdOrIds as never[]
}
modal.loading = false
}
/**
* 初始化菜单
*/
async function initRoles() {
modal.searchName = ''
modal.expandedKeys = []
// 角色树
await roleTree().then((res) => {
modal.treeData = treeDataTranslate(res.data, 'id', 'name')
generateTreeList(res.data)
})
}
/**
* 初始化权限码分配信息
*/
async function initAssign() {
// 当前用户已经选择的角色Id
await getRoleIds(modal.userId).then((res) => {
modal.checkedKeys = res.data
})
// 选中的的key值
modal.allTreeKeys = modal.treeList.map((item) => item.id) as string[]
}
/**
* 保存
*/
function handleSubmit() {
createConfirm({
iconType: 'info',
title: '警告',
content: '是否要保存角色分配信息',
onOk: async () => {
modal.loading = true
if (modal.batch) {
await addUserRoleBatch({
userIds: modal.userIds,
roleIds: modal.checkedKeys,
})
} else {
await addUserRole({
userId: modal.userId,
roleIds: modal.checkedKeys,
})
}
createMessage.success('保存成功')
modal.loading = false
modal.visible = false
},
})
}
/**
* 取消
*/
function handleCancel() {
modal.visible = false
}
/**
* 树数据铺平
*/
function generateTreeList(treeData) {
if (!treeData) {
return
}
for (let i = 0; i < treeData.length; i++) {
const node = treeData[i]
modal.treeList.push(node)
if (node.children) {
generateTreeList(node.children)
}
}
}
/**
* 搜索
*/
function search() {
const value = XEUtils.toValueString(modal.searchName).toLowerCase()
modal.expandedKeys = modal.treeList
.map((node) => {
if (
modal.searchName &&
node.pid &&
XEUtils.toValueString(node.name)?.toLowerCase()?.indexOf(value) > -1
) {
return node.pid
}
})
.filter((item, i, self) => item && self.indexOf(item) === i) as string[]
}
/**
* 渲染搜索项目数据开始段
*/
function searchRenderStart(title, searchName) {
return title.substring(0, title.toLowerCase().indexOf(searchName.toLowerCase()))
}
/**
* 渲染搜索项目数据中间段
*/
function searchRenderMiddle(title, searchName) {
return title.substring(
title.toLowerCase().indexOf(searchName.toLowerCase()),
title.toLowerCase().indexOf(searchName.toLowerCase()) + searchName.length,
)
}
/**
* 渲染搜索项目数据结束段
*/
function searchRenderEnd(title, searchName) {
return title.substring(
title.toLowerCase().indexOf(searchName.toLowerCase()) + searchName.length,
)
}
/**
* 展开/收起节点时触发
*/
function onExpand(keys) {
modal.expandedKeys = keys
}
/**
* 点击复选框触发
*/
function onCheck(key) {
modal.checkedKeys = key.checked
}
/**
* 展开全部
*/
function expandAll() {
modal.expandedKeys = modal.allTreeKeys
}
/**
* 合并全部
*/
function closeAll() {
modal.expandedKeys = []
}
/**
* 全选
*/
function checkALL() {
modal.checkedKeys = modal.allTreeKeys
}
/**
* 全不选
*/
function cancelCheckALL() {
modal.checkedKeys = []
}
defineExpose({ init })
</script>
<style scoped lang="less"></style>