100 changed files with 1713 additions and 3256 deletions
@ -1,36 +0,0 @@
|
||||
import type { GetUserInfoModel, LoginParams, LoginResultModel, SmsLoginParams } from './model/userModel' |
||||
import { defHttp } from '@/utils/http/axios' |
||||
|
||||
import type { ErrorMessageMode } from '@/types/axios' |
||||
|
||||
enum Api { |
||||
Login = '/system/auth/login', |
||||
Logout = '/system/auth/logout', |
||||
SmsLogin = '/system/auth/sms-login', |
||||
GetUserInfo = '/system/auth/get-permission-info', |
||||
} |
||||
|
||||
/** |
||||
* @description: user login api |
||||
*/ |
||||
export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') { |
||||
return defHttp.post<LoginResultModel>({ url: Api.Login, params }, { errorMessageMode: mode }) |
||||
} |
||||
|
||||
/** |
||||
* @description: user smslogin api |
||||
*/ |
||||
export function smsLogin(params: SmsLoginParams, mode: ErrorMessageMode = 'modal') { |
||||
return defHttp.post<LoginResultModel>({ url: Api.SmsLogin, params }, { errorMessageMode: mode }) |
||||
} |
||||
|
||||
/** |
||||
* @description: getUserInfo |
||||
*/ |
||||
export function getUserInfo() { |
||||
return defHttp.get<GetUserInfoModel>({ url: Api.GetUserInfo }, { errorMessageMode: 'none' }) |
||||
} |
||||
|
||||
export function doLogout() { |
||||
return defHttp.post({ url: Api.Logout }) |
||||
} |
@ -0,0 +1,29 @@
|
||||
import type { LoginParams, LoginResult, UserInfo } from './types' |
||||
import { defHttp } from '@/utils/http/axios' |
||||
import type { ErrorMessageMode } from '@/types/axios' |
||||
|
||||
export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') { |
||||
return defHttp.post<LoginResult>({ |
||||
url: '/auth/login', |
||||
params, |
||||
headers: { |
||||
'tenant-id': '000000', |
||||
}, |
||||
}, { |
||||
errorMessageMode: mode, |
||||
}) |
||||
} |
||||
|
||||
export function getUserInfo() { |
||||
return defHttp.get<UserInfo>({ |
||||
url: '/system/permission/get-permission-info', |
||||
}, { |
||||
errorMessageMode: 'none', |
||||
}) |
||||
} |
||||
|
||||
export function doLogout() { |
||||
return defHttp.post({ |
||||
url: '/auth/logout', |
||||
}) |
||||
} |
@ -0,0 +1,32 @@
|
||||
import type { MenuItem } from '@/api/system/menu/types' |
||||
|
||||
export interface LoginParams { |
||||
username: string |
||||
password: string |
||||
} |
||||
|
||||
export interface LoginResult { |
||||
userId: string |
||||
accessToken: string |
||||
refreshToken: string |
||||
expiresTime: number |
||||
} |
||||
|
||||
export interface UserInfo { |
||||
user: User |
||||
menus: MenuItem[] |
||||
buttons: string[] |
||||
} |
||||
|
||||
export interface User { |
||||
id: string |
||||
tenantId: string |
||||
account: string |
||||
realName: string |
||||
avatar?: string |
||||
email?: string |
||||
mobile?: string |
||||
remark?: string |
||||
deptId: string |
||||
roleId: string |
||||
} |
@ -1,48 +1,36 @@
|
||||
import type { Department, LazyGetDeptListParams } from './types' |
||||
import { defHttp } from '@/utils/http/axios' |
||||
|
||||
export interface DeptVO { |
||||
id?: number |
||||
name: string |
||||
parentId: number |
||||
status: number |
||||
sort: number |
||||
leaderUserId: number |
||||
phone: string |
||||
email: string |
||||
createTime: Date |
||||
export function lazyGetDeptList(params?: LazyGetDeptListParams) { |
||||
return defHttp.get<Department[]>({ |
||||
url: '/system/dept/lazy-list', |
||||
params, |
||||
}) |
||||
} |
||||
|
||||
export interface DeptPageReqVO { |
||||
name?: string |
||||
status?: number |
||||
export function createDept(data: Partial<Department>) { |
||||
return defHttp.post({ |
||||
url: '/system/dept/save', |
||||
data, |
||||
}) |
||||
} |
||||
|
||||
// 查询部门(精简)列表
|
||||
export function listSimpleDept() { |
||||
return defHttp.get({ url: '/system/dept/list-all-simple' }) |
||||
export function updateDept(data: Partial<Department>) { |
||||
return defHttp.post({ |
||||
url: '/system/dept/update', |
||||
data, |
||||
}) |
||||
} |
||||
|
||||
// 查询部门列表
|
||||
export function getDeptPage(params: DeptPageReqVO) { |
||||
return defHttp.get({ url: '/system/dept/list', params }) |
||||
export function deleteDept(id: string) { |
||||
return defHttp.post({ |
||||
url: `/system/dept/delete?id=${id}`, |
||||
}) |
||||
} |
||||
|
||||
// 查询部门详情
|
||||
export function getDept(id: number) { |
||||
return defHttp.get({ url: `/system/dept/get?id=${id}` }) |
||||
} |
||||
|
||||
// 新增部门
|
||||
export function createDept(data: DeptVO) { |
||||
return defHttp.post({ url: '/system/dept/create', data }) |
||||
} |
||||
|
||||
// 修改部门
|
||||
export function updateDept(params: DeptVO) { |
||||
return defHttp.put({ url: '/system/dept/update', data: params }) |
||||
} |
||||
|
||||
// 删除部门
|
||||
export function deleteDept(id: number) { |
||||
return defHttp.delete({ url: `/system/dept/delete?id=${id}` }) |
||||
export function getDeptTree(params?: { tenantId: string }) { |
||||
return defHttp.get<{ id: string, title: string }[]>({ |
||||
url: '/system/dept/tree', |
||||
params, |
||||
}) |
||||
} |
||||
|
@ -0,0 +1,17 @@
|
||||
export interface Department { |
||||
id: string |
||||
tenantId: string |
||||
parentId: string |
||||
deptName: string |
||||
parentName: string |
||||
deptTypeName: string |
||||
children: Department[] |
||||
hasChildren: boolean |
||||
} |
||||
|
||||
export interface LazyGetDeptListParams { |
||||
id?: string |
||||
tenantId?: string |
||||
parentId?: string |
||||
deptName?: string |
||||
} |
@ -1,52 +1,41 @@
|
||||
import type { GetMenuListParams, MenuItem } from './types' |
||||
import { defHttp } from '@/utils/http/axios' |
||||
|
||||
export interface MenuVO { |
||||
id: number |
||||
name: string |
||||
permission: string |
||||
type: number |
||||
sort: number |
||||
parentId: number |
||||
path: string |
||||
icon: string |
||||
component: string |
||||
status: number |
||||
visible: boolean |
||||
keepAlive: boolean |
||||
createTime: Date |
||||
export function getMenuListWithoutButtons() { |
||||
return defHttp.get<MenuItem[]>({ |
||||
url: '/system/menu/tree', |
||||
}) |
||||
} |
||||
|
||||
export interface MenuPageReqVO { |
||||
name?: string |
||||
status?: number |
||||
export function getMenuList(params: GetMenuListParams) { |
||||
return defHttp.get<PageResult<MenuItem>>({ |
||||
url: '/system/menu/list', |
||||
params, |
||||
}) |
||||
} |
||||
|
||||
// 查询菜单(精简)列表
|
||||
export function listSimpleMenus() { |
||||
return defHttp.get({ url: '/system/menu/list-all-simple' }) |
||||
export function getMenu(id: string) { |
||||
return defHttp.get({ |
||||
url: `/system/menu/get?id=${id}`, |
||||
}) |
||||
} |
||||
|
||||
// 查询菜单列表
|
||||
export function getMenuList(params: MenuPageReqVO) { |
||||
return defHttp.get({ url: '/system/menu/list', params }) |
||||
export function createMenu(data: Omit<MenuItem, 'id' | 'children'>) { |
||||
return defHttp.post({ |
||||
url: '/system/menu/save', |
||||
data, |
||||
}) |
||||
} |
||||
|
||||
// 获取菜单详情
|
||||
export function getMenu(id: number) { |
||||
return defHttp.get({ url: `/system/menu/get?id=${id}` }) |
||||
export function updateMenu(data: Omit<MenuItem, 'children'>) { |
||||
return defHttp.post({ |
||||
url: '/system/menu/update', |
||||
data, |
||||
}) |
||||
} |
||||
|
||||
// 新增菜单
|
||||
export function createMenu(data: MenuVO) { |
||||
return defHttp.post({ url: '/system/menu/create', data }) |
||||
} |
||||
|
||||
// 修改菜单
|
||||
export function updateMenu(data: MenuVO) { |
||||
return defHttp.put({ url: '/system/menu/update', data }) |
||||
} |
||||
|
||||
// 删除菜单
|
||||
export function deleteMenu(id: number) { |
||||
return defHttp.delete({ url: `/system/menu/delete?id=${id}` }) |
||||
export function deleteMenu(id: string) { |
||||
return defHttp.post({ |
||||
url: `/system/menu/delete?id=${id}`, |
||||
}) |
||||
} |
||||
|
@ -0,0 +1,26 @@
|
||||
export interface GetMenuListParams { |
||||
name?: string |
||||
code?: string |
||||
} |
||||
|
||||
export enum SystemMenuTypeEnum { |
||||
DIR = 0, // 目录
|
||||
MENU = 1, // 菜单
|
||||
BUTTON = 2, // 按钮
|
||||
} |
||||
|
||||
export interface MenuItem { |
||||
id: string |
||||
parentId: string |
||||
name: string |
||||
component: string |
||||
componentName?: string |
||||
path: string |
||||
icon: string |
||||
sort: number |
||||
children: MenuItem[] |
||||
code: string |
||||
type: SystemMenuTypeEnum |
||||
visible: BooleanFlag |
||||
keepAlive: BooleanFlag |
||||
} |
@ -1,70 +1,56 @@
|
||||
import type { GetRoleListParams, MenuTreeNode, Role } from './types' |
||||
import { defHttp } from '@/utils/http/axios' |
||||
|
||||
export interface RoleVO { |
||||
id: number |
||||
name: string |
||||
code: string |
||||
sort: number |
||||
status: number |
||||
type: number |
||||
createTime: Date |
||||
export function lazyGetRoleList(params: GetRoleListParams) { |
||||
return defHttp.get<Role[]>({ |
||||
url: '/system/role/lazy-list', |
||||
params, |
||||
}) |
||||
} |
||||
|
||||
export interface RolePageReqVO extends PageParam { |
||||
name?: string |
||||
code?: string |
||||
status?: number |
||||
createTime?: Date[] |
||||
export function createRole(data: Partial<Role>) { |
||||
return defHttp.post({ |
||||
url: '/system/role/save', |
||||
data, |
||||
}) |
||||
} |
||||
|
||||
export interface UpdateStatusReqVO { |
||||
id: number |
||||
status: number |
||||
export function updateRole(data: Partial<Role>) { |
||||
return defHttp.post({ |
||||
url: '/system/role/update', |
||||
data, |
||||
}) |
||||
} |
||||
|
||||
export interface RoleExportReqVO { |
||||
name?: string |
||||
code?: string |
||||
status?: number |
||||
createTime?: Date[] |
||||
export function deleteRole(id: string) { |
||||
return defHttp.post({ |
||||
url: `/system/role/delete?id=${id}`, |
||||
}) |
||||
} |
||||
|
||||
// 查询角色列表
|
||||
export function getRolePage(params: RolePageReqVO) { |
||||
return defHttp.get({ url: '/system/role/page', params }) |
||||
export function getRoleTree(params?: { tenantId: string }) { |
||||
return defHttp.get<{ id: string, title: string }[]>({ |
||||
url: '/system/role/tree', |
||||
params, |
||||
}) |
||||
} |
||||
|
||||
// 查询角色(精简)列表
|
||||
export function listSimpleRoles() { |
||||
return defHttp.get({ url: '/system/role/list-all-simple' }) |
||||
export function getMenuTree() { |
||||
return defHttp.get<MenuTreeNode[]>({ |
||||
url: '/system/menu/grant-tree', |
||||
}) |
||||
} |
||||
|
||||
// 查询角色详情
|
||||
export function getRole(id: number) { |
||||
return defHttp.get({ url: `/system/role/get?id=${id}` }) |
||||
export function getMenuIdsByRole(roleId: string) { |
||||
return defHttp.get<string[]>({ |
||||
url: '/system/permission/list-role-menus', |
||||
params: { roleId }, |
||||
}) |
||||
} |
||||
|
||||
// 新增角色
|
||||
export function createRole(data: RoleVO) { |
||||
return defHttp.post({ url: '/system/role/create', data }) |
||||
} |
||||
|
||||
// 修改角色
|
||||
export function updateRole(data: RoleVO) { |
||||
return defHttp.put({ url: '/system/role/update', data }) |
||||
} |
||||
|
||||
// 修改角色状态
|
||||
export function updateRoleStatus(data: UpdateStatusReqVO) { |
||||
return defHttp.put({ url: '/system/role/update-status', data }) |
||||
} |
||||
|
||||
// 删除角色
|
||||
export function deleteRole(id: number) { |
||||
return defHttp.delete({ url: `/system/role/delete?id=${id}` }) |
||||
} |
||||
|
||||
// 导出角色
|
||||
export function exportRole(params: RoleExportReqVO) { |
||||
return defHttp.download({ url: '/system/post/export', params }, '导出角色.xls') |
||||
export function assignMenuToRole(data: { roleId: string, menuIds: string[] }) { |
||||
return defHttp.post({ |
||||
url: '/system/permission/assign-role-menu', |
||||
data, |
||||
}) |
||||
} |
||||
|
@ -0,0 +1,29 @@
|
||||
export interface GetRoleListParams extends PageParam { |
||||
tenantId?: string |
||||
parentId?: string |
||||
roleName?: string |
||||
roleAlias?: string |
||||
} |
||||
|
||||
export interface Role { |
||||
id: string |
||||
tenantId: string |
||||
tenantName: string |
||||
parentId: string |
||||
roleName: string |
||||
roleAlias: string |
||||
createTime?: string |
||||
createUser?: string |
||||
createDept?: string |
||||
updateTime?: string |
||||
updateUser?: string |
||||
hasChildren?: boolean |
||||
children?: Role[] |
||||
} |
||||
|
||||
export interface MenuTreeNode { |
||||
id: string |
||||
parentId: string |
||||
title: string |
||||
children?: MenuTreeNode[] |
||||
} |
@ -1,62 +1,35 @@
|
||||
import type { GetTenantListParams, Tenant } from './types' |
||||
import { defHttp } from '@/utils/http/axios' |
||||
|
||||
export interface TenantVO { |
||||
id: number |
||||
name: string |
||||
contactName: string |
||||
contactMobile: string |
||||
status: number |
||||
domain: string |
||||
packageId: number |
||||
username: string |
||||
password: string |
||||
expireTime: Date |
||||
accountCount: number |
||||
createTime: Date |
||||
export function getTenantList(params: GetTenantListParams) { |
||||
return defHttp.get<PageResult<Tenant>>({ |
||||
url: '/system/tenant/page', |
||||
params, |
||||
}) |
||||
} |
||||
|
||||
export interface TenantPageReqVO extends PageParam { |
||||
name?: string |
||||
contactName?: string |
||||
contactMobile?: string |
||||
status?: number |
||||
createTime?: Date[] |
||||
export function updateTenant(data: Tenant) { |
||||
return defHttp.post({ |
||||
url: '/system/tenant/update', |
||||
data, |
||||
}) |
||||
} |
||||
|
||||
export interface TenantExportReqVO { |
||||
name?: string |
||||
contactName?: string |
||||
contactMobile?: string |
||||
status?: number |
||||
createTime?: Date[] |
||||
export function createTenant(data: Tenant) { |
||||
return defHttp.post({ |
||||
url: '/system/tenant/save', |
||||
data, |
||||
}) |
||||
} |
||||
|
||||
// 查询租户列表
|
||||
export function getTenantPage(params: TenantPageReqVO) { |
||||
return defHttp.get({ url: '/system/tenant/page', params }) |
||||
export function deleteTenant(id: string) { |
||||
return defHttp.post({ |
||||
url: `/system/tenant/remove?id=${id}`, |
||||
}) |
||||
} |
||||
|
||||
// 查询租户详情
|
||||
export function getTenant(id: number) { |
||||
return defHttp.get({ url: `/system/tenant/get?id=${id}` }) |
||||
} |
||||
|
||||
// 新增租户
|
||||
export function createTenant(data: TenantVO) { |
||||
return defHttp.post({ url: '/system/tenant/create', data }) |
||||
} |
||||
|
||||
// 修改租户
|
||||
export function updateTenant(data: TenantVO) { |
||||
return defHttp.put({ url: '/system/tenant/update', data }) |
||||
} |
||||
|
||||
// 删除租户
|
||||
export function deleteTenant(id: number) { |
||||
return defHttp.delete({ url: `/system/tenant/delete?id=${id}` }) |
||||
} |
||||
|
||||
// 导出租户
|
||||
export function exportTenant(params: TenantExportReqVO) { |
||||
return defHttp.download({ url: '/system/tenant/export-excel', params }, '租户.xls') |
||||
export function getAllTenants() { |
||||
return defHttp.get({ |
||||
url: '/system/tenant/select', |
||||
}) |
||||
} |
||||
|
@ -0,0 +1,16 @@
|
||||
export interface Tenant { |
||||
id: string |
||||
tenantId?: string |
||||
tenantName: string |
||||
adminAccount: string |
||||
contactName: string |
||||
contactMobile: string |
||||
expireTime?: string |
||||
} |
||||
|
||||
export interface GetTenantListParams extends PageParam { |
||||
tenantId?: string |
||||
tenantName?: string |
||||
contactName?: string |
||||
contactMobile?: string |
||||
} |
@ -1,91 +1,29 @@
|
||||
import type { GetUserListParams, SystemUser } from './types' |
||||
import { defHttp } from '@/utils/http/axios' |
||||
|
||||
export interface 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: Date |
||||
createTime: Date |
||||
export function getUserList(params: GetUserListParams) { |
||||
return defHttp.get<PageResult<SystemUser>>({ |
||||
url: '/system/user/page', |
||||
params, |
||||
}) |
||||
} |
||||
|
||||
export interface UserPageReqVO extends PageParam { |
||||
deptId?: number |
||||
username?: string |
||||
mobile?: string |
||||
status?: number |
||||
createTime?: Date[] |
||||
export function createUser(data: Partial<SystemUser>) { |
||||
return defHttp.post({ |
||||
url: '/system/user/save', |
||||
data, |
||||
}) |
||||
} |
||||
|
||||
export interface UserExportReqVO { |
||||
code?: string |
||||
name?: string |
||||
status?: number |
||||
createTime?: Date[] |
||||
export function updateUser(data: Partial<SystemUser>) { |
||||
return defHttp.post({ |
||||
url: '/system/user/update', |
||||
data, |
||||
}) |
||||
} |
||||
|
||||
// 查询用户管理列表
|
||||
export function getUserPage(params: UserPageReqVO) { |
||||
return defHttp.get({ url: '/system/user/page', params }) |
||||
} |
||||
|
||||
// 查询用户详情
|
||||
export function getUser(id: number) { |
||||
return defHttp.get({ url: `/system/user/get?id=${id}` }) |
||||
} |
||||
|
||||
// 新增用户
|
||||
export function createUser(data: UserVO) { |
||||
return defHttp.post({ url: '/system/user/create', data }) |
||||
} |
||||
|
||||
// 修改用户
|
||||
export function updateUser(data: UserVO) { |
||||
return defHttp.put({ url: '/system/user/update', data }) |
||||
} |
||||
|
||||
// 删除用户
|
||||
export function deleteUser(id: number) { |
||||
return defHttp.delete({ url: `/system/user/delete?id=${id}` }) |
||||
} |
||||
|
||||
// 导出用户
|
||||
export function exportUser(params: UserExportReqVO) { |
||||
return defHttp.download({ url: '/system/user/export', params }, '用户.xls') |
||||
} |
||||
|
||||
// 下载用户导入模板
|
||||
export function importUserTemplate() { |
||||
return defHttp.download({ url: '/system/user/get-import-template' }, '用户导入模板.xls') |
||||
} |
||||
|
||||
// 用户密码重置
|
||||
export function resetUserPwd(id: number, password: string) { |
||||
const data = { |
||||
id, |
||||
password, |
||||
} |
||||
return defHttp.put({ url: '/system/user/update-password', data }) |
||||
} |
||||
|
||||
// 用户状态修改
|
||||
export function updateUserStatus(id: number, status: number) { |
||||
const data = { |
||||
id, |
||||
status, |
||||
} |
||||
return defHttp.put({ url: '/system/user/update-status', data }) |
||||
} |
||||
|
||||
// 获取用户精简信息列表
|
||||
export function getListSimpleUsers() { |
||||
return defHttp.get({ url: '/system/user/list-all-simple' }) |
||||
export function deleteUser(id: string) { |
||||
return defHttp.post({ |
||||
url: `/system/user/delete?id=${id}`, |
||||
}) |
||||
} |
||||
|
@ -0,0 +1,23 @@
|
||||
export interface GetUserListParams extends PageParam { |
||||
tenantId?: string |
||||
deptId?: string |
||||
account?: string |
||||
mobile?: string |
||||
} |
||||
|
||||
export interface SystemUser { |
||||
id: string |
||||
account: string |
||||
avatar: string |
||||
deptId: string |
||||
deptName: string |
||||
email: string |
||||
mobile: string |
||||
realName: string |
||||
remark: string |
||||
roleId: string |
||||
roleName: string |
||||
sex: 1 | 2 | 3 |
||||
tenantId: string |
||||
tenantName: string |
||||
} |
@ -1,155 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { computed, reactive, ref, unref } from 'vue' |
||||
import { Form, Input } from 'ant-design-vue' |
||||
import LoginFormTitle from './LoginFormTitle.vue' |
||||
import { LoginStateEnum, useFormRules, useFormValid, useLoginState } from './useLogin' |
||||
import { CountdownInput } from '@/components/CountDown' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { useUserStore } from '@/store/modules/user' |
||||
import { usePermissionStore } from '@/store/modules/permission' |
||||
import { useGlobSetting } from '@/hooks/setting' |
||||
import { useDesign } from '@/hooks/web/useDesign' |
||||
import * as authUtil from '@/utils/auth' |
||||
|
||||
import { Verify } from '@/components/Verifition' |
||||
import { getTenantIdByName, sendSmsCode } from '@/api/base/login' |
||||
|
||||
const FormItem = Form.Item |
||||
|
||||
const { t } = useI18n() |
||||
const { prefixCls } = useDesign('login') |
||||
const { createMessage, notification, createErrorModal } = useMessage() |
||||
const { handleBackLogin, getLoginState } = useLoginState() |
||||
const { tenantEnable, captchaEnable } = useGlobSetting() |
||||
const { getFormRules } = useFormRules() |
||||
const userStore = useUserStore() |
||||
const permissionStore = usePermissionStore() |
||||
|
||||
const formRef = ref() |
||||
const loading = ref(false) |
||||
|
||||
const mobileCodeTimer = ref(0) |
||||
const scene = ref(21) |
||||
|
||||
const verify = ref() |
||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 |
||||
|
||||
const formData = reactive({ |
||||
tenantName: '芋道源码', |
||||
mobile: '', |
||||
mobileCode: '', |
||||
captchaVerification: '', |
||||
}) |
||||
|
||||
const { validForm } = useFormValid(formRef) |
||||
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.MOBILE) |
||||
|
||||
// 获取验证码 |
||||
async function getCode() { |
||||
// 情况一,未开启:则直接登录 |
||||
if (captchaEnable === 'false') { |
||||
await handleLogin() |
||||
} |
||||
else { |
||||
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录 |
||||
// 弹出验证码 |
||||
verify.value.show() |
||||
} |
||||
} |
||||
|
||||
// 获取租户ID |
||||
async function getTenantId() { |
||||
if (tenantEnable === 'true') { |
||||
const res = await getTenantIdByName(formData.tenantName) |
||||
authUtil.setTenantId(res) |
||||
} |
||||
} |
||||
|
||||
async function handleLogin() { |
||||
const data = await validForm() |
||||
if (!data) |
||||
return |
||||
try { |
||||
loading.value = true |
||||
const userInfo = await userStore.smsLogin({ |
||||
mobile: data.mobile, |
||||
code: data.mobileCode, |
||||
mode: 'none', // 不要默认的错误提示 |
||||
}) |
||||
if (userInfo) { |
||||
await permissionStore.changePermissionCode(userInfo.permissions) |
||||
notification.success({ |
||||
message: t('sys.login.loginSuccessTitle'), |
||||
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.user.nickname}`, |
||||
duration: 3, |
||||
}) |
||||
} |
||||
} |
||||
catch (error) { |
||||
createErrorModal({ |
||||
title: t('sys.api.errorTip'), |
||||
content: (error as unknown as Error).message || t('sys.api.networkExceptionMsg'), |
||||
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body, |
||||
}) |
||||
} |
||||
finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
async function getSmsCode() { |
||||
await getTenantId() |
||||
if (mobileCodeTimer.value > 0) |
||||
return |
||||
const data = await validForm() |
||||
if (!data) |
||||
return |
||||
const res = await sendSmsCode(formData.mobile, scene.value) |
||||
if (res) { |
||||
createMessage.success(t('common.successText')) |
||||
mobileCodeTimer.value = 60 |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div v-if="getShow"> |
||||
<LoginFormTitle class="enter-x" /> |
||||
<Form ref="formRef" class="enter-x p-4" :model="formData" :rules="getFormRules"> |
||||
<FormItem name="tenantName" class="enter-x"> |
||||
<Input |
||||
v-if="tenantEnable === 'true'" |
||||
v-model:value="formData.tenantName" |
||||
size="large" |
||||
:placeholder="t('sys.login.tenantName')" |
||||
class="fix-auto-fill" |
||||
/> |
||||
</FormItem> |
||||
<FormItem name="mobile" class="enter-x"> |
||||
<Input v-model:value="formData.mobile" size="large" :placeholder="t('sys.login.mobile')" class="fix-auto-fill" /> |
||||
</FormItem> |
||||
<FormItem name="mobileCode" class="enter-x"> |
||||
<CountdownInput |
||||
v-model:value="formData.mobileCode" |
||||
size="large" |
||||
class="fix-auto-fill" |
||||
:count="mobileCodeTimer" |
||||
:send-code-api="getSmsCode" |
||||
:placeholder="t('sys.login.smsCode')" |
||||
/> |
||||
</FormItem> |
||||
|
||||
<FormItem class="enter-x"> |
||||
<a-button type="primary" size="large" block :loading="loading" @click="getCode"> |
||||
{{ t('sys.login.loginButton') }} |
||||
</a-button> |
||||
<a-button size="large" block class="mt-4" @click="handleBackLogin"> |
||||
{{ t('sys.login.backSignIn') }} |
||||
</a-button> |
||||
</FormItem> |
||||
</Form> |
||||
<Verify ref="verify" mode="pop" :captcha-type="captchaType" :img-size="{ width: '400px', height: '200px' }" @success="handleLogin" /> |
||||
</div> |
||||
</template> |
@ -1,37 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { computed, unref } from 'vue' |
||||
import { Divider, Popover, QRCode } from 'ant-design-vue' |
||||
import LoginFormTitle from './LoginFormTitle.vue' |
||||
import { LoginStateEnum, useLoginState } from './useLogin' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
|
||||
import loginImg from '@/assets/images/logo.png' |
||||
|
||||
// Login QR code |
||||
const qrCodeUrl = '' |
||||
|
||||
const { t } = useI18n() |
||||
const { handleBackLogin, getLoginState } = useLoginState() |
||||
|
||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.QR_CODE) |
||||
</script> |
||||
|
||||
<template> |
||||
<div v-if="getShow"> |
||||
<LoginFormTitle class="enter-x" /> |
||||
<div class="enter-x min-h-64 min-w-64"> |
||||
<Popover :overlay-inner-style="{ padding: 0 }"> |
||||
<template #content> |
||||
<QRCode :value="qrCodeUrl" class="enter-x flex justify-center xl:justify-start" :width="280" :bordered="false" /> |
||||
</template> |
||||
<img width="100" height="100" :src="loginImg"> |
||||
</Popover> |
||||
<Divider class="enter-x"> |
||||
{{ t('sys.login.scanSign') }} |
||||
</Divider> |
||||
<a-button size="large" block class="enter-x mt-4" @click="handleBackLogin"> |
||||
{{ t('sys.login.backSignIn') }} |
||||
</a-button> |
||||
</div> |
||||
</div> |
||||
</template> |
@ -1,199 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { onMounted, reactive, ref } from 'vue' |
||||
import { useRoute } from 'vue-router' |
||||
import { Checkbox, Col, Form, Row } from 'ant-design-vue' |
||||
|
||||
import { useFormValid, useLoginState } from './useLogin' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
|
||||
import { useDesign } from '@/hooks/web/useDesign' |
||||
import { authorize, getAuthorize } from '@/api/base/login' |
||||
|
||||
const FormItem = Form.Item |
||||
|
||||
const { t } = useI18n() |
||||
const { query } = useRoute() |
||||
const { notification, createErrorModal } = useMessage() |
||||
const { prefixCls } = useDesign('login') |
||||
|
||||
const { handleBackLogin } = useLoginState() |
||||
|
||||
const formRef = ref() |
||||
const loading = ref(false) |
||||
|
||||
const loginForm = reactive({ |
||||
scopes: [] as any[], // 已选中的 scope 数组 |
||||
}) |
||||
|
||||
// URL 上的 client_id、scope 等参数 |
||||
const params = reactive({ |
||||
responseType: undefined as any, |
||||
clientId: undefined as any, |
||||
redirectUri: undefined as any, |
||||
state: undefined as any, |
||||
scopes: [] as any[], // 优先从 query 参数获取;如果未传递,从后端获取 |
||||
}) |
||||
|
||||
// 客户端信息 |
||||
let client = reactive({ |
||||
name: '', |
||||
logo: '', |
||||
}) |
||||
|
||||
const { validForm } = useFormValid(formRef) |
||||
|
||||
async function init() { |
||||
// 解析参数 |
||||
// 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write |
||||
// 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read |
||||
params.responseType = query.response_type as any |
||||
params.clientId = query.client_id as any |
||||
params.redirectUri = query.redirect_uri as any |
||||
params.state = query.state as any |
||||
if (query.scope) |
||||
params.scopes = (query.scope as any).split(' ') |
||||
|
||||
// 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。 |
||||
if (params.scopes.length > 0) { |
||||
const res = await doAuthorize(true, params.scopes, []) |
||||
const href = res |
||||
if (!href) { |
||||
console.log('自动授权未通过!') |
||||
return |
||||
} |
||||
location.href = href |
||||
} |
||||
|
||||
// 获取授权页的基本信息 |
||||
const res = await getAuthorize(params.clientId) |
||||
client = res.client |
||||
// 解析 scope |
||||
let scopes |
||||
// 1.1 如果 params.scope 非空,则过滤下返回的 scopes |
||||
if (params.scopes.length > 0) { |
||||
scopes = [] |
||||
for (const scope of res.scopes) { |
||||
if (params.scopes.includes(scope.key)) |
||||
scopes.push(scope) |
||||
} |
||||
// 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它 |
||||
} |
||||
else { |
||||
scopes = res.scopes |
||||
for (const scope of scopes) |
||||
params.scopes.push(scope.key) |
||||
} |
||||
// 生成已选中的 checkedScopes |
||||
for (const scope of scopes) { |
||||
if (scope.value) |
||||
loginForm.scopes.push(scope.key) |
||||
} |
||||
} |
||||
|
||||
async function handleAuthorize(approved) { |
||||
const data = await validForm() |
||||
if (!data) |
||||
return |
||||
try { |
||||
loading.value = true |
||||
// 计算 checkedScopes + uncheckedScopes |
||||
let checkedScopes |
||||
let uncheckedScopes |
||||
if (approved) { |
||||
// 同意授权,按照用户的选择 |
||||
checkedScopes = loginForm.scopes |
||||
uncheckedScopes = params.scopes.filter(item => !checkedScopes.includes(item)) |
||||
} |
||||
else { |
||||
// 拒绝,则都是取消 |
||||
checkedScopes = [] |
||||
uncheckedScopes = params.scopes |
||||
} |
||||
// 提交授权的请求 |
||||
const res = await doAuthorize(false, checkedScopes, uncheckedScopes) |
||||
if (res) { |
||||
const href = res |
||||
if (!href) |
||||
return |
||||
|
||||
location.href = href |
||||
notification.success({ |
||||
message: t('sys.login.loginSuccessTitle'), |
||||
description: `${t('sys.login.loginSuccessDesc')}`, |
||||
duration: 3, |
||||
}) |
||||
} |
||||
} |
||||
catch (error) { |
||||
createErrorModal({ |
||||
title: t('sys.api.errorTip'), |
||||
content: (error as unknown as Error).message || t('sys.api.networkExceptionMsg'), |
||||
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body, |
||||
}) |
||||
} |
||||
finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
async function doAuthorize(autoApprove, checkedScopes, uncheckedScopes) { |
||||
return await authorize( |
||||
params.responseType, |
||||
params.clientId, |
||||
params.redirectUri, |
||||
params.state, |
||||
autoApprove, |
||||
checkedScopes, |
||||
uncheckedScopes, |
||||
) |
||||
} |
||||
|
||||
function formatScope(scope) { |
||||
// 格式化 scope 授权范围,方便用户理解。 |
||||
// 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。 |
||||
switch (scope) { |
||||
case 'user.read': |
||||
return t('sys.login.ssoInfoDesc') |
||||
case 'user.write': |
||||
return t('sys.login.ssoEditDesc') |
||||
default: |
||||
return scope |
||||
} |
||||
} |
||||
|
||||
onMounted(() => { |
||||
init() |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<h2 class="enter-x mb-3 text-center text-2xl font-bold xl:text-left xl:text-3xl"> |
||||
{{ client.name + t('sys.login.ssoSignInFormTitle') }} |
||||
</h2> |
||||
<Form ref="formRef" class="enter-x p-4" :model="loginForm" @keypress.enter="handleAuthorize(true)"> |
||||
此第三方应用请求获取以下权限: |
||||
<Row class="enter-x"> |
||||
<Col :span="12"> |
||||
<template v-for="scope in params.scopes" :key="scope"> |
||||
<FormItem> |
||||
<!-- No logic, you need to deal with it yourself --> |
||||
<Checkbox :checked="scope" size="small"> |
||||
<a-button type="link" size="small"> |
||||
{{ formatScope(scope) }} |
||||
</a-button> |
||||
</Checkbox> |
||||
</FormItem> |
||||
</template> |
||||
</Col> |
||||
</Row> |
||||
|
||||
<FormItem class="enter-x"> |
||||
<a-button type="primary" size="large" block :loading="loading" @click="handleAuthorize(true)"> |
||||
{{ t('sys.login.loginButton') }} |
||||
</a-button> |
||||
<a-button size="large" class="enter-x mt-4" block @click="handleBackLogin"> |
||||
{{ t('common.cancelText') }} |
||||
</a-button> |
||||
</FormItem> |
||||
</Form> |
||||
</template> |
@ -1,200 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { computed } from 'vue' |
||||
import SSOForm from './SSOForm.vue' |
||||
import { AppDarkModeToggle, AppLocalePicker, AppLogo } from '@/components/Application' |
||||
import { useGlobSetting } from '@/hooks/setting' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useDesign } from '@/hooks/web/useDesign' |
||||
import { useLocaleStore } from '@/store/modules/locale' |
||||
|
||||
defineProps({ |
||||
sessionTimeout: { |
||||
type: Boolean, |
||||
}, |
||||
}) |
||||
|
||||
const globSetting = useGlobSetting() |
||||
const { prefixCls } = useDesign('login') |
||||
const { t } = useI18n() |
||||
const localeStore = useLocaleStore() |
||||
const showLocale = localeStore.getShowPicker |
||||
const title = computed(() => globSetting?.title ?? '') |
||||
</script> |
||||
|
||||
<template> |
||||
<div :class="prefixCls" class="relative h-full w-full px-4"> |
||||
<div class="absolute right-4 top-4 flex items-center"> |
||||
<AppDarkModeToggle v-if="!sessionTimeout" class="enter-x mr-2" /> |
||||
<AppLocalePicker v-if="!sessionTimeout && showLocale" class="enter-x text-white xl:text-gray-600" :show-text="false" /> |
||||
</div> |
||||
|
||||
<span class="-enter-x xl:hidden"> |
||||
<AppLogo :always-show-title="true" /> |
||||
</span> |
||||
|
||||
<div class="relative mx-auto h-full py-2 container sm:px-10"> |
||||
<div class="h-full flex"> |
||||
<div class="mr-4 hidden min-h-full pl-4 xl:w-6/12 xl:flex xl:flex-col"> |
||||
<AppLogo class="-enter-x" /> |
||||
<div class="my-auto"> |
||||
<img :alt="title" src="@/assets/svg/login-box-bg.svg" class="-enter-x w-1/2 -mt-16"> |
||||
<div class="-enter-x mt-10 text-white font-medium"> |
||||
<span class="mt-4 inline-block text-3xl"> {{ t('sys.login.signInTitle') }}</span> |
||||
</div> |
||||
<div class="-enter-x mt-5 text-white font-normal dark:text-gray-500"> |
||||
{{ t('sys.login.signInDesc') }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="h-full w-full flex py-5 xl:my-0 xl:h-auto xl:w-6/12 xl:py-0"> |
||||
<!-- eslint-disable max-len --> |
||||
<div |
||||
:class="`${prefixCls}-form`" |
||||
class="enter-x relative mx-auto my-auto w-full rounded-md px-5 py-8 shadow-md xl:ml-16 lg:w-2/4 sm:w-3/4 xl:w-auto xl:bg-transparent xl:p-4 sm:px-8 xl:shadow-none" |
||||
> |
||||
<SSOForm /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="less"> |
||||
@prefix-cls: ~'@{namespace}-login'; |
||||
@logo-prefix-cls: ~'@{namespace}-app-logo'; |
||||
@countdown-prefix-cls: ~'@{namespace}-countdown-input'; |
||||
@dark-bg: #293146; |
||||
|
||||
html[data-theme='dark'] { |
||||
.@{prefix-cls} { |
||||
background-color: @dark-bg; |
||||
|
||||
&::before { |
||||
background-image: url('@/assets/svg/login-bg-dark.svg'); |
||||
} |
||||
|
||||
.ant-input, |
||||
.ant-input-password { |
||||
background-color: #232a3b; |
||||
} |
||||
|
||||
.ant-btn:not(.ant-btn-link, .ant-btn-primary) { |
||||
border: 1px solid #4a5569; |
||||
} |
||||
|
||||
&-form { |
||||
background: transparent !important; |
||||
} |
||||
|
||||
.app-iconify { |
||||
color: #fff; |
||||
} |
||||
} |
||||
|
||||
input.fix-auto-fill, |
||||
.fix-auto-fill input { |
||||
-webkit-text-fill-color: #c9d1d9 !important; |
||||
box-shadow: inherit !important; |
||||
} |
||||
} |
||||
|
||||
.@{prefix-cls} { |
||||
min-height: 100%; |
||||
overflow: hidden; |
||||
|
||||
@media (max-width: @screen-xl) { |
||||
background-color: #293146; |
||||
|
||||
.@{prefix-cls}-form { |
||||
background-color: #fff; |
||||
} |
||||
} |
||||
|
||||
&::before { |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
margin-left: -48%; |
||||
content: ''; |
||||
background-image: url('@/assets/svg/login-bg.svg'); |
||||
background-repeat: no-repeat; |
||||
background-position: 100%; |
||||
background-size: auto 100%; |
||||
|
||||
@media (max-width: @screen-xl) { |
||||
display: none; |
||||
} |
||||
} |
||||
|
||||
.@{logo-prefix-cls} { |
||||
position: absolute; |
||||
top: 12px; |
||||
height: 30px; |
||||
|
||||
&__title { |
||||
font-size: 16px; |
||||
color: #fff; |
||||
} |
||||
|
||||
img { |
||||
width: 32px; |
||||
} |
||||
} |
||||
|
||||
.container { |
||||
.@{logo-prefix-cls} { |
||||
display: flex; |
||||
width: 60%; |
||||
height: 80px; |
||||
|
||||
&__title { |
||||
font-size: 24px; |
||||
color: #fff; |
||||
} |
||||
|
||||
img { |
||||
width: 48px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
&-sign-in-way { |
||||
.anticon { |
||||
font-size: 22px; |
||||
color: #888; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
|
||||
input:not([type='checkbox']) { |
||||
min-width: 360px; |
||||
|
||||
@media (max-width: @screen-xl) { |
||||
min-width: 320px; |
||||
} |
||||
|
||||
@media (max-width: @screen-lg) { |
||||
min-width: 260px; |
||||
} |
||||
|
||||
@media (max-width: @screen-md) { |
||||
min-width: 240px; |
||||
} |
||||
|
||||
@media (max-width: @screen-sm) { |
||||
min-width: 160px; |
||||
} |
||||
} |
||||
|
||||
.@{countdown-prefix-cls} input { |
||||
min-width: unset; |
||||
} |
||||
|
||||
.ant-divider-inner-text { |
||||
font-size: 12px; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,59 @@
|
||||
<script lang="ts" setup> |
||||
import { ref } from 'vue' |
||||
import { formSchema } from './data' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import { createDept, updateDept } from '@/api/system/dept' |
||||
import type { Department } from '@/api/system/dept/types' |
||||
|
||||
defineOptions({ name: 'DeptFormModal' }) |
||||
|
||||
const emit = defineEmits(['success', 'register']) |
||||
const { t } = useI18n() |
||||
|
||||
const [registerForm, { setFieldsValue, validate }] = useForm({ |
||||
labelWidth: 120, |
||||
baseColProps: { span: 24 }, |
||||
schemas: formSchema, |
||||
showActionButtonGroup: false, |
||||
actionColOptions: { span: 23 }, |
||||
}) |
||||
|
||||
const isUpdate = ref(false) |
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: Department) => { |
||||
isUpdate.value = true |
||||
setFieldsValue({ |
||||
...data, |
||||
parentId: data.parentId === '0' ? undefined : data.parentId, |
||||
}) |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate<Department>() |
||||
setModalProps({ confirmLoading: true }) |
||||
values.parentId = values.parentId ? values.parentId : '0' |
||||
await (isUpdate.value ? updateDept(values) : createDept(values)) |
||||
closeModal() |
||||
emit('success') |
||||
useMessage().createMessage.success(t('common.saveSuccessText')) |
||||
} |
||||
finally { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal |
||||
v-bind="$attrs" |
||||
:title="isUpdate ? t('action.edit') : t('action.create')" |
||||
@register="registerModal" |
||||
@ok="handleSubmit" |
||||
@cancel="isUpdate = false" |
||||
> |
||||
<BasicForm @register="registerForm" /> |
||||
</BasicModal> |
||||
</template> |
@ -1,61 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { ref, unref } from 'vue' |
||||
import { formSchema } from './dept.data' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import { createDept, getDept, updateDept } from '@/api/system/dept' |
||||
|
||||
defineOptions({ name: 'DeptModal' }) |
||||
|
||||
const emit = defineEmits(['success', 'register']) |
||||
const { t } = useI18n() |
||||
const { createMessage } = useMessage() |
||||
const isUpdate = ref(true) |
||||
|
||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
||||
labelWidth: 120, |
||||
baseColProps: { span: 24 }, |
||||
schemas: formSchema, |
||||
showActionButtonGroup: false, |
||||
actionColOptions: { span: 23 }, |
||||
}) |
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
||||
resetFields() |
||||
setModalProps({ confirmLoading: false }) |
||||
isUpdate.value = !!data?.isUpdate |
||||
if (unref(isUpdate)) { |
||||
const res = await getDept(data.record.id) |
||||
setFieldsValue({ ...res }) |
||||
} |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate() as any |
||||
setModalProps({ confirmLoading: true }) |
||||
if (unref(isUpdate)) |
||||
await updateDept(values) |
||||
else |
||||
await createDept(values) |
||||
|
||||
closeModal() |
||||
emit('success') |
||||
createMessage.success(t('common.saveSuccessText')) |
||||
} |
||||
finally { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal |
||||
v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" |
||||
@ok="handleSubmit" |
||||
> |
||||
<BasicForm @register="registerForm" /> |
||||
</BasicModal> |
||||
</template> |
@ -0,0 +1,85 @@
|
||||
import { lazyGetDeptList } from '@/api/system/dept' |
||||
import { getAllTenants } from '@/api/system/tenant' |
||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||
|
||||
export const columns: BasicColumn[] = [ |
||||
{ |
||||
title: '部门名称', |
||||
dataIndex: 'deptName', |
||||
width: 260, |
||||
align: 'left', |
||||
}, |
||||
{ |
||||
title: '所属租户', |
||||
dataIndex: 'tenantName', |
||||
width: 120, |
||||
}, |
||||
] |
||||
|
||||
export const searchFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '部门名称', |
||||
field: 'deptName', |
||||
component: 'Input', |
||||
colProps: { span: 6 }, |
||||
}, |
||||
{ |
||||
label: '所属租户', |
||||
field: 'tenantId', |
||||
component: 'ApiSelect', |
||||
componentProps: { |
||||
api: getAllTenants, |
||||
valueField: 'tenantId', |
||||
labelField: 'tenantName', |
||||
}, |
||||
colProps: { span: 6 }, |
||||
}, |
||||
] |
||||
|
||||
export const formSchema: FormSchema[] = [ |
||||
{ |
||||
field: 'id', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '上级部门', |
||||
field: 'parentId', |
||||
component: 'ApiTreeSelect', |
||||
componentProps: { |
||||
async api() { |
||||
try { |
||||
const res = await lazyGetDeptList() |
||||
return res.map(item => ({ ...item, isLeaf: !item.hasChildren })) |
||||
} |
||||
catch { |
||||
return [] |
||||
} |
||||
}, |
||||
async loadData(treeNode) { |
||||
try { |
||||
const res = await lazyGetDeptList({ parentId: treeNode.id }) |
||||
return res.map(item => ({ ...item, isLeaf: !item.hasChildren })) |
||||
} |
||||
catch { |
||||
return [] |
||||
} |
||||
}, |
||||
valueField: 'id', |
||||
labelField: 'deptName', |
||||
}, |
||||
}, |
||||
{ |
||||
label: '部门名称', |
||||
field: 'deptName', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '显示顺序', |
||||
field: 'sort', |
||||
required: true, |
||||
defaultValue: 0, |
||||
component: 'InputNumber', |
||||
}, |
||||
] |
@ -1,146 +0,0 @@
|
||||
import { listSimpleDept } from '@/api/system/dept' |
||||
import { getListSimpleUsers } from '@/api/system/user' |
||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||
import { useRender } from '@/components/Table' |
||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict' |
||||
|
||||
let userOptions: any[] = [] |
||||
|
||||
async function getUserList() { |
||||
const res = await getListSimpleUsers() |
||||
userOptions = res |
||||
} |
||||
|
||||
await getUserList() |
||||
|
||||
export const columns: BasicColumn[] = [ |
||||
{ |
||||
title: '部门名称', |
||||
dataIndex: 'name', |
||||
width: 260, |
||||
align: 'left', |
||||
}, |
||||
{ |
||||
title: '负责人', |
||||
dataIndex: 'leaderUserId', |
||||
width: 120, |
||||
customRender: ({ text }) => { |
||||
if (!text) |
||||
return '未设置' |
||||
|
||||
for (const user of userOptions) { |
||||
if (text === user.id) |
||||
return user.nickname |
||||
} |
||||
return `未知【${text}】` |
||||
}, |
||||
}, |
||||
{ |
||||
title: '排序', |
||||
dataIndex: 'sort', |
||||
width: 60, |
||||
}, |
||||
{ |
||||
title: '状态', |
||||
dataIndex: 'status', |
||||
width: 180, |
||||
customRender: ({ text }) => { |
||||
return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS) |
||||
}, |
||||
}, |
||||
{ |
||||
title: '创建时间', |
||||
dataIndex: 'createTime', |
||||
width: 180, |
||||
customRender: ({ text }) => { |
||||
return useRender.renderDate(text) |
||||
}, |
||||
}, |
||||
] |
||||
|
||||
export const searchFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '部门名称', |
||||
field: 'name', |
||||
component: 'Input', |
||||
colProps: { span: 8 }, |
||||
}, |
||||
{ |
||||
label: '状态', |
||||
field: 'status', |
||||
component: 'Select', |
||||
componentProps: { |
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
||||
}, |
||||
colProps: { span: 8 }, |
||||
}, |
||||
] |
||||
export const formSchema: FormSchema[] = [ |
||||
{ |
||||
label: '编号', |
||||
field: 'id', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '上级部门', |
||||
field: 'parentId', |
||||
required: true, |
||||
component: 'ApiTreeSelect', |
||||
componentProps: { |
||||
api: () => listSimpleDept(), |
||||
parentLabel: '主类目', |
||||
handleTree: 'id', |
||||
}, |
||||
}, |
||||
{ |
||||
label: '部门名称', |
||||
field: 'name', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '显示顺序', |
||||
field: 'sort', |
||||
required: true, |
||||
defaultValue: 0, |
||||
component: 'InputNumber', |
||||
}, |
||||
{ |
||||
label: '负责人', |
||||
field: 'leaderUserId', |
||||
component: 'ApiSelect', |
||||
componentProps: { |
||||
api: () => getListSimpleUsers(), |
||||
labelField: 'nickname', |
||||
valueField: 'id', |
||||
}, |
||||
}, |
||||
{ |
||||
label: '联系电话', |
||||
field: 'phone', |
||||
required: true, |
||||
rules: [ |
||||
{ |
||||
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', |
||||
}, |
||||
], |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '邮箱', |
||||
field: 'email', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '部门状态', |
||||
field: 'status', |
||||
component: 'Select', |
||||
componentProps: { |
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
||||
}, |
||||
}, |
||||
] |
@ -0,0 +1,55 @@
|
||||
<script lang="ts" setup> |
||||
import { ref } from 'vue' |
||||
import { formSchema } from './data' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import { createMenu, updateMenu } from '@/api/system/menu' |
||||
import type { MenuItem } from '@/api/system/menu/types' |
||||
|
||||
defineOptions({ name: 'SystemMenuFormModal' }) |
||||
|
||||
const emit = defineEmits(['success', 'register']) |
||||
|
||||
const [registerForm, { setFieldsValue, validate }] = useForm({ |
||||
labelWidth: 140, |
||||
baseColProps: { span: 24 }, |
||||
schemas: formSchema, |
||||
showActionButtonGroup: false, |
||||
}) |
||||
|
||||
const isUpdate = ref(true) |
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner((data: MenuItem) => { |
||||
isUpdate.value = true |
||||
setFieldsValue({ |
||||
...data, |
||||
parentId: data.parentId === '0' ? undefined : data.parentId, |
||||
}) |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate<MenuItem>() |
||||
setModalProps({ confirmLoading: true }) |
||||
await (isUpdate.value ? updateMenu(values) : createMenu(values)) |
||||
emit('success') |
||||
closeModal() |
||||
useMessage().createMessage.success('保存成功') |
||||
} |
||||
finally { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal |
||||
v-bind="$attrs" |
||||
:title="isUpdate ? '编辑' : '新建'" |
||||
@register="registerModal" |
||||
@ok="handleSubmit" |
||||
@cancel="isUpdate = false" |
||||
> |
||||
<BasicForm @register="registerForm" @submit="handleSubmit" /> |
||||
</BasicModal> |
||||
</template> |
@ -1,58 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { ref, unref } from 'vue' |
||||
import { formSchema } from './menu.data' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import { createMenu, getMenu, updateMenu } from '@/api/system/menu' |
||||
|
||||
defineOptions({ name: 'SystemMenuModal' }) |
||||
|
||||
const emit = defineEmits(['success', 'register']) |
||||
const { t } = useI18n() |
||||
const { createMessage } = useMessage() |
||||
const isUpdate = ref(true) |
||||
|
||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
||||
labelWidth: 120, |
||||
baseColProps: { span: 24 }, |
||||
schemas: formSchema, |
||||
showActionButtonGroup: false, |
||||
actionColOptions: { span: 23 }, |
||||
}) |
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
||||
resetFields() |
||||
setModalProps({ confirmLoading: false }) |
||||
isUpdate.value = !!data?.isUpdate |
||||
if (unref(isUpdate)) { |
||||
const res = await getMenu(data.record.id) |
||||
setFieldsValue({ ...res }) |
||||
} |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate() as any |
||||
setModalProps({ confirmLoading: true }) |
||||
if (unref(isUpdate)) |
||||
await updateMenu(values) |
||||
else |
||||
await createMenu(values) |
||||
|
||||
closeModal() |
||||
emit('success') |
||||
createMessage.success(t('common.saveSuccessText')) |
||||
} |
||||
finally { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit"> |
||||
<BasicForm @register="registerForm" /> |
||||
</BasicModal> |
||||
</template> |
@ -0,0 +1,56 @@
|
||||
<script lang="ts" setup> |
||||
import { ref } from 'vue' |
||||
import { formSchema } from './data' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import { createRole, updateRole } from '@/api/system/role' |
||||
import type { Role } from '@/api/system/role/types' |
||||
|
||||
defineOptions({ name: 'SystemRoleFormModal' }) |
||||
|
||||
const emit = defineEmits(['success', 'register']) |
||||
|
||||
const [registerForm, { setFieldsValue, validate }] = useForm({ |
||||
labelWidth: 120, |
||||
baseColProps: { span: 24 }, |
||||
schemas: formSchema, |
||||
showActionButtonGroup: false, |
||||
}) |
||||
|
||||
const isUpdate = ref(false) |
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: Role) => { |
||||
isUpdate.value = true |
||||
setFieldsValue({ |
||||
...data, |
||||
parentId: data.parentId === '0' ? undefined : data.parentId, |
||||
}) |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate<Role>() |
||||
values.parentId = values.parentId ? values.parentId : '0' // set default value |
||||
setModalProps({ confirmLoading: true }) |
||||
await (isUpdate.value ? updateRole(values) : createRole(values)) |
||||
closeModal() |
||||
emit('success') |
||||
useMessage().createMessage.success('保存成功') |
||||
} |
||||
finally { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal |
||||
v-bind="$attrs" |
||||
:title="isUpdate ? '编辑' : '新增'" |
||||
@register="registerModal" |
||||
@ok="handleSubmit" |
||||
@cancel="isUpdate = false" |
||||
> |
||||
<BasicForm @register="registerForm" /> |
||||
</BasicModal> |
||||
</template> |
@ -1,122 +1,54 @@
|
||||
<script lang="ts" setup> |
||||
import { ref, unref } from 'vue' |
||||
import { without } from 'lodash-es' |
||||
import { menuScopeFormSchema } from './role.data' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { ref } from 'vue' |
||||
import { useAsyncState } from '@vueuse/core' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import { getRole } from '@/api/system/role' |
||||
import type { CheckKeys, CheckedEvent, TreeItem } from '@/components/Tree' |
||||
import { BasicTree } from '@/components/Tree' |
||||
import { listSimpleMenus } from '@/api/system/menu' |
||||
import { handleTree } from '@/utils/tree' |
||||
import { assignRoleMenu, listRoleMenus } from '@/api/system/permission' |
||||
|
||||
defineOptions({ name: 'SystemRoleMenuModal' }) |
||||
import { assignMenuToRole, getMenuIdsByRole, getMenuTree } from '@/api/system/role' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
|
||||
defineOptions({ name: 'RoleMenuModal' }) |
||||
const emit = defineEmits(['success', 'register']) |
||||
const { t } = useI18n() |
||||
const { createMessage } = useMessage() |
||||
const treeData = ref<TreeItem[]>([]) |
||||
const menuKeys = ref<number[]>([]) |
||||
const menuHalfKeys = ref<number[]>([]) |
||||
|
||||
// 默认展开的层级 |
||||
const defaultExpandLevel = ref<number>(1) |
||||
// 祖先节点list |
||||
const parentIdSets = ref<Set<number>>(new Set()) |
||||
const treeRef = ref() |
||||
const checkedIds = ref<string[]>([]) |
||||
const { state, execute } = useAsyncState(getMenuTree, [], { immediate: false }) |
||||
|
||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
||||
labelWidth: 120, |
||||
baseColProps: { span: 24 }, |
||||
schemas: menuScopeFormSchema, |
||||
showActionButtonGroup: false, |
||||
actionColOptions: { span: 23 }, |
||||
}) |
||||
let roleId: string |
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (id: string) => { |
||||
try { |
||||
if (!state.value.length) |
||||
await execute() |
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
||||
await resetFields() |
||||
menuReset() |
||||
setModalProps({ confirmLoading: false }) |
||||
if (unref(treeData).length === 0) { |
||||
const res = await listSimpleMenus() |
||||
treeData.value = handleTree(res, 'id') |
||||
// 去重 拿到所有的父节点 |
||||
parentIdSets.value = new Set(res.map(item => item.parentId)) |
||||
checkedIds.value = await getMenuIdsByRole(roleId = id) |
||||
} |
||||
const role = await getRole(data.record.id) |
||||
const menuIds = await listRoleMenus(data.record.id) |
||||
|
||||
// https://www.lodashjs.com/docs/lodash.without |
||||
// 默认关联节点 需要排除所有的祖先节点 否则会全部勾选 |
||||
// 只保留子节点 关联情况下会自己选中父节点 |
||||
// 排除祖先节点后的子节点 达到"独立"的效果 但可以进行关联选择 |
||||
const excludeParentIds = without(menuIds, ...Array.from(parentIdSets.value)) |
||||
// 这里是后期更新/新增需要用的 需要使用原始参数 |
||||
menuKeys.value = menuIds |
||||
// 这里是view需要的 需要排除祖先节点才能正常显示 否则传入父节点会勾选所有子节点 |
||||
role.menuIds = excludeParentIds |
||||
// 这里只负责显示 后期传递参数不使用这里 所以不用祖先节点 |
||||
await setFieldsValue({ ...role }) |
||||
|
||||
// 默认展开的层级 |
||||
if (unref(treeRef)) |
||||
unref(treeRef).filterByLevel(defaultExpandLevel.value) |
||||
catch {} |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate() |
||||
setModalProps({ confirmLoading: true }) |
||||
await assignRoleMenu({ |
||||
roleId: values.id, |
||||
menuIds: [...menuKeys.value, ...menuHalfKeys.value], |
||||
}) |
||||
function handleSubmit() { |
||||
setModalProps({ confirmLoading: true }) |
||||
assignMenuToRole({ |
||||
roleId, |
||||
menuIds: checkedIds.value, |
||||
}).then(() => { |
||||
closeModal() |
||||
emit('success') |
||||
createMessage.success(t('common.saveSuccessText')) |
||||
} |
||||
finally { |
||||
checkedIds.value = [] |
||||
useMessage().createMessage.success('操作成功') |
||||
}).finally(() => { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
|
||||
function menuReset() { |
||||
menuKeys.value = [] |
||||
menuHalfKeys.value = [] |
||||
} |
||||
|
||||
function menuCheck(checkedKeys: CheckKeys, event: CheckedEvent) { |
||||
if (Array.isArray(checkedKeys)) { |
||||
// 这里是子节点的ID |
||||
menuKeys.value = checkedKeys as number[] |
||||
// 这里是父节点的ID 默认空数组 |
||||
menuHalfKeys.value = (event.halfCheckedKeys as number[]) || [] |
||||
} |
||||
}) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal v-bind="$attrs" title="修改角色菜单权限" @register="registerModal" @ok="handleSubmit"> |
||||
<BasicForm @register="registerForm"> |
||||
<template #menuIds="{ model, field }"> |
||||
<BasicTree |
||||
v-if="treeData.length" |
||||
ref="treeRef" |
||||
v-model:checkedKeys="model[field]" |
||||
:tree-data="treeData" |
||||
:field-names="{ title: 'name', key: 'id' }" |
||||
toolbar |
||||
checkable |
||||
search |
||||
:show-strictly-button="false" |
||||
:selectable="false" |
||||
title="菜单分配" |
||||
@check="menuCheck" |
||||
/> |
||||
</template> |
||||
</BasicForm> |
||||
<BasicModal v-bind="$attrs" title="菜单权限配置" width="20%" @register="registerModal" @ok="handleSubmit"> |
||||
<BasicTree |
||||
v-if="state.length" |
||||
v-model:value="checkedIds" |
||||
checkable |
||||
default-expand-all |
||||
:tree-data="state" |
||||
:selectable="false" |
||||
:field-names="{ key: 'id' }" |
||||
/> |
||||
</BasicModal> |
||||
</template> |
||||
|
@ -1,58 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { ref, unref } from 'vue' |
||||
import { formSchema } from './role.data' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import { createRole, getRole, updateRole } from '@/api/system/role' |
||||
|
||||
defineOptions({ name: 'SystemRoleModal' }) |
||||
|
||||
const emit = defineEmits(['success', 'register']) |
||||
const { t } = useI18n() |
||||
const { createMessage } = useMessage() |
||||
const isUpdate = ref(true) |
||||
|
||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
||||
labelWidth: 120, |
||||
baseColProps: { span: 24 }, |
||||
schemas: formSchema, |
||||
showActionButtonGroup: false, |
||||
actionColOptions: { span: 23 }, |
||||
}) |
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
||||
resetFields() |
||||
setModalProps({ confirmLoading: false }) |
||||
isUpdate.value = !!data?.isUpdate |
||||
if (unref(isUpdate)) { |
||||
const res = await getRole(data.record.id) |
||||
setFieldsValue({ ...res }) |
||||
} |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate() as any |
||||
setModalProps({ confirmLoading: true }) |
||||
if (unref(isUpdate)) |
||||
await updateRole(values) |
||||
else |
||||
await createRole(values) |
||||
|
||||
closeModal() |
||||
emit('success') |
||||
createMessage.success(t('common.saveSuccessText')) |
||||
} |
||||
finally { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit"> |
||||
<BasicForm @register="registerForm" /> |
||||
</BasicModal> |
||||
</template> |
@ -1,73 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { ref, unref } from 'vue' |
||||
import { dataScopeFormSchema } from './role.data' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import type { TreeItem } from '@/components/Tree' |
||||
import { BasicTree } from '@/components/Tree' |
||||
import { getRole } from '@/api/system/role' |
||||
import { listSimpleDept } from '@/api/system/dept' |
||||
import { handleTree } from '@/utils/tree' |
||||
import { assignRoleDataScope } from '@/api/system/permission' |
||||
|
||||
defineOptions({ name: 'SystemRoleScopeModal' }) |
||||
|
||||
const emit = defineEmits(['success', 'register']) |
||||
const { t } = useI18n() |
||||
const { createMessage } = useMessage() |
||||
const treeData = ref<TreeItem[]>([]) |
||||
|
||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
||||
labelWidth: 120, |
||||
baseColProps: { span: 24 }, |
||||
schemas: dataScopeFormSchema, |
||||
showActionButtonGroup: false, |
||||
actionColOptions: { span: 23 }, |
||||
}) |
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
||||
resetFields() |
||||
setModalProps({ confirmLoading: false }) |
||||
if (unref(treeData).length === 0) { |
||||
const res = await listSimpleDept() |
||||
treeData.value = handleTree(res, 'id') |
||||
} |
||||
const res = await getRole(data.record.id) |
||||
res.roleId = data.record.id |
||||
setFieldsValue({ ...res }) |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate() as any |
||||
setModalProps({ confirmLoading: true }) |
||||
await assignRoleDataScope(values) |
||||
closeModal() |
||||
emit('success') |
||||
createMessage.success(t('common.saveSuccessText')) |
||||
} |
||||
finally { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal v-bind="$attrs" title="编辑角色数据权限" @register="registerModal" @ok="handleSubmit"> |
||||
<BasicForm @register="registerForm"> |
||||
<template #dataScopeDeptIds="{ model, field }"> |
||||
<BasicTree |
||||
v-model:value="model[field]" |
||||
:tree-data="treeData" |
||||
:field-names="{ title: 'name', key: 'id' }" |
||||
:check-strictly="false" |
||||
checkable |
||||
toolbar |
||||
title="部门分配" |
||||
/> |
||||
</template> |
||||
</BasicForm> |
||||
</BasicModal> |
||||
</template> |
@ -0,0 +1,116 @@
|
||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||
import { getAllTenants } from '@/api/system/tenant' |
||||
import { getRoleTree } from '@/api/system/role' |
||||
import { getTenantId } from '@/utils/auth' |
||||
|
||||
export const columns: BasicColumn[] = [ |
||||
{ |
||||
title: '角色名称', |
||||
dataIndex: 'roleName', |
||||
width: 150, |
||||
}, |
||||
{ |
||||
title: '所属租户', |
||||
dataIndex: 'tenantName', |
||||
width: 150, |
||||
}, |
||||
{ |
||||
title: '角色别名', |
||||
dataIndex: 'roleAlias', |
||||
width: 150, |
||||
}, |
||||
{ |
||||
title: '角色排序', |
||||
dataIndex: 'sort', |
||||
width: 120, |
||||
}, |
||||
] |
||||
|
||||
export const searchFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '角色名称', |
||||
field: 'roleName', |
||||
component: 'Input', |
||||
colProps: { span: 7 }, |
||||
}, |
||||
{ |
||||
label: '所属租户', |
||||
field: 'tenantId', |
||||
component: 'ApiSelect', |
||||
componentProps: { |
||||
api: getAllTenants, |
||||
valueField: 'tenantId', |
||||
labelField: 'tenantName', |
||||
}, |
||||
colProps: { span: 7 }, |
||||
}, |
||||
{ |
||||
label: '角色别名', |
||||
field: 'roleAlias', |
||||
component: 'Input', |
||||
colProps: { span: 7 }, |
||||
}, |
||||
] |
||||
|
||||
export const formSchema: FormSchema[] = [ |
||||
{ |
||||
field: 'id', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色名称', |
||||
field: 'roleName', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色别名', |
||||
field: 'roleAlias', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色排序', |
||||
field: 'sort', |
||||
required: true, |
||||
defaultValue: 0, |
||||
component: 'InputNumber', |
||||
}, |
||||
{ |
||||
label: '上级角色', |
||||
field: 'parentId', |
||||
component: 'ApiTreeSelect', |
||||
componentProps: { |
||||
api: () => getRoleTree({ tenantId: getTenantId() }), |
||||
valueField: 'id', |
||||
labelField: 'title', |
||||
}, |
||||
}, |
||||
] |
||||
|
||||
export const menuScopeFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '角色编号', |
||||
field: 'id', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色名称', |
||||
field: 'name', |
||||
dynamicDisabled: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色标识', |
||||
field: 'code', |
||||
dynamicDisabled: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '菜单权限', |
||||
field: 'menuIds', |
||||
slot: 'menuIds', |
||||
}, |
||||
] |
@ -1,184 +0,0 @@
|
||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||
import { useRender } from '@/components/Table' |
||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict' |
||||
import { SystemDataScopeEnum } from '@/enums/systemEnum' |
||||
|
||||
export const columns: BasicColumn[] = [ |
||||
{ |
||||
title: '角色编号', |
||||
dataIndex: 'id', |
||||
width: 120, |
||||
}, |
||||
{ |
||||
title: '角色名称', |
||||
dataIndex: 'name', |
||||
width: 150, |
||||
}, |
||||
{ |
||||
title: '角色标识', |
||||
dataIndex: 'code', |
||||
width: 150, |
||||
}, |
||||
{ |
||||
title: '角色类型', |
||||
dataIndex: 'type', |
||||
width: 150, |
||||
customRender: ({ text }) => { |
||||
return useRender.renderDict(text, DICT_TYPE.SYSTEM_ROLE_TYPE) |
||||
}, |
||||
}, |
||||
{ |
||||
title: '显示顺序', |
||||
dataIndex: 'sort', |
||||
width: 120, |
||||
}, |
||||
{ |
||||
title: '状态', |
||||
dataIndex: 'status', |
||||
width: 180, |
||||
customRender: ({ text }) => { |
||||
return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS) |
||||
}, |
||||
}, |
||||
{ |
||||
title: '创建时间', |
||||
dataIndex: 'createTime', |
||||
width: 180, |
||||
customRender: ({ text }) => { |
||||
return useRender.renderDate(text) |
||||
}, |
||||
}, |
||||
] |
||||
|
||||
export const searchFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '角色名称', |
||||
field: 'name', |
||||
component: 'Input', |
||||
colProps: { span: 8 }, |
||||
}, |
||||
{ |
||||
label: '角色标识', |
||||
field: 'code', |
||||
component: 'Input', |
||||
colProps: { span: 8 }, |
||||
}, |
||||
{ |
||||
label: '状态', |
||||
field: 'status', |
||||
component: 'Select', |
||||
componentProps: { |
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
||||
}, |
||||
colProps: { span: 8 }, |
||||
}, |
||||
{ |
||||
label: '创建时间', |
||||
field: 'createTime', |
||||
component: 'RangePicker', |
||||
colProps: { span: 8 }, |
||||
}, |
||||
] |
||||
|
||||
export const formSchema: FormSchema[] = [ |
||||
{ |
||||
label: '编号', |
||||
field: 'id', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色名称', |
||||
field: 'name', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色标识', |
||||
field: 'code', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色顺序', |
||||
field: 'sort', |
||||
required: true, |
||||
defaultValue: 0, |
||||
component: 'InputNumber', |
||||
}, |
||||
{ |
||||
label: '状态', |
||||
field: 'status', |
||||
component: 'Select', |
||||
componentProps: { |
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
||||
}, |
||||
}, |
||||
{ |
||||
label: '备注', |
||||
field: 'remark', |
||||
component: 'InputTextArea', |
||||
}, |
||||
] |
||||
|
||||
export const menuScopeFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '角色编号', |
||||
field: 'id', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色名称', |
||||
field: 'name', |
||||
dynamicDisabled: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色标识', |
||||
field: 'code', |
||||
dynamicDisabled: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '菜单权限', |
||||
field: 'menuIds', |
||||
slot: 'menuIds', |
||||
}, |
||||
] |
||||
|
||||
export const dataScopeFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '角色编号', |
||||
field: 'roleId', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色名称', |
||||
field: 'name', |
||||
dynamicDisabled: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '角色标识', |
||||
field: 'code', |
||||
dynamicDisabled: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '权限范围', |
||||
field: 'dataScope', |
||||
required: true, |
||||
component: 'Select', |
||||
componentProps: { |
||||
options: getDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE), |
||||
}, |
||||
}, |
||||
{ |
||||
label: '数据权限', |
||||
field: 'dataScopeDeptIds', |
||||
ifShow: ({ values }) => values.dataScope === SystemDataScopeEnum.DEPT_CUSTOM, |
||||
slot: 'dataScopeDeptIds', |
||||
}, |
||||
] |
@ -0,0 +1,52 @@
|
||||
<script lang="ts" setup> |
||||
import { ref } from 'vue' |
||||
import { formSchema } from './data' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import { createTenant, updateTenant } from '@/api/system/tenant' |
||||
import type { Tenant } from '@/api/system/tenant/types' |
||||
|
||||
defineOptions({ name: 'TenantFormModal' }) |
||||
|
||||
const emit = defineEmits(['success', 'register']) |
||||
|
||||
const [registerForm, { setFieldsValue, validate }] = useForm({ |
||||
labelWidth: 120, |
||||
baseColProps: { span: 24 }, |
||||
schemas: formSchema, |
||||
showActionButtonGroup: false, |
||||
}) |
||||
|
||||
const isUpdate = ref(false) |
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
||||
isUpdate.value = true |
||||
setFieldsValue(data) |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate<Tenant>() |
||||
setModalProps({ confirmLoading: true }) |
||||
await (isUpdate.value ? updateTenant(values) : createTenant(values)) |
||||
closeModal() |
||||
emit('success') |
||||
useMessage().createMessage.success('保存成功') |
||||
} |
||||
finally { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal |
||||
v-bind="$attrs" |
||||
:title="isUpdate ? '编辑' : '新增'" |
||||
@register="registerModal" |
||||
@ok="handleSubmit" |
||||
@cancel="isUpdate = false" |
||||
> |
||||
<BasicForm @register="registerForm" /> |
||||
</BasicModal> |
||||
</template> |
@ -1,58 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { ref, unref } from 'vue' |
||||
import { formSchema } from './tenant.data' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import { createTenant, getTenant, updateTenant } from '@/api/system/tenant' |
||||
|
||||
defineOptions({ name: 'SystemTenantModal' }) |
||||
|
||||
const emit = defineEmits(['success', 'register']) |
||||
const { t } = useI18n() |
||||
const { createMessage } = useMessage() |
||||
const isUpdate = ref(true) |
||||
|
||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
||||
labelWidth: 120, |
||||
baseColProps: { span: 24 }, |
||||
schemas: formSchema, |
||||
showActionButtonGroup: false, |
||||
actionColOptions: { span: 23 }, |
||||
}) |
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
||||
resetFields() |
||||
setModalProps({ confirmLoading: false }) |
||||
isUpdate.value = !!data?.isUpdate |
||||
if (unref(isUpdate)) { |
||||
const res = await getTenant(data.record.id) |
||||
setFieldsValue({ ...res }) |
||||
} |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate() as any |
||||
setModalProps({ confirmLoading: true }) |
||||
if (unref(isUpdate)) |
||||
await updateTenant(values) |
||||
else |
||||
await createTenant(values) |
||||
|
||||
closeModal() |
||||
emit('success') |
||||
createMessage.success(t('common.saveSuccessText')) |
||||
} |
||||
finally { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit"> |
||||
<BasicForm @register="registerForm" /> |
||||
</BasicModal> |
||||
</template> |
@ -0,0 +1,106 @@
|
||||
import { h } from 'vue' |
||||
import { Tag } from 'ant-design-vue' |
||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||
|
||||
export const columns: BasicColumn[] = [ |
||||
{ |
||||
title: '租户 ID', |
||||
dataIndex: 'tenantId', |
||||
width: 150, |
||||
}, |
||||
{ |
||||
title: '租户名称', |
||||
dataIndex: 'tenantName', |
||||
width: 150, |
||||
}, |
||||
{ |
||||
title: '登录账号', |
||||
dataIndex: 'adminAccount', |
||||
width: 150, |
||||
}, |
||||
{ |
||||
title: '联系人名称', |
||||
dataIndex: 'contactName', |
||||
width: 150, |
||||
}, |
||||
{ |
||||
title: '联系人手机', |
||||
dataIndex: 'contactMobile', |
||||
width: 150, |
||||
}, |
||||
{ |
||||
title: '过期时间', |
||||
dataIndex: 'expireTime', |
||||
width: 150, |
||||
customRender({ value }) { |
||||
return h(Tag, { color: value ? 'default' : 'blue' }, () => value || '无限制') |
||||
}, |
||||
}, |
||||
] |
||||
|
||||
export const searchFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '租户 ID', |
||||
field: 'tenantId', |
||||
component: 'Input', |
||||
colProps: { span: 5 }, |
||||
}, |
||||
{ |
||||
label: '租户名称', |
||||
field: 'tenantName', |
||||
component: 'Input', |
||||
colProps: { span: 5 }, |
||||
}, |
||||
{ |
||||
label: '联系人名称', |
||||
field: 'contactName', |
||||
component: 'Input', |
||||
colProps: { span: 5 }, |
||||
}, |
||||
{ |
||||
label: '联系人手机', |
||||
field: 'contactMobile', |
||||
component: 'Input', |
||||
colProps: { span: 5 }, |
||||
}, |
||||
] |
||||
|
||||
export const formSchema: FormSchema[] = [ |
||||
{ |
||||
field: 'id', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '租户名', |
||||
field: 'tenantName', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '登录账号', |
||||
field: 'adminAccount', |
||||
required: true, |
||||
component: 'Input', |
||||
rules: [ |
||||
{ min: 6, max: 30, message: '登陆账号长度为6-30' }, |
||||
], |
||||
// cannot edit
|
||||
show: ({ values }) => !values.id, |
||||
}, |
||||
{ |
||||
label: '联系人名称', |
||||
field: 'contactName', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '联系人手机', |
||||
field: 'contactMobile', |
||||
component: 'InputNumber', |
||||
componentProps: { |
||||
controls: false, |
||||
precision: 0, |
||||
}, |
||||
}, |
||||
] |
@ -1,191 +0,0 @@
|
||||
import { getTenantPackageList } from '@/api/system/tenantPackage' |
||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||
import { useRender } from '@/components/Table' |
||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict' |
||||
|
||||
export const columns: BasicColumn[] = [ |
||||
{ |
||||
title: '租户编号', |
||||
dataIndex: 'id', |
||||
width: 100, |
||||
}, |
||||
{ |
||||
title: '租户名', |
||||
dataIndex: 'name', |
||||
width: 180, |
||||
}, |
||||
{ |
||||
title: '租户套餐', |
||||
dataIndex: 'packageId', |
||||
width: 100, |
||||
}, |
||||
{ |
||||
title: '联系人', |
||||
dataIndex: 'contactName', |
||||
width: 120, |
||||
}, |
||||
{ |
||||
title: '联系手机', |
||||
dataIndex: 'contactMobile', |
||||
width: 120, |
||||
}, |
||||
{ |
||||
title: '账号额度', |
||||
dataIndex: 'accountCount', |
||||
width: 120, |
||||
customRender: ({ text }) => { |
||||
return useRender.renderTag(text) |
||||
}, |
||||
}, |
||||
{ |
||||
title: '过期时间', |
||||
dataIndex: 'expireTime', |
||||
width: 180, |
||||
customRender: ({ text }) => { |
||||
return useRender.renderDate(text) |
||||
}, |
||||
}, |
||||
{ |
||||
title: '绑定域名', |
||||
dataIndex: 'website', |
||||
width: 200, |
||||
}, |
||||
{ |
||||
title: '租户状态', |
||||
dataIndex: 'status', |
||||
width: 180, |
||||
customRender: ({ text }) => { |
||||
return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS) |
||||
}, |
||||
}, |
||||
{ |
||||
title: '备注', |
||||
dataIndex: 'remark', |
||||
width: 180, |
||||
}, |
||||
{ |
||||
title: '创建时间', |
||||
dataIndex: 'createTime', |
||||
width: 180, |
||||
customRender: ({ text }) => { |
||||
return useRender.renderDate(text) |
||||
}, |
||||
}, |
||||
] |
||||
|
||||
export const searchFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '租户名', |
||||
field: 'name', |
||||
component: 'Input', |
||||
colProps: { span: 8 }, |
||||
}, |
||||
{ |
||||
label: '联系人', |
||||
field: 'contactName', |
||||
component: 'Input', |
||||
colProps: { span: 8 }, |
||||
}, |
||||
{ |
||||
label: '联系手机', |
||||
field: 'contactMobile', |
||||
component: 'Input', |
||||
colProps: { span: 8 }, |
||||
}, |
||||
{ |
||||
label: '状态', |
||||
field: 'status', |
||||
component: 'Select', |
||||
componentProps: { |
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
||||
}, |
||||
colProps: { span: 8 }, |
||||
}, |
||||
{ |
||||
label: '创建时间', |
||||
field: 'createTime', |
||||
component: 'RangePicker', |
||||
colProps: { span: 8 }, |
||||
}, |
||||
] |
||||
|
||||
export const formSchema: FormSchema[] = [ |
||||
{ |
||||
label: '编号', |
||||
field: 'id', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '租户名', |
||||
field: 'name', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '租户套餐', |
||||
field: 'packageId', |
||||
required: true, |
||||
component: 'ApiSelect', |
||||
componentProps: { |
||||
api: () => getTenantPackageList(), |
||||
labelField: 'name', |
||||
valueField: 'id', |
||||
}, |
||||
}, |
||||
{ |
||||
label: '联系人', |
||||
field: 'contactName', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '联系手机', |
||||
field: 'contactMobile', |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '用户名称', |
||||
field: 'username', |
||||
component: 'Input', |
||||
ifShow: ({ values }) => !values.id, |
||||
}, |
||||
{ |
||||
label: '用户密码', |
||||
field: 'password', |
||||
component: 'InputPassword', |
||||
ifShow: ({ values }) => !values.id, |
||||
}, |
||||
{ |
||||
label: '账号额度', |
||||
field: 'accountCount', |
||||
required: true, |
||||
defaultValue: 0, |
||||
component: 'InputNumber', |
||||
}, |
||||
{ |
||||
label: '过期时间', |
||||
field: 'expireTime', |
||||
required: true, |
||||
component: 'DatePicker', |
||||
componentProps: { |
||||
showTime: true, |
||||
format: 'YYYY-MM-DD HH:mm:ss', |
||||
valueFormat: 'x', |
||||
}, |
||||
}, |
||||
{ |
||||
label: '绑定域名', |
||||
field: 'website', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '租户状态', |
||||
field: 'status', |
||||
component: 'Select', |
||||
componentProps: { |
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
||||
}, |
||||
}, |
||||
] |
@ -1,43 +1,63 @@
|
||||
<script lang="ts" setup> |
||||
import { onMounted, ref } from 'vue' |
||||
|
||||
import type { TreeItem } from '@/components/Tree' |
||||
import type { EventDataNode } from 'ant-design-vue/es/tree' |
||||
import { BasicTree } from '@/components/Tree' |
||||
import { listSimpleDept } from '@/api/system/dept' |
||||
import { handleTree } from '@/utils/tree' |
||||
import { lazyGetDeptList } from '@/api/system/dept' |
||||
import type { Department, LazyGetDeptListParams } from '@/api/system/dept/types' |
||||
|
||||
defineOptions({ name: 'SystemDeptTree' }) |
||||
defineProps<{ dept?: string }>() |
||||
const emit = defineEmits(['update:dept']) |
||||
|
||||
const emit = defineEmits(['select']) |
||||
const treeRef = ref() |
||||
const treeData = ref<TreeItem[]>([]) |
||||
const departmentList = ref<Department[]>([]) |
||||
|
||||
async function fetch() { |
||||
const res = await listSimpleDept() |
||||
treeData.value = handleTree(res, 'id') |
||||
async function requestDeptList(params?: LazyGetDeptListParams) { |
||||
return lazyGetDeptList(params) |
||||
.then((res) => { |
||||
return res.map((item) => { |
||||
return { |
||||
...item, |
||||
isLeaf: !item.hasChildren, |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
function handleSelect(keys) { |
||||
emit('select', keys[0]) |
||||
const basicTreeRef = ref<InstanceType<typeof BasicTree>>() |
||||
async function onLoadDeptList(treeNode: EventDataNode) { |
||||
try { |
||||
return await requestDeptList({ parentId: treeNode.id }) |
||||
} |
||||
catch { |
||||
} |
||||
} |
||||
|
||||
onMounted(() => { |
||||
fetch() |
||||
requestDeptList() |
||||
.then((res) => { |
||||
departmentList.value = res |
||||
}) |
||||
}) |
||||
|
||||
function onSelect(value: string[]) { |
||||
emit('update:dept', value[0] || '') |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="m-4 mr-0 overflow-hidden" v-bind="$attrs"> |
||||
<div class="box-border h-full py-12px pl-12px" v-bind="$attrs"> |
||||
<BasicTree |
||||
ref="treeRef" |
||||
ref="basicTreeRef" |
||||
title="部门列表" |
||||
toolbar |
||||
search |
||||
tree-wrapper-class-name="h-[calc(100%-35px)] overflow-auto" |
||||
:click-row-to-expand="false" |
||||
:tree-data="treeData" |
||||
:field-names="{ key: 'id', title: 'name' }" |
||||
@select="handleSelect" |
||||
/> |
||||
:tree-data="departmentList" |
||||
:field-names="{ key: 'id', title: 'deptName' }" |
||||
:load-data="onLoadDeptList" |
||||
@select="onSelect" |
||||
> |
||||
<template #icon> |
||||
<span class="i-ant-design:deployment-unit-outlined" /> |
||||
</template> |
||||
</BasicTree> |
||||
</div> |
||||
</template> |
||||
|
@ -1,58 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { ref, unref } from 'vue' |
||||
import { formSchema } from './user.data' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import { createUser, getUser, updateUser } from '@/api/system/user' |
||||
|
||||
defineOptions({ name: 'SystemUserModal' }) |
||||
|
||||
const emit = defineEmits(['success', 'register']) |
||||
const { t } = useI18n() |
||||
const { createMessage } = useMessage() |
||||
const isUpdate = ref(true) |
||||
|
||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
||||
labelWidth: 120, |
||||
baseColProps: { span: 24 }, |
||||
schemas: formSchema, |
||||
showActionButtonGroup: false, |
||||
actionColOptions: { span: 23 }, |
||||
}) |
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
||||
resetFields() |
||||
setModalProps({ confirmLoading: false }) |
||||
isUpdate.value = !!data?.isUpdate |
||||
if (unref(isUpdate)) { |
||||
const res = await getUser(data.record.id) |
||||
setFieldsValue({ ...res }) |
||||
} |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate() as any |
||||
setModalProps({ confirmLoading: true }) |
||||
if (unref(isUpdate)) |
||||
await updateUser(values) |
||||
else |
||||
await createUser(values) |
||||
|
||||
closeModal() |
||||
emit('success') |
||||
createMessage.success(t('common.saveSuccessText')) |
||||
} |
||||
finally { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal v-bind="$attrs" :title="isUpdate ? t('action.edit') : t('action.create')" @register="registerModal" @ok="handleSubmit"> |
||||
<BasicForm @register="registerForm" /> |
||||
</BasicModal> |
||||
</template> |
@ -1,52 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { userRoleFormSchema } from './user.data' |
||||
import { useI18n } from '@/hooks/web/useI18n' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { BasicForm, useForm } from '@/components/Form' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
import { getUser } from '@/api/system/user' |
||||
import { assignUserRole, listUserRoles } from '@/api/system/permission' |
||||
|
||||
defineOptions({ name: 'SystemUserRoleModal' }) |
||||
|
||||
const emit = defineEmits(['success', 'register']) |
||||
const { t } = useI18n() |
||||
const { createMessage } = useMessage() |
||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({ |
||||
labelWidth: 120, |
||||
baseColProps: { span: 24 }, |
||||
schemas: userRoleFormSchema, |
||||
showActionButtonGroup: false, |
||||
actionColOptions: { span: 23 }, |
||||
}) |
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
||||
resetFields() |
||||
setModalProps({ confirmLoading: false }) |
||||
|
||||
const res = await getUser(data.record.id) |
||||
const roleIds = await listUserRoles(data.record.id) |
||||
res.roleIds = roleIds |
||||
setFieldsValue({ ...res }) |
||||
}) |
||||
|
||||
async function handleSubmit() { |
||||
try { |
||||
const values = await validate() |
||||
setModalProps({ confirmLoading: true }) |
||||
await assignUserRole({ userId: values.id, roleIds: values.roleIds }) |
||||
closeModal() |
||||
emit('success') |
||||
createMessage.success(t('common.saveSuccessText')) |
||||
} |
||||
finally { |
||||
setModalProps({ confirmLoading: false }) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal v-bind="$attrs" title="用户角色权限" @register="registerModal" @ok="handleSubmit"> |
||||
<BasicForm @register="registerForm" /> |
||||
</BasicModal> |
||||
</template> |
@ -0,0 +1,309 @@
|
||||
import type { Ref } from 'vue' |
||||
import { h } from 'vue' |
||||
import { Avatar } from 'ant-design-vue' |
||||
import { getRoleTree } from '@/api/system/role' |
||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||
import { getAllTenants } from '@/api/system/tenant' |
||||
import { getDeptTree, lazyGetDeptList } from '@/api/system/dept' |
||||
|
||||
export const columns: BasicColumn[] = [ |
||||
{ |
||||
title: '登陆账号', |
||||
dataIndex: 'account', |
||||
width: 180, |
||||
}, |
||||
{ |
||||
title: '所属租户', |
||||
dataIndex: 'tenantName', |
||||
width: 100, |
||||
}, |
||||
{ |
||||
title: '用户姓名', |
||||
dataIndex: 'realName', |
||||
width: 120, |
||||
customRender({ record, value }) { |
||||
return h('div', [ |
||||
h( |
||||
Avatar, |
||||
{ |
||||
src: /^https?/.test(record.avatar) ? record.avatar : undefined, |
||||
size: 'small', |
||||
style: { marginRight: '10px' }, |
||||
}, |
||||
() => value?.slice(0, 1), |
||||
), |
||||
value, |
||||
]) |
||||
}, |
||||
}, |
||||
{ |
||||
title: '手机号码', |
||||
dataIndex: 'mobile', |
||||
width: 120, |
||||
}, |
||||
{ |
||||
title: '用户邮箱', |
||||
dataIndex: 'email', |
||||
width: 120, |
||||
}, |
||||
{ |
||||
title: '所属角色', |
||||
dataIndex: 'roleName', |
||||
width: 120, |
||||
}, |
||||
{ |
||||
title: '所属部门', |
||||
dataIndex: 'deptName', |
||||
width: 120, |
||||
}, |
||||
] |
||||
|
||||
export const searchFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '账号', |
||||
field: 'account', |
||||
component: 'Input', |
||||
colProps: { span: 6 }, |
||||
}, |
||||
{ |
||||
label: '手机号码', |
||||
field: 'mobile', |
||||
component: 'InputNumber', |
||||
componentProps: { |
||||
controls: false, |
||||
precision: 0, |
||||
}, |
||||
colProps: { span: 6 }, |
||||
}, |
||||
{ |
||||
label: '所属部门', |
||||
field: 'tenantId', |
||||
component: 'ApiTreeSelect', |
||||
componentProps: { |
||||
async api() { |
||||
try { |
||||
const res = await lazyGetDeptList() |
||||
return res.map(item => ({ ...item, isLeaf: !item.hasChildren })) |
||||
} |
||||
catch { |
||||
return [] |
||||
} |
||||
}, |
||||
async loadData(treeNode) { |
||||
try { |
||||
const res = await lazyGetDeptList({ parentId: treeNode.id }) |
||||
return res.map(item => ({ ...item, isLeaf: !item.hasChildren })) |
||||
} |
||||
catch { |
||||
return [] |
||||
} |
||||
}, |
||||
valueField: 'id', |
||||
labelField: 'deptName', |
||||
}, |
||||
colProps: { span: 6 }, |
||||
}, |
||||
] |
||||
|
||||
export function getFormSchema(isUpdate: Ref<boolean>): FormSchema[] { |
||||
return [ |
||||
{ |
||||
field: 'id', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
field: 'BaseInfo', |
||||
label: '基本信息', |
||||
component: 'Divider', |
||||
componentProps: { |
||||
plain: false, |
||||
}, |
||||
}, |
||||
{ |
||||
label: '账号', |
||||
field: 'account', |
||||
required: true, |
||||
component: 'Input', |
||||
ifShow: () => !isUpdate.value, |
||||
}, |
||||
{ |
||||
label: '密码', |
||||
field: 'password', |
||||
required: true, |
||||
component: 'InputPassword', |
||||
ifShow: () => !isUpdate.value, |
||||
}, |
||||
{ |
||||
label: '确认密码', |
||||
field: 'confirmPassword', |
||||
required: true, |
||||
component: 'InputPassword', |
||||
ifShow: () => !isUpdate.value, |
||||
dynamicRules: ({ values }) => { |
||||
return [ |
||||
{ |
||||
required: true, |
||||
validator: (_, value) => { |
||||
if (value !== values.password) |
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
return Promise.reject('两次输入的密码不一致!') |
||||
|
||||
return Promise.resolve() |
||||
}, |
||||
}, |
||||
] |
||||
}, |
||||
}, |
||||
{ |
||||
label: '所属租户', |
||||
field: 'tenantId', |
||||
required: true, |
||||
component: 'ApiSelect', |
||||
defaultValue: undefined, |
||||
dynamicDisabled: () => isUpdate.value, |
||||
componentProps({ formActionType, formModel }) { |
||||
return { |
||||
api: getAllTenants, |
||||
valueField: 'tenantId', |
||||
labelField: 'tenantName', |
||||
onChange(value?: string) { |
||||
function updateRole(treeData: { id: string, title: string }[]) { |
||||
if (!isUpdate) |
||||
formModel.roleId && formActionType.setFieldsValue({ roleId: undefined }) |
||||
|
||||
formActionType.updateSchema({ |
||||
field: 'roleId', |
||||
componentProps: { |
||||
treeData, |
||||
}, |
||||
}) |
||||
} |
||||
function updateDept(treeData: { id: string, title: string }[]) { |
||||
if (!isUpdate) |
||||
formModel.deptId && formActionType.setFieldsValue({ deptId: undefined }) |
||||
|
||||
formActionType.updateSchema({ |
||||
field: 'deptId', |
||||
componentProps: { |
||||
treeData, |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
if (!value) { |
||||
updateRole([]) |
||||
updateDept([]) |
||||
return |
||||
} |
||||
|
||||
getRoleTree({ tenantId: value }) |
||||
.then((res) => { |
||||
updateRole(res) |
||||
}) |
||||
|
||||
getDeptTree({ tenantId: value }) |
||||
.then((res) => { |
||||
updateDept(res) |
||||
}) |
||||
}, |
||||
} |
||||
}, |
||||
}, |
||||
{ |
||||
label: '所属部门', |
||||
field: 'deptId', |
||||
required: true, |
||||
component: 'TreeSelect', |
||||
componentProps: { |
||||
treeData: [], |
||||
fieldNames: { |
||||
label: 'title', |
||||
value: 'id', |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
label: '所属角色', |
||||
field: 'roleId', |
||||
required: true, |
||||
component: 'TreeSelect', |
||||
componentProps: { |
||||
treeData: [], |
||||
fieldNames: { |
||||
label: 'title', |
||||
value: 'id', |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
field: 'UserInfo', |
||||
label: '用户信息', |
||||
component: 'Divider', |
||||
componentProps: { |
||||
plain: false, |
||||
}, |
||||
}, |
||||
{ |
||||
label: '用户姓名', |
||||
field: 'realName', |
||||
component: 'Input', |
||||
required: true, |
||||
}, |
||||
{ |
||||
label: '用户头像', |
||||
field: 'avatar', |
||||
component: 'FileUpload', |
||||
componentProps: { |
||||
maxCount: 1, |
||||
fileType: 'image', |
||||
}, |
||||
}, |
||||
{ |
||||
label: '用户邮箱', |
||||
field: 'email', |
||||
component: 'Input', |
||||
rules: [ |
||||
{ type: 'email', message: '邮箱格式不正确' }, |
||||
], |
||||
}, |
||||
{ |
||||
label: '手机号码', |
||||
field: 'mobile', |
||||
component: 'InputNumber', |
||||
componentProps: { |
||||
controls: false, |
||||
precision: 0, |
||||
}, |
||||
rules: [ |
||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确' }, |
||||
], |
||||
}, |
||||
{ |
||||
label: '用户性别', |
||||
field: 'sex', |
||||
component: 'Select', |
||||
componentProps: { |
||||
options: [ |
||||
{ |
||||
label: '男', |
||||
value: 0, |
||||
}, |
||||
{ |
||||
label: '女', |
||||
value: 1, |
||||
}, |
||||
{ |
||||
label: '未知', |
||||
value: 2, |
||||
}, |
||||
], |
||||
}, |
||||
}, |
||||
{ |
||||
label: '备注', |
||||
field: 'remark', |
||||
component: 'InputTextArea', |
||||
}, |
||||
] |
||||
} |
@ -1,283 +0,0 @@
|
||||
/* eslint-disable prefer-promise-reject-errors */ |
||||
import { h } from 'vue' |
||||
import { Switch } from 'ant-design-vue' |
||||
import dayjs from 'dayjs' |
||||
import { useMessage } from '@/hooks/web/useMessage' |
||||
import { listSimpleDept } from '@/api/system/dept' |
||||
import { listSimplePosts } from '@/api/system/post' |
||||
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||
import { useRender } from '@/components/Table' |
||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict' |
||||
import { updateUserStatus } from '@/api/system/user' |
||||
import { listSimpleRoles } from '@/api/system/role' |
||||
|
||||
export const columns: BasicColumn[] = [ |
||||
{ |
||||
title: '用户编号', |
||||
dataIndex: 'id', |
||||
width: 100, |
||||
}, |
||||
{ |
||||
title: '用户名称', |
||||
dataIndex: 'username', |
||||
width: 180, |
||||
}, |
||||
{ |
||||
title: '用户昵称', |
||||
dataIndex: 'nickname', |
||||
width: 100, |
||||
}, |
||||
{ |
||||
title: '部门', |
||||
dataIndex: 'deptId', |
||||
width: 120, |
||||
customRender: ({ record }) => { |
||||
return useRender.renderTag(record.dept && record.dept.name) |
||||
}, |
||||
}, |
||||
{ |
||||
title: '手机号码', |
||||
dataIndex: 'mobile', |
||||
width: 120, |
||||
}, |
||||
{ |
||||
title: '状态', |
||||
dataIndex: 'status', |
||||
width: 180, |
||||
// customRender: ({ text }) => {
|
||||
// return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS)
|
||||
// }
|
||||
customRender: ({ record }) => { |
||||
if (!Reflect.has(record, 'pendingStatus')) |
||||
record.pendingStatus = false |
||||
|
||||
return h(Switch, { |
||||
checked: record.status === 0, |
||||
checkedChildren: '已启用', |
||||
unCheckedChildren: '已禁用', |
||||
loading: record.pendingStatus, |
||||
onChange(checked: boolean) { |
||||
record.pendingStatus = true |
||||
const newStatus = checked ? 0 : 1 |
||||
const { createMessage } = useMessage() |
||||
updateUserStatus(record.id, newStatus) |
||||
.then(() => { |
||||
record.status = newStatus |
||||
createMessage.success('已成功修改用户状态') |
||||
}) |
||||
.catch(() => { |
||||
createMessage.error('修改用户状态失败') |
||||
}) |
||||
.finally(() => { |
||||
record.pendingStatus = false |
||||
}) |
||||
}, |
||||
}) |
||||
}, |
||||
}, |
||||
{ |
||||
title: '创建时间', |
||||
dataIndex: 'createTime', |
||||
width: 180, |
||||
customRender: ({ text }) => { |
||||
return useRender.renderDate(text) |
||||
}, |
||||
}, |
||||
] |
||||
|
||||
export const searchFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '用户名称', |
||||
field: 'username', |
||||
component: 'Input', |
||||
colProps: { span: 8 }, |
||||
}, |
||||
{ |
||||
label: '手机号码', |
||||
field: 'mobile', |
||||
component: 'Input', |
||||
colProps: { span: 8 }, |
||||
}, |
||||
{ |
||||
label: '状态', |
||||
field: 'status', |
||||
component: 'Select', |
||||
componentProps: { |
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
||||
}, |
||||
colProps: { span: 8 }, |
||||
}, |
||||
{ |
||||
label: '创建时间', |
||||
field: 'createTime', |
||||
component: 'RangePicker', |
||||
componentProps: { |
||||
showTime: { |
||||
defaultValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('23:59:59', 'HH:mm:ss')], |
||||
}, |
||||
}, |
||||
colProps: { span: 8 }, |
||||
}, |
||||
] |
||||
|
||||
export const formSchema: FormSchema[] = [ |
||||
{ |
||||
label: '编号', |
||||
field: 'id', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '用户昵称', |
||||
field: 'nickname', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '用户头像', |
||||
field: 'avatar', |
||||
component: 'FileUpload', |
||||
componentProps: { |
||||
maxCount: 1, |
||||
fileType: 'image', |
||||
}, |
||||
}, |
||||
{ |
||||
label: '归属部门', |
||||
field: 'deptId', |
||||
required: true, |
||||
component: 'ApiTreeSelect', |
||||
componentProps: { |
||||
api: () => listSimpleDept(), |
||||
handleTree: 'id', |
||||
}, |
||||
}, |
||||
{ |
||||
label: '手机号码', |
||||
field: 'mobile', |
||||
required: true, |
||||
defaultValue: 0, |
||||
component: 'InputNumber', |
||||
}, |
||||
{ |
||||
label: '邮箱', |
||||
field: 'email', |
||||
required: true, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '用户名称', |
||||
field: 'username', |
||||
component: 'Input', |
||||
dynamicDisabled: ({ values }) => !!values.id, |
||||
}, |
||||
{ |
||||
label: '用户密码', |
||||
field: 'password', |
||||
component: 'InputPassword', |
||||
ifShow: ({ values }) => !values.id, |
||||
}, |
||||
{ |
||||
label: '用户性别', |
||||
field: 'sex', |
||||
component: 'Select', |
||||
componentProps: { |
||||
options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX), |
||||
}, |
||||
}, |
||||
{ |
||||
label: '岗位', |
||||
field: 'postIds', |
||||
component: 'ApiSelect', |
||||
defaultValue: [], |
||||
componentProps: { |
||||
api: () => listSimplePosts(), |
||||
labelField: 'name', |
||||
valueField: 'id', |
||||
mode: 'tags', |
||||
}, |
||||
}, |
||||
{ |
||||
label: '状态', |
||||
field: 'status', |
||||
component: 'Select', |
||||
componentProps: { |
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS), |
||||
}, |
||||
}, |
||||
{ |
||||
label: '备注', |
||||
field: 'remark', |
||||
component: 'InputTextArea', |
||||
}, |
||||
] |
||||
|
||||
export const userRoleFormSchema: FormSchema[] = [ |
||||
{ |
||||
label: '编号', |
||||
field: 'id', |
||||
show: false, |
||||
component: 'Input', |
||||
}, |
||||
{ |
||||
label: '用户名称', |
||||
field: 'username', |
||||
component: 'Input', |
||||
dynamicDisabled: () => true, |
||||
}, |
||||
{ |
||||
label: '用户昵称', |
||||
field: 'nickname', |
||||
component: 'Input', |
||||
dynamicDisabled: () => true, |
||||
}, |
||||
{ |
||||
label: '角色', |
||||
field: 'roleIds', |
||||
component: 'ApiSelect', |
||||
componentProps: { |
||||
api: () => listSimpleRoles(), |
||||
labelField: 'name', |
||||
valueField: 'id', |
||||
mode: 'tags', |
||||
}, |
||||
}, |
||||
] |
||||
|
||||
export const userPwdFormSchema: FormSchema[] = [ |
||||
{ |
||||
field: 'newPassword', |
||||
label: '新密码', |
||||
component: 'StrengthMeter', |
||||
componentProps: { |
||||
placeholder: '新密码', |
||||
}, |
||||
rules: [ |
||||
{ |
||||
required: true, |
||||
message: '请输入新密码', |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
field: 'confirmPassword', |
||||
label: '确认密码', |
||||
component: 'InputPassword', |
||||
dynamicRules: ({ values }) => { |
||||
return [ |
||||
{ |
||||
required: true, |
||||
validator: (_, value) => { |
||||
if (!value) |
||||
return Promise.reject('密码不能为空') |
||||
|
||||
if (value !== values.newPassword) |
||||
return Promise.reject('两次输入的密码不一致!') |
||||
|
||||
return Promise.resolve() |
||||
}, |
||||
}, |
||||
] |
||||
}, |
||||
}, |
||||
] |
Reference in new issue