mirror of
https://gitee.com/bootx/dax-pay-ui
synced 2026-08-06 05:06:26 +08:00
feat 商户用户和权限相关界面
This commit is contained in:
@@ -1,17 +1,28 @@
|
||||
import { defHttp } from '@/utils/http/axios'
|
||||
import { PageResult, Result } from '#/axios'
|
||||
import { UserInfo } from '@/views/iam/user/User.api'
|
||||
import { Role } from '@/views/iam/role/Role.api'
|
||||
|
||||
/**
|
||||
* 用户列表
|
||||
*/
|
||||
export function findAll(params) {
|
||||
export function page(params) {
|
||||
return defHttp.get<Result<PageResult<UserInfo[]>>>({
|
||||
url: '/merchant/user/page',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户详情
|
||||
*/
|
||||
export function get(id) {
|
||||
return defHttp.get<Result<UserInfo>>({
|
||||
url: '/merchant/user/findById',
|
||||
params: { id },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户
|
||||
*/
|
||||
@@ -23,21 +34,71 @@ export function add(params) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 修根用户
|
||||
* 修改用户
|
||||
*/
|
||||
export function edit(params) {
|
||||
export function update(params) {
|
||||
return defHttp.post<Result>({
|
||||
url: '/merchant/user/update',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
*/
|
||||
export function restartPassword(userId, newPassword) {
|
||||
return defHttp.post({
|
||||
url: '/merchant/user/restartPassword',
|
||||
data: { userId, newPassword },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量重置密码
|
||||
*/
|
||||
export function restartPasswordBatch(userIds, newPassword) {
|
||||
return defHttp.post({
|
||||
url: '/merchant/user/restartPasswordBatch',
|
||||
data: { userIds, newPassword },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配角色
|
||||
*/
|
||||
export function assignRole(params) {
|
||||
export function addUserRole(params) {
|
||||
return defHttp.post<Result>({
|
||||
url: '/merchant/user/assignRole',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配角色 批量
|
||||
*/
|
||||
export function addUserRoleBatch(data) {
|
||||
return defHttp.post({
|
||||
url: `/merchant/user/assignRoleBatch`,
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户拥有角色id集合
|
||||
*/
|
||||
export function getRoleIds(userId) {
|
||||
return defHttp.get<Result<string[]>>({
|
||||
url: `/merchant/user/findRoleIdsByUser`,
|
||||
params: { userId },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户拥有角色集合
|
||||
*/
|
||||
export function getRoles(userId) {
|
||||
return defHttp.get<Result<Role[]>>({
|
||||
url: `/merchant/user/findRolesByUser`,
|
||||
params: { userId },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,232 @@
|
||||
<script setup lang="ts"></script>
|
||||
<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-button type="primary" pre-icon="ant-design:plus-outlined" @click="add"
|
||||
>新建</a-button
|
||||
>
|
||||
<a-dropdown v-if="batchOperateFlag">
|
||||
<a-button post-icon="ant-design:down-outlined"> 批量操作 </a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<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="resetPwdBatch()">重置密码</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-toolbar>
|
||||
<div class="h-65vh">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
height="auto"
|
||||
size="medium"
|
||||
@checkbox-all="selectAllEvent"
|
||||
@checkbox-change="selectChangeEvent"
|
||||
:loading="loading"
|
||||
:data="pagination.records"
|
||||
>
|
||||
<vxe-column type="checkbox" width="50" />
|
||||
<vxe-column field="name" title="姓名" />
|
||||
<vxe-column field="account" title="账号" />
|
||||
<vxe-column field="phone" title="手机号" />
|
||||
<vxe-column field="email" title="邮箱" />
|
||||
<vxe-column field="avatar" title="头像" align="center">
|
||||
<template #default="{ row }">
|
||||
<Avatar size="large" :src="getFileUrl(row.avatar)" />
|
||||
</template>
|
||||
</vxe-column>
|
||||
|
||||
<template></template>
|
||||
<vxe-column field="status" title="用户状态" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ dictConvert('UserStatusCode', row.status) || '无' }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column fixed="right" width="170" :showOverflow="false" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a href="javascript:" @click="show(row)">查看</a>
|
||||
<a-divider type="vertical" />
|
||||
<a href="javascript:" @click="edit(row)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-dropdown>
|
||||
<a> 更多 <icon icon="ant-design:down-outlined" :size="12" /> </a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item>
|
||||
<a-link @click="assignRoles(row)">角色分配</a-link>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a-link @click="resetPwd(row)">重置密码</a-link>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="[1, 3].includes(row.status)">
|
||||
<a-link v-if="row.status === 1" @click="lockUserConfirm(row.id, true)"
|
||||
>封禁账号</a-link
|
||||
>
|
||||
<a-link v-if="row.status === 3" @click="lockUserConfirm(row.id, false)"
|
||||
>解锁账号</a-link
|
||||
>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
</div>
|
||||
<vxe-pager
|
||||
size="medium"
|
||||
:loading="loading"
|
||||
:current-page="pagination.current"
|
||||
:page-size="pagination.size"
|
||||
:total="pagination.total"
|
||||
@page-change="handleTableChange"
|
||||
/>
|
||||
<UserAdd ref="userAdd" @ok="queryPage" />
|
||||
<UserEdit ref="userEdit" @ok="queryPage" />
|
||||
<UserShow ref="userShow" />
|
||||
<UserResetPwd ref="userResetPwd" />
|
||||
<UserRoleAssign ref="userRoleAssign" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
<script lang="ts" setup>
|
||||
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
|
||||
import { Avatar } from 'ant-design-vue'
|
||||
import useTablePage from '@/hooks/bootx/useTablePage'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { QueryField, STRING } from '@/components/Bootx/Query/Query'
|
||||
import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
|
||||
import { useDict } from '@/hooks/bootx/useDict'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import UserShow from './UserShow.vue'
|
||||
import UserEdit from './UserEdit.vue'
|
||||
import UserAdd from './UserAdd.vue'
|
||||
import UserResetPwd from './UserResetPwd.vue'
|
||||
import UserRoleAssign from './UserRoleAssign.vue'
|
||||
import ALink from '@/components/Link/Link.vue'
|
||||
import { useFilePlatform } from '@/hooks/bootx/useFilePlatform'
|
||||
import { page } from './MerchantUser.api'
|
||||
|
||||
// 使用hooks
|
||||
const {
|
||||
handleTableChange,
|
||||
pageQueryResHandel,
|
||||
resetQueryParams,
|
||||
pagination,
|
||||
pages,
|
||||
model,
|
||||
loading,
|
||||
batchOperateFlag,
|
||||
} = useTablePage(queryPage)
|
||||
const { dictConvert } = useDict()
|
||||
const { getFileUrl } = useFilePlatform()
|
||||
|
||||
// 查询条件
|
||||
const fields = [
|
||||
{ field: 'account', type: STRING, name: '账号', placeholder: '请输入要查询的账号' },
|
||||
{ field: 'name', type: STRING, name: '姓名', placeholder: '请输入要查询的姓名' },
|
||||
] as QueryField[]
|
||||
const xTable = ref<VxeTableInstance>()
|
||||
const xToolbar = ref<VxeToolbarInstance>()
|
||||
let userAdd = ref<any>()
|
||||
let userEdit = ref<any>()
|
||||
let userShow = ref<any>()
|
||||
let userRoleAssign = ref<any>()
|
||||
let userResetPwd = 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 selectAllEvent() {
|
||||
const records = xTable.value?.getCheckboxRecords()
|
||||
batchOperateFlag.value = !!records?.length
|
||||
}
|
||||
/**
|
||||
* 选中事件
|
||||
*/
|
||||
function selectChangeEvent() {
|
||||
const records = xTable.value?.getCheckboxRecords()
|
||||
batchOperateFlag.value = !!records?.length
|
||||
}
|
||||
// 分配角色
|
||||
function assignRoles(record) {
|
||||
userRoleAssign.value.init(false, record.id)
|
||||
}
|
||||
// 批量分配角色
|
||||
function assignRolesBatch() {
|
||||
const userIds = xTable.value?.getCheckboxRecords().map((o) => o.id)
|
||||
userRoleAssign.value.init(true, userIds)
|
||||
}
|
||||
function add() {
|
||||
userAdd.value.init()
|
||||
}
|
||||
/**
|
||||
* 查看信息
|
||||
*/
|
||||
function show(record) {
|
||||
userShow.value.init(record.id)
|
||||
}
|
||||
/**
|
||||
* 信息编辑
|
||||
*/
|
||||
function edit(record) {
|
||||
userEdit.value.init(record.id)
|
||||
}
|
||||
/**
|
||||
* 重置密码
|
||||
*/
|
||||
function resetPwd(record) {
|
||||
userResetPwd.value.init(false, record.id)
|
||||
}
|
||||
/**
|
||||
* 重置密码
|
||||
*/
|
||||
function resetPwdBatch() {
|
||||
const userIds = xTable.value?.getCheckboxRecords().map((o) => o.id)
|
||||
userResetPwd.value.init(true, userIds)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
184
src/views/daxpay/merchant/user/UserAdd.vue
Normal file
184
src/views/daxpay/merchant/user/UserAdd.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<basic-drawer
|
||||
showFooter
|
||||
title="添加用户"
|
||||
v-bind="$attrs"
|
||||
:width="modalWidth"
|
||||
:open="visible"
|
||||
:mask-closable="false"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<a-form
|
||||
class="small-from-item"
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:validate-trigger="['blur', 'change']"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item label="用户账号" name="account" validate-first>
|
||||
<a-input v-model:value="form.account" placeholder="请输入用户账号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="用户名称" name="name">
|
||||
<a-input v-model:value="form.name" placeholder="请输入用户名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="登录密码" name="password" validate-first>
|
||||
<strength-meter
|
||||
v-model:value="form.password"
|
||||
placeholder="请输入登录密码"
|
||||
@change="validateToNextPassword"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="确认密码" name="confirmPassword" validate-first>
|
||||
<a-input-password v-model:value="form.confirmPassword" placeholder="请重新输入登录密码" />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" validate-first name="phone">
|
||||
<a-input v-model:value="form.phone" placeholder="请输入用户手机号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" validate-first name="email">
|
||||
<a-input v-model:value="form.email" placeholder="请输入用户邮箱" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button key="cancel" @click="handleCancel">取消</a-button>
|
||||
<a-button key="forward" :loading="confirmLoading" type="primary" @click="handleOk"
|
||||
>保存</a-button
|
||||
>
|
||||
</a-space>
|
||||
</template>
|
||||
</basic-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BasicDrawer } from '@/components/Drawer'
|
||||
import useFormEdit from '@/hooks/bootx/useFormEdit'
|
||||
import { FormInstance, Rule } from 'ant-design-vue/lib/form'
|
||||
import { nextTick, reactive, ref } from 'vue'
|
||||
import StrengthMeter from '/@/components/StrengthMeter/src/StrengthMeter.vue'
|
||||
import { validateEmail, validateMobile } from '@/utils/validate'
|
||||
import { existsAccount, existsEmail, existsPhone } from '@/api/sys/userAssist'
|
||||
import { add } from './MerchantUser.api'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { UserInfo } from '@/views/iam/user/User.api'
|
||||
|
||||
const { handleCancel, labelCol, wrapperCol, modalWidth, confirmLoading, visible } = useFormEdit()
|
||||
const { createMessage } = useMessage()
|
||||
const formRef = ref<FormInstance>()
|
||||
let form = ref<UserInfo>({
|
||||
name: '',
|
||||
account: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
})
|
||||
// 校验
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入用户名称' }],
|
||||
account: [
|
||||
{ required: true, message: '请输入用户账号' },
|
||||
{ validator: checkAccount, trigger: 'blur' },
|
||||
],
|
||||
password: [{ required: true, message: '请输入登录密码!' }],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请重新输入登录密码!' },
|
||||
{ validator: compareToFirstPassword },
|
||||
],
|
||||
phone: [{ validator: validatePhone }, { validator: validateBindPhone, trigger: 'blur' }],
|
||||
email: [{ validator: validateEmailRule }, { validator: validateBindEmail, trigger: 'blur' }],
|
||||
} as Record<string, Rule[]>)
|
||||
|
||||
let confirmDirty = ref(false)
|
||||
|
||||
// 事件
|
||||
const emits = defineEmits(['ok'])
|
||||
|
||||
function init() {
|
||||
visible.value = true
|
||||
confirmLoading.value = false
|
||||
nextTick(() => {
|
||||
formRef.value?.resetFields()
|
||||
})
|
||||
}
|
||||
|
||||
function handleOk() {
|
||||
formRef.value?.validate().then(async () => {
|
||||
confirmLoading.value = true
|
||||
await add(form.value).finally(() => (confirmLoading.value = false))
|
||||
createMessage.success('添加用户成功')
|
||||
confirmLoading.value = false
|
||||
emits('ok')
|
||||
handleCancel()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户名检查
|
||||
*/
|
||||
async function checkAccount() {
|
||||
const { data } = await existsAccount(form.value.account)
|
||||
return data ? Promise.reject('用户名已存在!') : Promise.resolve()
|
||||
}
|
||||
/**
|
||||
* 密码检查
|
||||
*/
|
||||
function validateToNextPassword() {
|
||||
if (confirmDirty.value) {
|
||||
formRef.value?.validateFields(['confirmPassword'])
|
||||
}
|
||||
}
|
||||
function compareToFirstPassword() {
|
||||
if (form.value.confirmPassword !== form.value.password) {
|
||||
confirmDirty.value = true
|
||||
return Promise.reject('密码不一致')
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验邮箱号
|
||||
*/
|
||||
function validateEmailRule() {
|
||||
const { email } = form.value
|
||||
if (!email) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
const { msg, result } = validateEmail(email)
|
||||
return result ? Promise.resolve() : Promise.reject(msg)
|
||||
}
|
||||
/**
|
||||
* 后台校验邮箱号是否被使用
|
||||
*/
|
||||
async function validateBindEmail() {
|
||||
const { email } = form.value
|
||||
const { data } = await existsEmail(email)
|
||||
return data ? Promise.reject('邮箱已被使用') : Promise.resolve()
|
||||
}
|
||||
/**
|
||||
* 校验手机号
|
||||
*/
|
||||
function validatePhone() {
|
||||
const { phone } = form.value
|
||||
if (!phone) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
const { msg, result } = validateMobile(phone)
|
||||
return result ? Promise.resolve() : Promise.reject(msg)
|
||||
}
|
||||
/**
|
||||
* 后台校验手机号是否被使用
|
||||
*/
|
||||
async function validateBindPhone() {
|
||||
const { phone } = form.value
|
||||
const { data } = await existsPhone(phone)
|
||||
return data ? Promise.reject('手机号已被使用') : Promise.resolve()
|
||||
}
|
||||
defineExpose({ init })
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
170
src/views/daxpay/merchant/user/UserEdit.vue
Normal file
170
src/views/daxpay/merchant/user/UserEdit.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<basic-drawer
|
||||
showFooter
|
||||
title="用户信息修改"
|
||||
v-bind="$attrs"
|
||||
:width="modalWidth"
|
||||
:open="visible"
|
||||
:mask-closable="false"
|
||||
@ok="handleOk"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<a-form
|
||||
class="small-from-item"
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:validate-trigger="['blur', 'change']"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item label="用户账号" validate-first name="account">
|
||||
<a-input v-model:value="form.account" placeholder="请输入用户账号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="用户名称" name="name">
|
||||
<a-input v-model:value="form.name" placeholder="请输入用户名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" validate-first name="phone">
|
||||
<a-input v-model:value="form.phone" placeholder="请输入用户手机号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" validate-first name="email">
|
||||
<a-input v-model:value="form.email" placeholder="请输入用户邮箱" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</basic-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BasicDrawer } from '@/components/Drawer'
|
||||
import useFormEdit from '@/hooks/bootx/useFormEdit'
|
||||
import { FormInstance, Rule } from 'ant-design-vue/lib/form'
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
import { existsEmailNotId, existsPhoneNotId, existsAccountNotId } from '@/api/sys/userAssist'
|
||||
import { validateEmail, validateMobile } from '@/utils/validate'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { get, update } from './MerchantUser.api'
|
||||
import { UserInfo } from '@/views/iam/user/User.api'
|
||||
|
||||
const { handleCancel, diffForm, labelCol, wrapperCol, modalWidth, confirmLoading, visible } =
|
||||
useFormEdit()
|
||||
const { createMessage } = useMessage()
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
let form = ref<UserInfo>({
|
||||
id: null,
|
||||
name: '',
|
||||
account: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
})
|
||||
let rawForm: any = {}
|
||||
|
||||
// 校验
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
name: [{ required: true, message: '请输入名称' }],
|
||||
account: [
|
||||
{ required: true, message: '请输入账号' },
|
||||
{ validator: checkAccount, trigger: 'blur' },
|
||||
],
|
||||
phone:
|
||||
rawForm.phone !== form.value.phone
|
||||
? [{ validator: validatePhone }, { validator: validateBindPhone, trigger: 'blur' }]
|
||||
: [],
|
||||
email:
|
||||
rawForm.email !== form.value.email
|
||||
? [{ validator: validateEmailRule }, { validator: validateBindEmail, trigger: 'blur' }]
|
||||
: [],
|
||||
} as Record<string, Rule[]>
|
||||
})
|
||||
// 事件
|
||||
const emits = defineEmits(['ok'])
|
||||
|
||||
/**
|
||||
* 入口
|
||||
*/
|
||||
function init(id) {
|
||||
visible.value = true
|
||||
confirmLoading.value = true
|
||||
nextTick(() => {
|
||||
formRef.value?.resetFields()
|
||||
})
|
||||
get(id).then(({ data }) => {
|
||||
rawForm = { ...data }
|
||||
form.value = data
|
||||
confirmLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*/
|
||||
function handleOk() {
|
||||
formRef.value?.validate().then(async () => {
|
||||
confirmLoading.value = true
|
||||
await update({
|
||||
...form.value,
|
||||
...diffForm(form.value, rawForm, 'password'),
|
||||
}).finally(() => (confirmLoading.value = false))
|
||||
createMessage.success('用户更改成功')
|
||||
confirmLoading.value = false
|
||||
emits('ok')
|
||||
handleCancel()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户名检查
|
||||
*/
|
||||
async function checkAccount() {
|
||||
const { data } = await existsAccountNotId(form.value.account, form.value.id)
|
||||
return data ? Promise.reject('用户名已存在!') : Promise.resolve()
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验邮箱号
|
||||
*/
|
||||
function validateEmailRule() {
|
||||
const { email } = form.value
|
||||
if (!email) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
const { msg, result } = validateEmail(email)
|
||||
return result ? Promise.resolve() : Promise.reject(msg)
|
||||
}
|
||||
/**
|
||||
* 后台校验邮箱号是否被使用
|
||||
*/
|
||||
async function validateBindEmail() {
|
||||
const { email, id } = form.value
|
||||
if (rawForm.email === email) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
const { data } = await existsEmailNotId(email, id)
|
||||
return data ? Promise.reject('邮箱已被使用') : Promise.resolve()
|
||||
}
|
||||
/**
|
||||
* 校验手机号
|
||||
*/
|
||||
function validatePhone() {
|
||||
const { phone } = form.value
|
||||
if (!phone) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
const { msg, result } = validateMobile(phone)
|
||||
return result ? Promise.resolve() : Promise.reject(msg)
|
||||
}
|
||||
/**
|
||||
* 后台校验手机号是否被使用
|
||||
*/
|
||||
async function validateBindPhone() {
|
||||
const { phone, id } = form.value
|
||||
const { data } = await existsPhoneNotId(phone, id)
|
||||
return data ? Promise.reject('手机号已被使用') : Promise.resolve()
|
||||
}
|
||||
defineExpose({ init })
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
92
src/views/daxpay/merchant/user/UserResetPwd.vue
Normal file
92
src/views/daxpay/merchant/user/UserResetPwd.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<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 './MerchantUser.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(b: boolean, userIdOrIds: string | string[]) {
|
||||
visible.value = true
|
||||
batch.value = b
|
||||
if (!b) {
|
||||
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).finally(
|
||||
() => (confirmLoading.value = false),
|
||||
)
|
||||
} else {
|
||||
await restartPassword(userId.value, form.value.newPassword).finally(
|
||||
() => (confirmLoading.value = false),
|
||||
)
|
||||
}
|
||||
createMessage.success('保存成功')
|
||||
handleCancel()
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
defineExpose({ init })
|
||||
</script>
|
||||
<style scoped lang="less"></style>
|
||||
269
src/views/daxpay/merchant/user/UserRoleAssign.vue
Normal file
269
src/views/daxpay/merchant/user/UserRoleAssign.vue
Normal 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 { useMessage } from '@/hooks/web/useMessage'
|
||||
import { addUserRole, addUserRoleBatch, getRoleIds } from './MerchantUser.api'
|
||||
|
||||
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>
|
||||
85
src/views/daxpay/merchant/user/UserShow.vue
Normal file
85
src/views/daxpay/merchant/user/UserShow.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<basic-drawer
|
||||
show-footer
|
||||
title="用户信息"
|
||||
v-bind="$attrs"
|
||||
:width="modalWidth"
|
||||
:open="visible"
|
||||
:mask-closable="false"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<a-form
|
||||
shio
|
||||
class="small-from-item"
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item label="用户账号" name="account">
|
||||
<a-input disabled v-model:value="form.account" />
|
||||
</a-form-item>
|
||||
<a-form-item label="用户名称" name="name">
|
||||
<a-input disabled v-model:value="form.name" />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input disabled v-model:value="form.phone" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱">
|
||||
<a-input disabled v-model:value="form.email" />
|
||||
</a-form-item>
|
||||
<a-form-item label="管理员">
|
||||
<a-tag v-if="form.administrator" color="green">是</a-tag>
|
||||
<a-tag v-else>否</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item label="用户状态">
|
||||
<a-tag>{{ dictConvert('UserStatusCode', form.status) || '无' }}</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item label="角色列表">
|
||||
<a-tag color="green" v-for="o in roles" :key="o.id">{{ o.name }}</a-tag>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="handleCancel">取消</a-button>
|
||||
</template>
|
||||
</basic-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useFormEdit from '@/hooks/bootx/useFormEdit'
|
||||
import BasicDrawer from '/@/components/Drawer/src/BasicDrawer.vue'
|
||||
import { get, getRoles } from './MerchantUser.api'
|
||||
import { useDict } from '@/hooks/bootx/useDict'
|
||||
import { ref } from 'vue'
|
||||
import { Role } from '@/views/iam/role/Role.api'
|
||||
import { UserInfo } from '@/views/iam/user/User.api'
|
||||
|
||||
const { handleCancel, labelCol, wrapperCol, modalWidth, confirmLoading, visible } = useFormEdit()
|
||||
const { dictConvert } = useDict()
|
||||
|
||||
let form = ref<UserInfo>({
|
||||
name: '',
|
||||
account: '',
|
||||
})
|
||||
let roles = ref<Role[]>([])
|
||||
|
||||
async function init(id) {
|
||||
confirmLoading.value = true
|
||||
visible.value = true
|
||||
|
||||
Promise.all([
|
||||
get(id).then((res) => {
|
||||
form.value = res.data
|
||||
}),
|
||||
getRoles(id).then((res) => {
|
||||
roles.value = res.data
|
||||
}),
|
||||
]).then(() => (confirmLoading.value = false))
|
||||
}
|
||||
|
||||
defineExpose({ init })
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -111,8 +111,8 @@
|
||||
<UserAdd ref="userAdd" @ok="queryPage" />
|
||||
<UserEdit ref="userEdit" @ok="queryPage" />
|
||||
<UserShow ref="userShow" />
|
||||
<UserRoleAssign ref="userRoleAssign" />
|
||||
<UserResetPwd ref="userResetPwd" />
|
||||
<UserRoleAssign ref="userRoleAssign" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -134,7 +134,7 @@
|
||||
import UserResetPwd from './UserResetPwd.vue'
|
||||
import UserRoleAssign from './role/UserRoleAssign.vue'
|
||||
import ALink from '@/components/Link/Link.vue'
|
||||
import { useFilePlatform } from "@/hooks/bootx/useFilePlatform";
|
||||
import { useFilePlatform } from '@/hooks/bootx/useFilePlatform'
|
||||
|
||||
// 使用hooks
|
||||
const {
|
||||
@@ -149,7 +149,7 @@
|
||||
} = useTablePage(queryPage)
|
||||
const { createConfirm } = useMessage()
|
||||
const { dictConvert } = useDict()
|
||||
const {getFileUrl} = useFilePlatform()
|
||||
const { getFileUrl } = useFilePlatform()
|
||||
|
||||
// 查询条件
|
||||
const fields = [
|
||||
|
||||
Reference in New Issue
Block a user