Compare commits

...
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

3 Commits
main ... dev

  1. 6
      .env
  2. 4
      .env.development
  3. 2
      .env.production
  4. 7
      .vscode/settings.json
  5. 1
      eslint.config.js
  6. 16
      src/App.vue
  7. 6
      src/api/base/login.ts
  8. 7
      src/api/base/model/userModel.ts
  9. 36
      src/api/base/user.ts
  10. 29
      src/api/base/user/index.ts
  11. 32
      src/api/base/user/types.ts
  12. 11
      src/api/system/area/index.ts
  13. 62
      src/api/system/dept/index.ts
  14. 17
      src/api/system/dept/types.ts
  15. 36
      src/api/system/dict/data.ts
  16. 36
      src/api/system/dict/type.ts
  17. 46
      src/api/system/dict/types.ts
  18. 49
      src/api/system/errorCode/index.ts
  19. 30
      src/api/system/loginLog/index.ts
  20. 31
      src/api/system/mail/account.ts
  21. 11
      src/api/system/mail/log.ts
  22. 54
      src/api/system/mail/template.ts
  23. 67
      src/api/system/menu/index.ts
  24. 26
      src/api/system/menu/types.ts
  25. 42
      src/api/system/notice/index.ts
  26. 32
      src/api/system/notify/message.ts
  27. 63
      src/api/system/notify/template.ts
  28. 51
      src/api/system/oauth2/client.ts
  29. 28
      src/api/system/oauth2/token.ts
  30. 41
      src/api/system/operatelog/index.ts
  31. 42
      src/api/system/permission/index.ts
  32. 58
      src/api/system/post/index.ts
  33. 92
      src/api/system/role/index.ts
  34. 29
      src/api/system/role/types.ts
  35. 64
      src/api/system/sensitiveWord/index.ts
  36. 50
      src/api/system/sms/smsChannel/index.ts
  37. 55
      src/api/system/sms/smsLog/index.ts
  38. 94
      src/api/system/sms/smsTemplate/index.ts
  39. 75
      src/api/system/tenant/index.ts
  40. 16
      src/api/system/tenant/types.ts
  41. 49
      src/api/system/tenantPackage/index.ts
  42. 102
      src/api/system/user/index.ts
  43. 23
      src/api/system/user/types.ts
  44. 4
      src/components/DictTag/index.ts
  45. 72
      src/components/DictTag/src/DictTag.vue
  46. 8
      src/components/Form/src/BasicForm.vue
  47. 19
      src/components/Form/src/components/ApiTreeSelect.vue
  48. 22
      src/components/Form/src/hooks/useFormEvents.ts
  49. 5
      src/components/Form/src/hooks/useFormValues.ts
  50. 2
      src/components/Form/src/types/index.ts
  51. 55
      src/components/Icon/src/IconPicker.vue
  52. 2
      src/components/Table/src/BasicTable.vue
  53. 3
      src/components/Table/src/components/TableAction.vue
  54. 1
      src/components/Table/src/hooks/useColumns.ts
  55. 13
      src/components/Table/src/hooks/useRender.ts
  56. 2
      src/components/Table/src/types/table.ts
  57. 2
      src/components/Tree/src/types/tree.ts
  58. 15
      src/enums/appEnum.ts
  59. 3
      src/enums/cacheEnum.ts
  60. 2
      src/enums/httpEnum.ts
  61. 9
      src/enums/systemEnum.ts
  62. 4
      src/hooks/component/useFormItem.ts
  63. 4
      src/hooks/setting/index.ts
  64. 3
      src/hooks/setting/useRootSetting.ts
  65. 42
      src/hooks/web/usePermission.ts
  66. 2
      src/layouts/default/header/components/lock/LockModal.vue
  67. 6
      src/layouts/default/header/components/user-dropdown/index.vue
  68. 23
      src/router/guard/paramMenuGuard.ts
  69. 6
      src/router/guard/permissionGuard.ts
  70. 28
      src/router/helper/routeHelper.ts
  71. 70
      src/router/menus/index.ts
  72. 10
      src/router/routes/index.ts
  73. 2
      src/router/routes/modules/dashboard.ts
  74. 2
      src/router/types.ts
  75. 6
      src/settings/componentSetting.ts
  76. 6
      src/settings/projectSetting.ts
  77. 67
      src/store/modules/dict.ts
  78. 3
      src/store/modules/lock.ts
  79. 104
      src/store/modules/permission.ts
  80. 52
      src/store/modules/user.ts
  81. 24
      src/store/modules/userMessage.ts
  82. 2
      src/types/axios.d.ts
  83. 11
      src/types/config.d.ts
  84. 7
      src/types/global.d.ts
  85. 8
      src/types/index.d.ts
  86. 5
      src/types/store.d.ts
  87. 2
      src/utils/auth/index.ts
  88. 97
      src/utils/dict.ts
  89. 4
      src/utils/env.ts
  90. 14
      src/utils/http/axios/index.ts
  91. 13
      src/utils/index.ts
  92. 2
      src/views/base/lock/LockPage.vue
  93. 4
      src/views/base/login/Login.vue
  94. 107
      src/views/base/login/LoginForm.vue
  95. 2
      src/views/base/login/LoginFormTitle.vue
  96. 155
      src/views/base/login/MobileForm.vue
  97. 37
      src/views/base/login/QrCodeForm.vue
  98. 199
      src/views/base/login/SSOForm.vue
  99. 16
      src/views/base/login/SessionTimeoutLogin.vue
  100. 200
      src/views/base/login/sso.vue
  101. Some files were not shown because too many files have changed in this diff Show More

6
.env

@ -6,9 +6,3 @@ VITE_GLOB_APP_TITLE = Fast Iot Web
# 简称,用于配置文件名字 不要出现空格、数字开头等特殊字符
VITE_GLOB_APP_SHORT_NAME = FAST_IOT_Admin
# 租户开关
VITE_GLOB_APP_TENANT_ENABLE = true
# 验证码的开关
VITE_GLOB_APP_CAPTCHA_ENABLE = true

4
.env.development

@ -7,13 +7,13 @@ VITE_PUBLIC_PATH = /
# 本地开发代理,可以解决跨域及多地址代理
# 如果接口地址匹配到,则会转发到http://localhost:3000,防止本地出现跨域问题
# 可以有多个,注意多个不能换行,否则代理将会失效
VITE_PROXY = [["/dev-api","192.168.1.100:48081/admin-api"],["/upload","192.168.1.100:48081/admin-api/infra/file/upload"]]
VITE_PROXY = []
# 是否删除Console.log
VITE_DROP_CONSOLE = false
# 接口地址,如果没有跨域问题,直接在这里配置即可
VITE_GLOB_API_URL = /dev-api
VITE_GLOB_API_URL = http://192.168.1.100:48081/admin-api
# 文件上传接口 可选
VITE_GLOB_UPLOAD_URL = /upload

2
.env.production

@ -13,7 +13,7 @@ VITE_BUILD_COMPRESS = 'gzip'
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
# 接口地址 可以由nginx做转发或者直接写实际地址
VITE_GLOB_API_URL = http://localhost:48080/admin-api
VITE_GLOB_API_URL = /admin-api
# 文件上传地址 可以由nginx做转发或者直接写实际地址
VITE_GLOB_UPLOAD_URL = /upload

7
.vscode/settings.json vendored

@ -74,5 +74,10 @@
"vuedraggable",
"vueuse"
],
"terminal.integrated.scrollback": 10000
"terminal.integrated.scrollback": 10000,
"i18n-ally.localesPaths": [
"src/locales",
"src/locales/lang",
"public/resource/tinymce/langs"
]
}

1
eslint.config.js

@ -9,6 +9,7 @@ export default antfu(
'vue/custom-event-name-casing': 'off',
'vue/component-name-in-template-casing': 'off',
'vue/require-toggle-inside-transition': 'off',
'ts/no-use-before-define': 'off',
},
},
unocss.configs.flat,

16
src/App.vue

@ -1,10 +1,9 @@
<script lang="ts" setup>
import 'dayjs/locale/zh-cn'
import { App, ConfigProvider } from 'ant-design-vue'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { App, ConfigProvider } from 'ant-design-vue'
import type { TransformCellTextProps } from 'ant-design-vue/lib/table/interface'
import { AppProvider } from '@/components/Application'
import { useTitle } from '@/hooks/web/useTitle'
import { useLocale } from '@/locales/useLocale'
@ -18,10 +17,17 @@ const { themeConfig } = storeToRefs(appStore)
const componentSize = computed(() => appStore.getComponentSize)
// Listening to page changes and dynamically changing site titles
useTitle()
function transformCellText(props: TransformCellTextProps) {
if (!props.text || !props.text.length)
return '-'
return props.text
}
</script>
<template>
<ConfigProvider :locale="getAntdLocale" :theme="themeConfig" :component-size="componentSize">
<ConfigProvider :locale="getAntdLocale" :theme="themeConfig" :component-size="componentSize" :transform-cell-text="transformCellText">
<App class="h-full w-full">
<AppProvider>
<RouterView />

6
src/api/base/login.ts

@ -1,4 +1,3 @@
import type { TentantNameVO } from './model/loginModel'
import { defHttp } from '@/utils/http/axios'
import { getRefreshToken } from '@/utils/auth'
@ -18,11 +17,6 @@ export function refreshToken() {
return defHttp.post({ url: Api.RefreshToken + refreshToken })
}
// 使用租户名,获得租户编号
export function getTenantIdByName(name: string) {
return defHttp.get<TentantNameVO>({ url: Api.GetTenantIdByName + name })
}
// 登出
export function loginOut() {
return defHttp.delete({ url: Api.LoginOut })

7
src/api/base/model/userModel.ts

@ -1,12 +1,9 @@
import type { RouteItem } from './menuModel'
/**
* @description: Login interface parameters
*/
export interface LoginParams {
username: string
password: string
captchaVerification: string
}
/**
@ -31,10 +28,6 @@ export interface LoginResultModel {
* @description: Get user information return value
*/
export interface GetUserInfoModel {
roles: string[]
permissions: string[]
menus: RouteItem[]
// 用户id
user: userModel
}

36
src/api/base/user.ts

@ -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 })
}

29
src/api/base/user/index.ts

@ -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',
})
}

32
src/api/base/user/types.ts

@ -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
}

11
src/api/system/area/index.ts

@ -1,11 +0,0 @@
import { defHttp } from '@/utils/http/axios'
// 获得地区树
export function getAreaTree() {
return defHttp.get({ url: '/system/area/tree' })
}
// 获得 IP 对应的地区名
export function getAreaByIp(ip: string) {
return defHttp.get({ url: `/system/area/get-by-ip?ip=${ip}` })
}

62
src/api/system/dept/index.ts

@ -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,
})
}

17
src/api/system/dept/types.ts

@ -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
}

36
src/api/system/dict/data.ts

@ -1,36 +0,0 @@
import type { DictDataExportReqVO, DictDataPageReqVO, DictDataVO } from './types'
import { defHttp } from '@/utils/http/axios'
// 查询字典数据(精简)列表
export function listSimpleDictData() {
return defHttp.get({ url: '/system/dict-data/list-all-simple' })
}
// 查询字典数据列表
export function getDictDataPage(params: DictDataPageReqVO) {
return defHttp.get({ url: '/system/dict-data/page', params })
}
// 查询字典数据详情
export function getDictData(id: number) {
return defHttp.get({ url: `/system/dict-data/get?id=${id}` })
}
// 新增字典数据
export function createDictData(data: DictDataVO) {
return defHttp.post({ url: '/system/dict-data/create', data })
}
// 修改字典数据
export function updateDictData(data: DictDataVO) {
return defHttp.put({ url: '/system/dict-data/update', data })
}
// 删除字典数据
export function deleteDictData(id: number) {
return defHttp.delete({ url: `/system/dict-data/delete?id=${id}` })
}
// 导出字典类型数据
export function exportDictData(params: DictDataExportReqVO) {
return defHttp.get({ url: '/system/dict-data/export', params })
}

36
src/api/system/dict/type.ts

@ -1,36 +0,0 @@
import type { DictTypeExportReqVO, DictTypePageReqVO, DictTypeVO } from './types'
import { defHttp } from '@/utils/http/axios'
// 查询字典(精简)列表
export function listSimpleDictType() {
return defHttp.get({ url: '/system/dict-type/list-all-simple' })
}
// 查询字典列表
export function getDictTypePage(params: DictTypePageReqVO) {
return defHttp.get({ url: '/system/dict-type/page', params })
}
// 查询字典详情
export function getDictType(id: number) {
return defHttp.get({ url: `/system/dict-type/get?id=${id}` })
}
// 新增字典
export function createDictType(data: DictTypeVO) {
return defHttp.post({ url: '/system/dict-type/create', data })
}
// 修改字典
export function updateDictType(data: DictTypeVO) {
return defHttp.put({ url: '/system/dict-type/update', data })
}
// 删除字典
export function deleteDictType(id: number) {
return defHttp.delete({ url: `/system/dict-type/delete?id=${id}` })
}
// 导出字典类型
export function exportDictType(params: DictTypeExportReqVO) {
return defHttp.get({ url: '/system/dict-type/export', params })
}

46
src/api/system/dict/types.ts

@ -1,46 +0,0 @@
export interface DictTypeVO {
id: number
name: string
type: string
status: number
remark: string
createTime: Date
}
export interface DictTypePageReqVO {
name: string
type: string
status: number
createTime: Date[]
}
export interface DictTypeExportReqVO {
name: string
type: string
status: number
createTime: Date[]
}
export interface DictDataVO {
id: number
sort: number
label: string
value: string
dictType: string
status: number
colorType: string
cssClass: string
remark: string
createTime: Date
}
export interface DictDataPageReqVO {
label: string
dictType: string
status: number
}
export interface DictDataExportReqVO {
label: string
dictType: string
status: number
}

49
src/api/system/errorCode/index.ts

@ -1,49 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface ErrorCodeVO {
id: number
type: number
applicationName: string
code: number
message: string
memo: string
createTime: Date
}
export interface ErrorCodePageReqVO extends PageParam {
type?: number
applicationName?: string
code?: number
message?: string
createTime?: Date[]
}
// 查询错误码列表
export function getErrorCodePage(params: ErrorCodePageReqVO) {
return defHttp.get({ url: '/system/error-code/page', params })
}
// 查询错误码详情
export function getErrorCode(id: number) {
return defHttp.get({ url: `/system/error-code/get?id=${id}` })
}
// 新增错误码
export function createErrorCode(data: ErrorCodeVO) {
return defHttp.post({ url: '/system/error-code/create', data })
}
// 修改错误码
export function updateErrorCode(data: ErrorCodeVO) {
return defHttp.put({ url: '/system/error-code/update', data })
}
// 删除错误码
export function deleteErrorCode(id: number) {
return defHttp.delete({ url: `/system/error-code/delete?id=${id}` })
}
// 导出错误码
export function excelErrorCode(params: ErrorCodePageReqVO) {
return defHttp.download({ url: '/system/error-code/export-excel', params }, '错误码.xls')
}

30
src/api/system/loginLog/index.ts

@ -1,30 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface LoginLogVO {
id: number
logType: number
traceId: number
userId: number
userType: number
username: string
status: number
userIp: string
userAgent: string
createTime: Date
}
export interface LoginLogReqVO extends PageParam {
userIp?: string
username?: string
status?: boolean
createTime?: Date[]
}
// 查询登录日志列表
export function getLoginLogPage(params: LoginLogReqVO) {
return defHttp.get({ url: '/system/login-log/page', params })
}
// 导出登录日志
export function exportLoginLog(params: LoginLogReqVO) {
return defHttp.download({ url: '/system/login-log/export', params }, '登录日志.xls')
}

31
src/api/system/mail/account.ts

@ -1,31 +0,0 @@
import { defHttp } from '@/utils/http/axios'
// 创建邮箱账号
export function createMailAccount(data) {
return defHttp.post({ url: '/system/mail-account/create', data })
}
// 更新邮箱账号
export function updateMailAccount(data) {
return defHttp.put({ url: '/system/mail-account/update', data })
}
// 删除邮箱账号
export function deleteMailAccount(id: number) {
return defHttp.delete({ url: `/system/mail-account/delete?id=${id}` })
}
// 获得邮箱账号
export function getMailAccount(id: number) {
return defHttp.get({ url: `/system/mail-account/get?id=${id}` })
}
// 获得邮箱账号分页
export function getMailAccountPage(params) {
return defHttp.get({ url: '/system/mail-account/page', params })
}
// 获取邮箱账号的精简信息列表
export function getSimpleMailAccountList() {
return defHttp.get({ url: '/system/mail-account/list-all-simple' })
}

11
src/api/system/mail/log.ts

@ -1,11 +0,0 @@
import { defHttp } from '@/utils/http/axios'
// 获得邮件日志
export function getMailLog(id: number) {
return defHttp.get({ url: `/system/mail-log/get?id=${id}` })
}
// 获得邮件日志分页
export function getMailAccountPage(params) {
return defHttp.get({ url: '/system/mail-log/page', params })
}

54
src/api/system/mail/template.ts

@ -1,54 +0,0 @@
import { defHttp } from '@/utils/http/axios'
// 创建邮件模版
export function createMailTemplate(data) {
return defHttp.post({ url: '/system/mail-template/create', data })
}
// 更新邮件模版
export function updateMailTemplate(data) {
return defHttp.put({ url: '/system/mail-template/update', data })
}
// 删除邮件模版
export function deleteMailTemplate(id: number) {
return defHttp.delete({ url: `/system/mail-template/delete?id=${id}` })
}
// 获得邮件模版
export function getMailTemplate(id: number) {
return defHttp.get({ url: `/system/mail-template/get?id=${id}` })
}
// 获得邮件模版分页
export function getMailTemplatePage(params) {
return defHttp.get({ url: '/system/mail-template/page', params })
}
// 邮件模板
export interface MailTemplate {
name: string // 标题
code: string // 编码
accountId: number
nickname: string // 发送人
title: string // 标题
content: string // 内容
status: number //
remark?: any // 备注
id: number
params: string[] // 模板里的参数
createTime: number
}
export interface SendMailParams {
mail: string
templateCode: string
templateParams: {
[key: string]: any
}
}
// 发送测试邮件
export function sendMail(data: SendMailParams) {
return defHttp.post({ url: '/system/mail-template/send-mail', data })
}

67
src/api/system/menu/index.ts

@ -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}`,
})
}

26
src/api/system/menu/types.ts

@ -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
}

42
src/api/system/notice/index.ts

@ -1,42 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface NoticeVO {
id: number
title: string
type: number
content: string
status: number
remark: string
creator: string
createTime: Date
}
export interface NoticePageReqVO extends PageParam {
title?: string
status?: number
}
// 查询公告列表
export function getNoticePage(params: NoticePageReqVO) {
return defHttp.get({ url: '/system/notice/page', params })
}
// 查询公告详情
export function getNotice(id: number) {
return defHttp.get({ url: `/system/notice/get?id=${id}` })
}
// 新增公告
export function createNotice(data: NoticeVO) {
return defHttp.post({ url: '/system/notice/create', data })
}
// 修改公告
export function updateNotice(data: NoticeVO) {
return defHttp.put({ url: '/system/notice/update', data })
}
// 删除公告
export function deleteNotice(id: number) {
return defHttp.delete({ url: `/system/notice/delete?id=${id}` })
}

32
src/api/system/notify/message.ts

@ -1,32 +0,0 @@
import qs from 'qs'
import { defHttp } from '@/utils/http/axios'
// 获得站内信分页
export function getNotifyMessagePage(params) {
return defHttp.get({ url: '/system/notify-message/page', params })
}
// 获得我的站内信分页
export function getMyNotifyMessagePage(params) {
return defHttp.get({ url: '/system/notify-message/my-page', params })
}
// 批量标记已读
export function updateNotifyMessageRead(ids: number[]) {
return defHttp.put({ url: `/system/notify-message/update-read?${qs.stringify({ ids }, { indices: false })}` })
}
// 标记所有站内信为已读
export function updateAllNotifyMessageRead() {
return defHttp.put({ url: '/system/notify-message/update-all-read' })
}
// 获取当前用户的最新站内信列表
export function getUnreadNotifyMessageList() {
return defHttp.get({ url: '/system/notify-message/get-unread-list' })
}
// 获得当前用户的未读站内信数量
export function getUnreadNotifyMessageCount() {
return defHttp.get<number>({ url: '/system/notify-message/get-unread-count' })
}

63
src/api/system/notify/template.ts

@ -1,63 +0,0 @@
import { defHttp } from '@/utils/http/axios'
// 创建站内信模板
export function createNotifyTemplate(data) {
return defHttp.post({ url: '/system/notify-template/create', data })
}
// 更新站内信模板
export function updateNotifyTemplate(data) {
return defHttp.put({ url: '/system/notify-template/update', data })
}
// 删除站内信模板
export function deleteNotifyTemplate(id: number) {
return defHttp.delete({ url: `/system/notify-template/delete?id=${id}` })
}
// 获得站内信模板
export function getNotifyTemplate(id: number) {
return defHttp.get({ url: `/system/notify-template/get?id=${id}` })
}
// 获得站内信模板分页
export function getNotifyTemplatePage(params) {
return defHttp.get({ url: '/system/notify-template/page', params })
}
// 获取岗位精简信息列表
export function listSimplePosts() {
return defHttp.get({ url: '/system/post/list-all-simple' })
}
// 导出站内信模板 Excel
export function exportNotifyTemplateExcel(params) {
return defHttp.download({ url: '/system/notify-template/export-excel', params }, '导出站内信模板.xls')
}
export interface SendNotifyParam {
userId: number
templateCode: string
templateParams: {
[key: string]: string
}
}
export interface NotifyTemplate {
name: string
code: string
type: number
nickname: string
content: string
status: number
remark?: any
id: number
params: string[]
createTime: number
key: string
}
// 发送
export function sendNotify(data: SendNotifyParam) {
return defHttp.post({ url: '/system/notify-template/send-notify', data })
}

51
src/api/system/oauth2/client.ts

@ -1,51 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface OAuth2ClientVO {
id: number
clientId: string
secret: string
name: string
logo: string
description: string
status: number
accessTokenValiditySeconds: number
refreshTokenValiditySeconds: number
redirectUris: string[]
autoApprove: boolean
authorizedGrantTypes: string[]
scopes: string[]
authorities: string[]
resourceIds: string[]
additionalInformation: string
isAdditionalInformationJson: boolean
createTime: Date
}
export interface OAuth2ClientPageReqVO extends PageParam {
name?: string
status?: number
}
// 查询 OAuth2列表
export function getOAuth2ClientPage(params: OAuth2ClientPageReqVO) {
return defHttp.get({ url: '/system/oauth2-client/page', params })
}
// 查询 OAuth2详情
export function getOAuth2Client(id: number) {
return defHttp.get({ url: `/system/oauth2-client/get?id=${id}` })
}
// 新增 OAuth2
export function createOAuth2Client(data: OAuth2ClientVO) {
return defHttp.post({ url: '/system/oauth2-client/create', data })
}
// 修改 OAuth2
export function updateOAuth2Client(data: OAuth2ClientVO) {
return defHttp.put({ url: '/system/oauth2-client/update', data })
}
// 删除 OAuth2
export function deleteOAuth2Client(id: number) {
return defHttp.delete({ url: `/system/oauth2-client/delete?id=${id}` })
}

28
src/api/system/oauth2/token.ts

@ -1,28 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface OAuth2TokenVO {
id: number
accessToken: string
refreshToken: string
userId: number
userType: number
clientId: string
createTime: Date
expiresTime: Date
}
export interface OAuth2TokenPageReqVO extends PageParam {
userId?: number
userType?: number
clientId?: string
}
// 查询 token列表
export function getAccessTokenPage(params: OAuth2TokenPageReqVO) {
return defHttp.get({ url: '/system/oauth2-token/page', params })
}
// 删除 token
export function deleteAccessToken(accessToken: number) {
return defHttp.delete({ url: `/system/oauth2-token/delete?accessToken=${accessToken}` })
}

41
src/api/system/operatelog/index.ts

@ -1,41 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface OperateLogVO {
id: number
userNickname: string
traceId: string
userId: number
module: string
name: string
type: number
content: string
exts: Map<string, object>
defHttpMethod: string
defHttpUrl: string
userIp: string
userAgent: string
javaMethod: string
javaMethodArgs: string
startTime: Date
duration: number
resultCode: number
resultMsg: string
resultData: string
}
export interface OperateLogPageReqVO extends PageParam {
module?: string
userNickname?: string
type?: number
success?: boolean
startTime?: Date[]
}
// 查询操作日志列表
export function getOperateLogPage(params: OperateLogPageReqVO) {
return defHttp.get({ url: '/system/operate-log/page', params })
}
// 导出操作日志
export function exportOperateLog(params: OperateLogPageReqVO) {
return defHttp.download({ url: '/system/operate-log/export', params }, '操作日志.xls')
}

42
src/api/system/permission/index.ts

@ -1,42 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface PermissionAssignUserRoleReqVO {
userId: number
roleIds: number[]
}
export interface PermissionAssignRoleMenuReqVO {
roleId: number
menuIds: number[]
}
export interface PermissionAssignRoleDataScopeReqVO {
roleId: number
dataScope: number
dataScopeDeptIds: number[]
}
// 查询角色拥有的菜单权限
export function listRoleMenus(roleId: number) {
return defHttp.get({ url: `/system/permission/list-role-menus?roleId=${roleId}` })
}
// 赋予角色菜单权限
export function assignRoleMenu(data: PermissionAssignRoleMenuReqVO) {
return defHttp.post({ url: '/system/permission/assign-role-menu', data })
}
// 赋予角色数据权限
export function assignRoleDataScope(data: PermissionAssignRoleDataScopeReqVO) {
return defHttp.post({ url: '/system/permission/assign-role-data-scope', data })
}
// 查询用户拥有的角色数组
export function listUserRoles(userId: number) {
return defHttp.get({ url: `/system/permission/list-user-roles?userId=${userId}` })
}
// 赋予用户角色
export function assignUserRole(data: PermissionAssignUserRoleReqVO) {
return defHttp.post({ url: '/system/permission/assign-user-role', data })
}

58
src/api/system/post/index.ts

@ -1,58 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface PostVO {
id?: number
name: string
code: string
sort: number
status: number
remark: string
createTime?: Date
}
export interface PostPageReqVO extends PageParam {
code?: string
name?: string
status?: number
}
export interface PostExportReqVO {
code?: string
name?: string
status?: number
}
// 查询岗位列表
export function getPostPage(params: PostPageReqVO) {
return defHttp.get<PageResult<PostVO>>({ url: '/system/post/page', params })
}
// 获取岗位精简信息列表
export function listSimplePosts() {
return defHttp.get({ url: '/system/post/list-all-simple' })
}
// 查询岗位详情
export function getPost(id: number) {
return defHttp.get({ url: `/system/post/get?id=${id}` })
}
// 新增岗位
export function createPost(data) {
return defHttp.post({ url: '/system/post/create', data })
}
// 修改岗位
export function updatePost(data) {
return defHttp.put({ url: '/system/post/update', data })
}
// 删除岗位
export function deletePost(id: number) {
return defHttp.delete({ url: `/system/post/delete?id=${id}` })
}
// 导出岗位
export function exportPost(params: PostExportReqVO) {
return defHttp.download({ url: '/system/post/export', params }, '导出岗位.xls')
}

92
src/api/system/role/index.ts

@ -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,
})
}

29
src/api/system/role/types.ts

@ -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[]
}

64
src/api/system/sensitiveWord/index.ts

@ -1,64 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface SensitiveWordVO {
id: number
name: string
status: number
description: string
tags: string[]
createTime: Date
}
export interface SensitiveWordPageReqVO extends PageParam {
name?: string
tag?: string
status?: number
createTime?: Date[]
}
export interface SensitiveWordExportReqVO {
name?: string
tag?: string
status?: number
createTime?: Date[]
}
// 查询敏感词列表
export function getSensitiveWordPage(params: SensitiveWordPageReqVO) {
return defHttp.get({ url: '/system/sensitive-word/page', params })
}
// 查询敏感词详情
export function getSensitiveWord(id: number) {
return defHttp.get({ url: `/system/sensitive-word/get?id=${id}` })
}
// 新增敏感词
export function createSensitiveWord(data: SensitiveWordVO) {
return defHttp.post({ url: '/system/sensitive-word/create', data })
}
// 修改敏感词
export function updateSensitiveWord(data: SensitiveWordVO) {
return defHttp.put({ url: '/system/sensitive-word/update', data })
}
// 删除敏感词
export function deleteSensitiveWord(id: number) {
return defHttp.delete({ url: `/system/sensitive-word/delete?id=${id}` })
}
// 导出敏感词
export function exportSensitiveWord(params: SensitiveWordExportReqVO) {
return defHttp.download({ url: '/system/sensitive-word/export-excel', params }, '导出敏感词.xls')
}
// 获取所有敏感词的标签数组
export function getSensitiveWordTags() {
return defHttp.get({ url: '/system/sensitive-word/get-tags' })
}
// 获得文本所包含的不合法的敏感词数组
export function validateText(id: number) {
return defHttp.get({ url: `/system/sensitive-word/validate-text?${id}` })
}

50
src/api/system/sms/smsChannel/index.ts

@ -1,50 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface SmsChannelVO {
id: number
code: string
status: number
signature: string
remark: string
apiKey: string
apiSecret: string
callbackUrl: string
createTime: Date
}
export interface SmsChannelPageReqVO extends PageParam {
signature?: string
code?: string
status?: number
createTime?: Date[]
}
// 查询短信渠道列表
export function getSmsChannelPage(params: SmsChannelPageReqVO) {
return defHttp.get({ url: '/system/sms-channel/page', params })
}
// 获得短信渠道精简列表
export function getSimpleSmsChannels() {
return defHttp.get({ url: '/system/sms-channel/list-all-simple' })
}
// 查询短信渠道详情
export function getSmsChannel(id: number) {
return defHttp.get({ url: `/system/sms-channel/get?id=${id}` })
}
// 新增短信渠道
export function createSmsChannel(data: SmsChannelVO) {
return defHttp.post({ url: '/system/sms-channel/create', data })
}
// 修改短信渠道
export function updateSmsChannel(data: SmsChannelVO) {
return defHttp.put({ url: '/system/sms-channel/update', data })
}
// 删除短信渠道
export function deleteSmsChannel(id: number) {
return defHttp.delete({ url: `/system/sms-channel/delete?id=${id}` })
}

55
src/api/system/sms/smsLog/index.ts

@ -1,55 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface SmsLogVO {
id: number
channelId: number
channelCode: string
templateId: number
templateCode: string
templateType: number
templateContent: string
templateParams: Map<string, object>
mobile: string
userId: number
userType: number
sendStatus: number
sendTime: Date
apiSendCode: string
apiSendMsg: string
apidefHttpId: string
apiSerialNo: string
receiveStatus: number
receiveTime: Date
apiReceiveCode: string
apiReceiveMsg: string
createTime: Date
}
export interface SmsLogPageReqVO extends PageParam {
channelId?: number
templateId?: number
mobile?: string
sendStatus?: number
sendTime?: Date[]
receiveStatus?: number
receiveTime?: Date[]
}
export interface SmsLogExportReqVO {
channelId?: number
templateId?: number
mobile?: string
sendStatus?: number
sendTime?: Date[]
receiveStatus?: number
receiveTime?: Date[]
}
// 查询短信日志列表
export function getSmsLogPage(params: SmsLogPageReqVO) {
return defHttp.get({ url: '/system/sms-log/page', params })
}
// 导出短信日志
export function exportSmsLog(params: SmsLogExportReqVO) {
return defHttp.download({ url: '/system/sms-log/export', params }, '短信日志.xls')
}

94
src/api/system/sms/smsTemplate/index.ts

@ -1,94 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface SmsTemplateVO {
id: number
type: number
status: number
code: string
name: string
content: string
remark: string
apiTemplateId: string
channelId: number
channelCode: string
params: string[]
createTime: Date
}
export interface SendSmsReqVO {
mobile: string
templateCode: string
templateParams: {
[key: string]: any
}
}
export interface SmsTemplatePageReqVO {
type?: number
status?: number
code?: string
content?: string
apiTemplateId?: string
channelId?: number
createTime?: Date[]
}
export interface SmsTemplateExportReqVO {
type?: number
status?: number
code?: string
content?: string
apiTemplateId?: string
channelId?: number
createTime?: Date[]
}
// 查询短信模板列表
export function getSmsTemplatePage(params: SmsTemplatePageReqVO) {
return defHttp.get({ url: '/system/sms-template/page', params })
}
// 查询短信模板详情
export function getSmsTemplate(id: number) {
return defHttp.get({ url: `/system/sms-template/get?id=${id}` })
}
// 新增短信模板
export function createSmsTemplate(data: SmsTemplateVO) {
return defHttp.post({ url: '/system/sms-template/create', data })
}
// 修改短信模板
export function updateSmsTemplate(data: SmsTemplateVO) {
return defHttp.put({ url: '/system/sms-template/update', data })
}
// 删除短信模板
export function deleteSmsTemplate(id: number) {
return defHttp.delete({ url: `/system/sms-template/delete?id=${id}` })
}
// 邮件模板
export interface SmsTemplate {
name: string // 标题
code: string // 编码
accountId: number
nickname: string // 发送人
title: string // 标题
content: string // 内容
status: number //
remark?: any // 备注
id: number
params: string[] // 模板里的参数
createTime: number
}
// 发送短信
export function sendSms(data: SendSmsReqVO) {
return defHttp.post({ url: '/system/sms-template/send-sms', data })
}
// 导出短信模板
export function exportSmsTemplate(params: SmsTemplateExportReqVO) {
return defHttp.download({ url: '/system/sms-template/export-excel', params }, '短信模板.xls')
}

75
src/api/system/tenant/index.ts

@ -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',
})
}

16
src/api/system/tenant/types.ts

@ -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
}

49
src/api/system/tenantPackage/index.ts

@ -1,49 +0,0 @@
import { defHttp } from '@/utils/http/axios'
export interface TenantPackageVO {
id: number
name: string
status: number
remark: string
creator: string
updater: string
updateTime: string
menuIds: number[]
createTime: Date
}
export interface TenantPackagePageReqVO extends PageParam {
name?: string
status?: number
remark?: string
createTime?: Date[]
}
// 查询租户套餐列表
export function getTenantPackagePage(params: TenantPackagePageReqVO) {
return defHttp.get({ url: '/system/tenant-package/page', params })
}
// 获得租户
export function getTenantPackage(id: number) {
return defHttp.get({ url: `/system/tenant-package/get?id=${id}` })
}
// 新增租户套餐
export function createTenantPackage(data: TenantPackageVO) {
return defHttp.post({ url: '/system/tenant-package/create', data })
}
// 修改租户套餐
export function updateTenantPackage(data: TenantPackageVO) {
return defHttp.put({ url: '/system/tenant-package/update', data })
}
// 删除租户套餐
export function deleteTenantPackage(id: number) {
return defHttp.delete({ url: `/system/tenant-package/delete?id=${id}` })
}
// 获取租户套餐精简信息列表
export function getTenantPackageList() {
return defHttp.get({ url: '/system/tenant-package/get-simple-list' })
}

102
src/api/system/user/index.ts

@ -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}`,
})
}

23
src/api/system/user/types.ts

@ -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
}

4
src/components/DictTag/index.ts

@ -1,4 +0,0 @@
import dictTag from './src/DictTag.vue'
import { withInstall } from '@/utils'
export const DictTag = withInstall(dictTag)

72
src/components/DictTag/src/DictTag.vue

@ -1,72 +0,0 @@
<script lang="tsx">
import type { PropType } from 'vue'
import { defineComponent, ref } from 'vue'
import { Tag } from 'ant-design-vue'
import { isHexColor } from '@/utils/color'
import type { DictDataType } from '@/utils/dict'
import { getDictOpts } from '@/utils/dict'
import { propTypes } from '@/utils/propTypes'
export default defineComponent({
name: 'DictTag',
props: {
type: {
type: String as PropType<string>,
required: true,
},
value: propTypes.oneOfType([propTypes.string, propTypes.number, propTypes.bool]),
icon: { type: String },
},
setup(props) {
const dictData = ref<DictDataType>()
const getDictObj = (dictType: string, value: string) => {
let dictOptions: DictDataType[] = []
dictOptions = getDictOpts(dictType)
if (dictOptions && dictOptions.length === 0)
return
dictOptions.forEach((dict: DictDataType) => {
if (dict.value === value) {
if (`${dict.colorType}` === 'primary')
dict.colorType = 'processing'
else if (`${dict.colorType}` === 'danger')
dict.colorType = 'error'
else if (`${dict.colorType}` === 'info')
dict.colorType = 'default'
dictData.value = dict
}
})
}
const rederDictTag = () => {
if (!props.type)
return null
//
if (props.value === undefined || props.value === null)
return null
getDictObj(props.type, props.value.toString())
if (dictData.value === undefined)
return null
// && isHexColor(dictData.value?.cssClass)
return (
<Tag
color={
dictData.value?.colorType
? dictData.value?.colorType
: dictData.value?.cssClass && isHexColor(dictData.value?.cssClass)
? dictData.value?.cssClass
: ''
}
>
{dictData.value?.label || ''}
</Tag>
)
}
return () => rederDictTag()
},
})
</script>

8
src/components/Form/src/BasicForm.vue

@ -309,14 +309,6 @@ const getFormActionBindProps = computed(() => ({ ...getProps.value, ...advanceSt
margin: 0 6px 0 2px;
}
&-with-help {
margin-bottom: 0;
}
&:not(.ant-form-item-with-help) {
margin-bottom: 20px;
}
&.suffix-item {
.ant-form-item-children {
display: flex;

19
src/components/Form/src/components/ApiTreeSelect.vue

@ -14,7 +14,7 @@ const props = defineProps({
api: { type: Function as PropType<(arg?: any) => Promise<Recordable<any>>> },
params: { type: Object },
immediate: propTypes.bool.def(true),
async: propTypes.bool.def(false),
loadData: { type: Function as PropType<(treeNode: Recordable<any>) => Promise<Recordable<any>>> },
resultField: propTypes.string.def(''),
handleTree: propTypes.string.def(''),
parentId: propTypes.number.def(0),
@ -24,7 +24,7 @@ const props = defineProps({
valueField: propTypes.string.def('id'),
childrenField: propTypes.string.def('children'),
})
const emit = defineEmits(['options-change', 'change', 'load-data'])
const emit = defineEmits(['options-change', 'change'])
const attrs = useAttrs()
const treeData = ref<Recordable<any>[]>([])
const isFirstLoaded = ref<boolean>(false)
@ -64,14 +64,15 @@ onMounted(() => {
props.immediate && fetch()
})
function onLoadData(treeNode) {
return new Promise((resolve: (value?: unknown) => void) => {
if (isArray(treeNode.children) && treeNode.children.length > 0) {
resolve()
async function onLoadData(treeNode) {
if (isArray(treeNode.children) && treeNode.children.length > 0)
return
try {
treeNode.dataRef.children = await props.loadData!(treeNode)
treeData.value = [...treeData.value]
}
emit('load-data', { treeData, treeNode, resolve })
})
catch {}
}
async function fetch() {
@ -114,7 +115,7 @@ async function fetch() {
<TreeSelect
v-bind="getAttrs"
:field-names="fieldNames"
:load-data="async ? onLoadData : undefined"
:load-data="loadData ? onLoadData : undefined"
@change="handleChange"
>
<template v-for="item in Object.keys($slots)" #[item]="data">

22
src/components/Form/src/hooks/useFormEvents.ts

@ -124,7 +124,7 @@ export function useFormEvents({
const { componentProps } = schema || {}
let _props = componentProps as any
if (typeof componentProps === 'function')
_props = _props({ formModel: unref(formModel) })
_props = _props({ formModel: unref(formModel), formActionType })
const constructValue = tryConstructArray(key, values) || tryConstructObject(key, values)
@ -340,6 +340,10 @@ export function useFormEvents({
return handleFormValues(values)
}
async function setProps(formProps: Partial<FormProps>): Promise<void> {
await unref(formElRef)?.setProps(formProps)
}
async function validate(nameList?: NamePath[] | false | undefined) {
let _nameList: any
if (nameList === undefined)
@ -385,6 +389,22 @@ export function useFormEvents({
}
}
const formActionType: FormActionType = {
getFieldsValue,
setFieldsValue,
resetFields,
updateSchema,
resetSchema,
setProps,
removeSchemaByField,
appendSchemaByField,
clearValidate,
validateFields,
validate,
submit: handleSubmit,
scrollToField,
}
return {
handleSubmit,
clearValidate,

5
src/components/Form/src/hooks/useFormValues.ts

@ -67,6 +67,7 @@ export function useFormValues({
continue
const transformDateFunc = unref(getProps).transformDateFunc
if (isObject(value))
value = transformDateFunc?.(value)
@ -75,11 +76,7 @@ export function useFormValues({
// Remove spaces
if (isString(value)) {
// remove params from URL
if (value === '')
value = undefined
else
value = value.trim()
}
if (!tryDeconstructArray(key, value, res) && !tryDeconstructObject(key, value, res)) {

2
src/components/Form/src/types/index.ts

@ -134,7 +134,7 @@ export interface ComponentProps {
ApiSelect: CustomComponents['ApiSelect'] & ComponentProps['Select']
TreeSelect: ExtractPropTypes<(typeof import('ant-design-vue/es/tree-select'))['default']>
ApiTree: CustomComponents['ApiTree'] & ExtractPropTypes<(typeof import('ant-design-vue/es/tree'))['default']>
ApiTreeSelect: CustomComponents['ApiTreeSelect'] & ComponentProps['TreeSelect']
ApiTreeSelect: CustomComponents['ApiTreeSelect'] & Omit<ComponentProps['TreeSelect'], 'loadData'>
ApiRadioGroup: CustomComponents['ApiRadioGroup'] & ComponentProps['RadioGroup']
RadioButtonGroup: CustomComponents['RadioButtonGroup'] & ComponentProps['RadioGroup']
RadioGroup: ExtractPropTypes<(typeof import('ant-design-vue/es/radio'))['RadioGroup']>

55
src/components/Icon/src/IconPicker.vue

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { ref, watch, watchEffect } from 'vue'
import { Empty, Input, Pagination, Popover } from 'ant-design-vue'
import { Empty, Input, Popover } from 'ant-design-vue'
import { useDebounceFn } from '@vueuse/core'
import svgIcons from 'virtual:svg-icons-names'
import SvgIcon from './SvgIcon.vue'
@ -8,7 +8,6 @@ import { getIcons } from './icons'
import { useDesign } from '@/hooks/web/useDesign'
import { ScrollContainer } from '@/components/Container'
import { usePagination } from '@/hooks/web/usePagination'
import { useI18n } from '@/hooks/web/useI18n'
import { copyText } from '@/utils/copyTextToClipboard'
@ -39,15 +38,13 @@ const icons = isSvgMode ? getSvgIcons() : getIcons()
const currentSelect = ref('')
const open = ref(false)
const currentList = ref(icons)
const currentList = ref(icons.slice(0, props.pageSize))
const { t } = useI18n()
const { prefixCls } = useDesign('icon-picker')
const debounceHandleSearchChange = useDebounceFn(handleSearchChange, 100)
const { getPaginationList, getTotal, setCurrentPage } = usePagination(currentList, props.pageSize)
watchEffect(() => {
currentSelect.value = props.value
})
@ -60,24 +57,27 @@ watch(
},
)
function handlePageChange(page: number) {
setCurrentPage(page)
}
function handleClick(icon: string) {
currentSelect.value = icon
if (props.copy)
copyText(icon, t('component.icon.copy'))
}
const hiddenLoadMore = ref(false)
function loadMoreIcons() {
currentList.value = icons.slice(0, currentList.value.length + props.pageSize)
hiddenLoadMore.value = currentList.value.length >= icons.length
}
function handleSearchChange(e: ChangeEvent) {
const value = e.target.value
if (!value) {
setCurrentPage(1)
currentList.value = icons
hiddenLoadMore.value = false
currentList.value = icons.slice(0, props.pageSize)
return
}
currentList.value = icons.filter(item => item.includes(value))
hiddenLoadMore.value = true
}
</script>
@ -90,32 +90,37 @@ function handleSearchChange(e: ChangeEvent) {
<Popover v-model="open" placement="bottomLeft" trigger="click" :overlay-class-name="`${prefixCls}-popover`">
<template #title>
<div class="flex justify-between">
<Input :placeholder="t('component.icon.search')" allow-clear @change="debounceHandleSearchChange" />
<Input placeholder="Search" allow-clear @change="debounceHandleSearchChange">
<template #prefix>
<span class="i-ant-design:search-outlined text-gray-600" />
</template>
</Input>
</div>
</template>
<template #content>
<div v-if="getPaginationList.length">
<ScrollContainer class="border border-t-0 border-solid">
<ul class="flex flex-wrap px-2">
<div v-if="currentList.length">
<ScrollContainer>
<ul class="grid grid-cols-8 gap-y-2">
<li
v-for="icon in getPaginationList" :key="icon"
:class="currentSelect === icon ? 'border border-primary' : ''"
class="mr-1 mt-1 w-1/8 flex cursor-pointer items-center justify-center border border-solid p-2 hover:border-primary"
v-for="icon in currentList" :key="icon"
:class="currentSelect === icon ? '!text-primary' : ''"
class="cursor-pointer text-center text-gray-600"
:title="icon" @click="handleClick(icon)"
>
<!-- <Icon :icon="icon" :prefix="prefix" /> -->
<SvgIcon v-if="isSvgMode" :name="icon" />
<span v-else :class="icon" />
<span v-else :class="icon" class="text-18px" />
</li>
</ul>
</ScrollContainer>
<div v-if="getTotal >= pageSize" class="flex items-center justify-center py-2">
<Pagination show-less-items size="small" :page-size="pageSize" :total="getTotal" @change="handlePageChange" />
<div v-if="!hiddenLoadMore" class="mt-4 text-center text-gray-600">
<a-button size="small" @click="loadMoreIcons">
Load More
</a-button>
</div>
</ScrollContainer>
</div>
<template v-else>
<div class="p-5">
<div class="p-5 text-gray-600">
<Empty />
</div>
</template>
@ -124,7 +129,7 @@ function handleSearchChange(e: ChangeEvent) {
<span v-if="isSvgMode && currentSelect" class="flex cursor-pointer items-center px-2 py-1">
<SvgIcon :name="currentSelect" />
</span>
<span v-else :class="currentSelect || 'i-ion:apps-outline'" class="cursor-pointer px-4 py-2" />
<span v-else :class="currentSelect || 'i-ion:apps-outline'" class="cursor-pointer px-4 py-2 text-gray-600" />
</Popover>
</template>
</Input>

2
src/components/Table/src/BasicTable.vue

@ -366,7 +366,7 @@ emit('register', tableAction, formActions)
}
.ant-table-wrapper {
padding: 6px;
padding: 20px;
background-color: var(--component-background);
border-radius: 6px;

3
src/components/Table/src/components/TableAction.vue

@ -1,7 +1,6 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, toRaw, unref } from 'vue'
import { DownOutlined } from '@ant-design/icons-vue'
import type { TooltipProps } from 'ant-design-vue'
import { Divider, Tooltip } from 'ant-design-vue'
import { useTableContext } from '../hooks/useTableContext'
@ -144,7 +143,7 @@ function onCellClick(e: MouseEvent) {
>
<slot name="more" />
<a-button v-if="!$slots.more" type="link">
{{ t('action.more') }} <DownOutlined class="icon-more" />
{{ t('action.more') }} <span class="i-tabler:chevron-down" />
</a-button>
</Dropdown>
</div>

1
src/components/Table/src/hooks/useColumns.ts

@ -122,7 +122,6 @@ export function useColumns(
columns.forEach((item) => {
const { customRender, slots } = item
handleItem(item, Reflect.has(item, 'ellipsis') ? !!item.ellipsis : !!ellipsis && !customRender && !slots)
})
return columns

13
src/components/Table/src/hooks/useRender.ts

@ -3,7 +3,6 @@ import dayjs from 'dayjs'
import { Button, Tag } from 'ant-design-vue'
import TableImg from '../components/TableImg.vue'
import { isArray, isString } from '@/utils/is'
import { DictTag } from '@/components/DictTag'
import { JsonPreview } from '@/components/CodeEditor'
export const useRender = {
@ -87,18 +86,6 @@ export const useRender = {
else
return dayjs(text).format(format)
},
/**
*
* @param text
* @param dictType
* @returns
*/
renderDict: (text: string, dictType: string) => {
if (dictType)
return h(DictTag, { type: dictType, value: text })
return ''
},
/**
* icon
* @param text icon

2
src/components/Table/src/types/table.ts

@ -172,7 +172,7 @@ export interface BasicTableProps<T = Recordable<any>> {
/**
* , `src/settings/componentSetting.ts > table > fetchSetting`
*/
api?: (...arg: any) => Promise<T[] | { list: T[], pageNo?: number, pageSize?: number, total?: number }>
api?: (...arg: any) => Promise<T[] | { records: T[], current?: number, size?: number, total?: number }>
// 请求之前处理参数
beforeFetch?: Fn
// 自定义处理接口返回参数

2
src/components/Tree/src/types/tree.ts

@ -77,7 +77,7 @@ export const treeProps = buildProps({
},
treeData: {
type: Array as PropType<TreeDataItem[]>,
type: Array as PropType<(Omit<TreeDataItem, 'key'> & { key?: string | number })[]>,
},
actionList: {

15
src/enums/appEnum.ts

@ -25,21 +25,6 @@ export enum SessionTimeoutProcessingEnum {
PAGE_COVERAGE,
}
/**
*
*/
export enum PermissionModeEnum {
// role
// 角色权限
ROLE = 'ROLE',
// black
// 后端
BACK = 'BACK',
// route mapping
// 路由映射
ROUTE_MAPPING = 'ROUTE_MAPPING',
}
// Route switching animation
// 路由切换动画
export enum RouterTransitionEnum {

3
src/enums/cacheEnum.ts

@ -16,9 +16,6 @@ export const ROLES_KEY = 'ROLES__KEY__'
// project config key
export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'
// dict info key
export const DICT_KEY = 'DICT__KEY__'
// lock info
export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'

2
src/enums/httpEnum.ts

@ -2,7 +2,7 @@
* @description: Request result set
*/
export enum ResultEnum {
SUCCESS = 0,
SUCCESS = 200,
ERROR = -1,
TIMEOUT = 400,
UNAUTHORIZED = 401,

9
src/enums/systemEnum.ts

@ -1,12 +1,3 @@
/**
*
*/
export const SystemMenuTypeEnum = {
DIR: 1, // 目录
MENU: 2, // 菜单
BUTTON: 3, // 按钮
}
/**
*
*/

4
src/hooks/component/useFormItem.ts

@ -1,5 +1,5 @@
import type { DeepReadonly, Ref, UnwrapRef, WritableComputedRef } from 'vue'
import { computed, getCurrentInstance, nextTick, reactive, readonly, toRaw, unref, watchEffect } from 'vue'
import { computed, getCurrentInstance, reactive, readonly, toRaw, unref, watchEffect } from 'vue'
import { isEqual } from 'lodash-es'
@ -37,9 +37,7 @@ export function useRuleFormItem<T extends Recordable>(props: T, key: keyof T = '
return
innerState.value = value as T[keyof T]
nextTick(() => {
emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || []))
})
},
})

4
src/hooks/setting/index.ts

@ -10,8 +10,6 @@ export function useGlobSetting(): Readonly<GlobConfig> {
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_APP_TENANT_ENABLE,
VITE_GLOB_APP_CAPTCHA_ENABLE,
} = getAppEnvConfig()
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
@ -27,8 +25,6 @@ export function useGlobSetting(): Readonly<GlobConfig> {
shortName: VITE_GLOB_APP_SHORT_NAME,
urlPrefix: VITE_GLOB_API_URL_PREFIX,
uploadUrl: VITE_GLOB_UPLOAD_URL,
tenantEnable: VITE_GLOB_APP_TENANT_ENABLE,
captchaEnable: VITE_GLOB_APP_CAPTCHA_ENABLE,
}
return glob
}

3
src/hooks/setting/useRootSetting.ts

@ -18,8 +18,6 @@ export function useRootSetting() {
const getCanEmbedIFramePage = computed(() => appStore.getProjectConfig.canEmbedIFramePage)
const getPermissionMode = computed(() => appStore.getProjectConfig.permissionMode)
const getShowLogo = computed(() => appStore.getProjectConfig.showLogo)
const getContentMode = computed(() => appStore.getProjectConfig.contentMode)
@ -70,7 +68,6 @@ export function useRootSetting() {
getPageLoading,
getOpenKeepAlive,
getCanEmbedIFramePage,
getPermissionMode,
getShowLogo,
getUseErrorHandle,
getShowBreadCrumb,

42
src/hooks/web/usePermission.ts

@ -1,42 +1,19 @@
import type { RouteRecordRaw } from 'vue-router'
import { intersection } from 'lodash-es'
import { useTabs } from './useTabs'
import { useAppStore } from '@/store/modules/app'
import { usePermissionStore } from '@/store/modules/permission'
import { useUserStore } from '@/store/modules/user'
import { resetRouter, router } from '@/router'
// import { RootRoute } from '@/router/routes';
import projectSetting from '@/settings/projectSetting'
import { PermissionModeEnum } from '@/enums/appEnum'
import type { RoleEnum } from '@/enums/roleEnum'
import { isArray } from '@/utils/is'
import { useMultipleTabStore } from '@/store/modules/multipleTab'
// User permissions related operations
export function usePermission() {
const userStore = useUserStore()
const appStore = useAppStore()
const permissionStore = usePermissionStore()
const { closeAll } = useTabs(router)
/**
* Change permission mode
*/
function togglePermissionMode() {
appStore.setProjectConfig({
permissionMode: projectSetting.permissionMode
=== PermissionModeEnum.BACK
? PermissionModeEnum.ROUTE_MAPPING
: PermissionModeEnum.BACK,
})
location.reload()
}
/**
* Reset and regain authority resource information
*
@ -64,33 +41,18 @@ export function usePermission() {
if (!value)
return def
const permMode = appStore.getProjectConfig.permissionMode
if ([PermissionModeEnum.ROUTE_MAPPING, PermissionModeEnum.ROLE].includes(permMode)) {
if (!isArray(value))
return userStore.getRoleList?.includes(value as RoleEnum)
return (intersection(value, userStore.getRoleList) as RoleEnum[]).length > 0
}
if (PermissionModeEnum.BACK === permMode) {
const allCodeList = permissionStore.getPermCodeList as string[]
if (!isArray(value))
return allCodeList.includes(value)
return (intersection(value, allCodeList)).length > 0
}
return true
}
/**
* Change roles
* @param roles
*/
async function changeRole(roles: RoleEnum | RoleEnum[]): Promise<void> {
if (projectSetting.permissionMode !== PermissionModeEnum.ROUTE_MAPPING)
throw new Error('Please switch PermissionModeEnum to ROUTE_MAPPING mode in the configuration to operate!')
if (!isArray(roles))
roles = [roles]
@ -102,8 +64,8 @@ export function usePermission() {
* refresh menu data
*/
function refreshMenu() {
resume()
return resume()
}
return { changeRole, hasPermission, togglePermissionMode, refreshMenu }
return { changeRole, hasPermission, refreshMenu }
}

2
src/layouts/default/header/components/lock/LockModal.vue

@ -13,7 +13,7 @@ const { t } = useI18n()
const userStore = useUserStore()
const lockStore = useLockStore()
const getRealName = computed(() => userStore.getUserInfo?.user.nickname)
const getRealName = computed(() => userStore.getUserInfo?.user.realName)
const [register, { closeModal }] = useModalInner()
const [registerForm, { validateFields, resetFields }] = useForm({

6
src/layouts/default/header/components/user-dropdown/index.vue

@ -32,8 +32,8 @@ const { getShowDoc, getUseLockPage } = useHeaderSetting()
const userStore = useUserStore()
const getUserInfo = computed(() => {
const { nickname = '', avatar } = userStore.getUserInfo.user || {}
return { nickname, avatar: avatar || headerImg }
const { realName = '', avatar } = userStore.getUserInfo.user || {}
return { realName, avatar: avatar || headerImg }
})
const [register, { openModal }] = useModal()
@ -76,7 +76,7 @@ function handleMenuClick(e: MenuInfo) {
</Avatar>
<span :class="`${prefixCls}__info hidden md:block`">
<span :class="`${prefixCls}__name`" class="truncate">
{{ getUserInfo.nickname }}
{{ getUserInfo.realName }}
</span>
</span>
</span>

23
src/router/guard/paramMenuGuard.ts

@ -1,9 +1,6 @@
import type { Router } from 'vue-router'
import { configureDynamicParamsMenu } from '../helper/menuHelper'
import type { Menu } from '../types'
import { PermissionModeEnum } from '@/enums/appEnum'
import { useAppStoreWithOut } from '@/store/modules/app'
import { usePermissionStoreWithOut } from '@/store/modules/permission'
export function createParamMenuGuard(router: Router) {
@ -21,27 +18,9 @@ export function createParamMenuGuard(router: Router) {
return
}
let menus: Menu[] = []
if (isBackMode())
menus = permissionStore.getBackMenuList
else if (isRouteMappingMode())
menus = permissionStore.getFrontMenuList
const menus: Menu[] = permissionStore.getBackMenuList
menus.forEach(item => configureDynamicParamsMenu(item, to.params))
next()
})
}
function getPermissionMode() {
const appStore = useAppStoreWithOut()
return appStore.getProjectConfig.permissionMode
}
function isBackMode() {
return getPermissionMode() === PermissionModeEnum.BACK
}
function isRouteMappingMode() {
return getPermissionMode() === PermissionModeEnum.ROUTE_MAPPING
}

6
src/router/guard/permissionGuard.ts

@ -3,7 +3,6 @@ import type { RouteRecordRaw, Router } from 'vue-router'
import { usePermissionStoreWithOut } from '@/store/modules/permission'
import { PageEnum } from '@/enums/pageEnum'
import { useDictStoreWithOut } from '@/store/modules/dict'
import { useUserStoreWithOut } from '@/store/modules/user'
import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic'
@ -17,7 +16,6 @@ const LOGIN_PATH = PageEnum.BASE_LOGIN
const whitePathList: PageEnum[] = [LOGIN_PATH]
export function createPermissionGuard(router: Router) {
const dictStore = useDictStoreWithOut()
const userStore = useUserStoreWithOut()
const permissionStore = usePermissionStoreWithOut()
router.beforeEach(async (to, from, next) => {
@ -79,9 +77,6 @@ export function createPermissionGuard(router: Router) {
return
}
if (!dictStore.getIsSetDict)
await dictStore.setDictMap()
// get userinfo while last fetch time is empty
if (userStore.getLastUpdateTime === 0) {
try {
@ -108,7 +103,6 @@ export function createPermissionGuard(router: Router) {
})
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw)
permissionStore.setDynamicAddedRoute(true)
if (to.name === PAGE_NOT_FOUND_ROUTE.name) {

28
src/router/helper/routeHelper.ts

@ -6,6 +6,8 @@ import { EXCEPTION_COMPONENT, LAYOUT, getParentLayout } from '@/router/constant'
import type { AppRouteModule, AppRouteRecordRaw } from '@/router/types'
import { warn } from '@/utils/log'
import { isHttpUrl } from '@/utils/is'
import { toCamelCase } from '@/utils'
import type { MenuItem } from '@/api/system/menu/types'
export type LayoutMapKey = 'LAYOUT'
const IFRAME = () => import('@/views/base/iframe/FrameBlank.vue')
@ -44,7 +46,7 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
meta.orderNo = item.sort
meta.ignoreKeepAlive = !item.keepAlive
item.meta = meta
item.name = item.name = item.componentName && item.componentName.length > 0 ? item.componentName : toCamelCase(item.path, true)
item.name = item.componentName && item.componentName.length > 0 ? item.componentName : toCamelCase(item.path, true)
children && asyncImportRoute(children)
})
}
@ -76,11 +78,13 @@ function dynamicImport(dynamicViewsModules: Record<string, () => Promise<Recorda
// Turn background objects into routing objects
// 将背景对象变成路由对象
export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] {
routeList.forEach((route) => {
export function transformObjToRoute(menuList: MenuItem[]): AppRouteModule[] {
const routeList: AppRouteModule[] = []
menuList.forEach((item) => {
const route = { ...item } as unknown as AppRouteModule
if (isHttpUrl(route.path))
route.component = 'IFrame'
else if (route.children && route.parentId === 0)
else if (route.children && route.parentId === '0')
route.component = 'LAYOUT'
else if (!route.children)
route.component = route.component as string
@ -112,9 +116,10 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul
else {
warn(`请正确配置路由:${route?.name}的component属性`)
}
routeList.push(route)
route.children && asyncImportRoute(route.children)
})
return routeList as unknown as T[]
return routeList
}
/**
@ -194,16 +199,3 @@ function isMultipleRoute(routeModule: AppRouteModule) {
}
return flag
}
function toCamelCase(str: string, upperCaseFirst: boolean) {
str = (str || '')
.replace(/-(.)/g, (group1: string) => {
return group1.toUpperCase()
})
.replaceAll('-', '')
if (upperCaseFirst && str)
str = str.charAt(0).toUpperCase() + str.slice(1)
return str
}

70
src/router/menus/index.ts

@ -1,14 +1,6 @@
import type { RouteRecordNormalized } from 'vue-router'
import { pathToRegexp } from 'path-to-regexp'
import type { Menu, MenuModule } from '@/router/types'
import { useAppStoreWithOut } from '@/store/modules/app'
import { usePermissionStore } from '@/store/modules/permission'
import { getAllParentPath, transformMenuModule } from '@/router/helper/menuHelper'
import { filter } from '@/utils/helper/treeHelper'
import { isHttpUrl } from '@/utils/is'
import { router } from '@/router'
import { PermissionModeEnum } from '@/enums/appEnum'
const modules = import.meta.glob('./modules/**/*.ts', { eager: true })
@ -24,22 +16,6 @@ Object.keys(modules).forEach((key) => {
// ==========Helper===========
// ===========================
function getPermissionMode() {
const appStore = useAppStoreWithOut()
return appStore.getProjectConfig.permissionMode
}
function isBackMode() {
return getPermissionMode() === PermissionModeEnum.BACK
}
function isRouteMappingMode() {
return getPermissionMode() === PermissionModeEnum.ROUTE_MAPPING
}
function isRoleMode() {
return getPermissionMode() === PermissionModeEnum.ROLE
}
const staticMenus: Menu[] = []
;(() => {
menuModules.sort((a, b) => {
@ -62,21 +38,12 @@ function getAsyncMenus() {
return show
})
}
if (isBackMode())
return menuFilter(permissionStore.getBackMenuList)
if (isRouteMappingMode())
return menuFilter(permissionStore.getFrontMenuList)
return staticMenus
return menuFilter(permissionStore.getBackMenuList)
}
export async function getMenus(): Promise<Menu[]> {
const menus = await getAsyncMenus()
if (isRoleMode()) {
const routes = router.getRoutes()
return filter(menus, basicFilter(routes))
}
return menus
}
@ -90,10 +57,6 @@ export async function getCurrentParentPath(currentPath: string) {
export async function getShallowMenus(): Promise<Menu[]> {
const menus = await getAsyncMenus()
const shallowMenuList = menus.map(item => ({ ...item, children: undefined }))
if (isRoleMode()) {
const routes = router.getRoutes()
return shallowMenuList.filter(basicFilter(routes))
}
return shallowMenuList
}
@ -104,36 +67,5 @@ export async function getChildrenMenus(parentPath: string) {
if (!parent || !parent.children || !!parent?.meta?.hideChildrenInMenu)
return [] as Menu[]
if (isRoleMode()) {
const routes = router.getRoutes()
return filter(parent.children, basicFilter(routes))
}
return parent.children
}
function basicFilter(routes: RouteRecordNormalized[]) {
return (menu: Menu) => {
const matchRoute = routes.find((route) => {
if (isHttpUrl(menu.path))
return true
if (route.meta?.carryParam)
return pathToRegexp(route.path).test(menu.path)
const isSame = route.path === menu.path
if (!isSame)
return false
if (route.meta?.ignoreAuth)
return true
return isSame || pathToRegexp(route.path).test(menu.path)
})
if (!matchRoute)
return false
menu.icon = (menu.icon || matchRoute.meta.icon) as string
menu.meta = matchRoute.meta
return true
}
}

10
src/router/routes/index.ts

@ -38,15 +38,6 @@ export const LoginRoute: AppRouteRecordRaw = {
},
}
export const SSORoute: AppRouteRecordRaw = {
path: '/sso',
name: 'SSO',
component: () => import('@/views/base/login/sso.vue'),
meta: {
title: t('routes.basic.sso'),
},
}
export const ProfileRoute: AppRouteRecordRaw = {
path: '/profile',
component: LAYOUT,
@ -75,7 +66,6 @@ export const ProfileRoute: AppRouteRecordRaw = {
// 未经许可的基本路由
export const basicRoutes = [
LoginRoute,
SSORoute,
RootRoute,
ProfileRoute,
REDIRECT_ROUTE,

2
src/router/routes/modules/dashboard.ts

@ -7,7 +7,7 @@ const dashboard: AppRouteModule = {
path: '/dashboard',
name: 'Dashboard',
component: LAYOUT,
parentId: 0,
parentId: '0',
redirect: '/dashboard/analysis',
meta: {
orderNo: 10,

2
src/router/types.ts

@ -10,7 +10,7 @@ export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta' | 'childr
icon?: string
name: string
sort?: number
parentId?: number
parentId?: string
meta: RouteMeta
component?: Component | string
components?: Component

6
src/settings/componentSetting.ts

@ -9,11 +9,11 @@ export default {
// 支持 xxx.xxx.xxx格式
fetchSetting: {
// 传给后台的当前页字段
pageField: 'pageNo',
pageField: 'current',
// 传给后台的每页显示多少条的字段
sizeField: 'pageSize',
sizeField: 'size',
// 接口返回表格数据的字段
listField: 'list',
listField: 'records',
// 接口返回表格总数的字段
totalField: 'total',
},

6
src/settings/projectSetting.ts

@ -5,7 +5,6 @@ import { MenuModeEnum, MenuTypeEnum, MixSidebarTriggerEnum, TriggerEnum } from '
import { CacheTypeEnum } from '@/enums/cacheEnum'
import {
ContentEnum,
PermissionModeEnum,
RouterTransitionEnum,
SessionTimeoutProcessingEnum,
SettingButtonPositionEnum,
@ -26,11 +25,6 @@ const setting: ProjectConfig = {
// SettingButtonPositionEnum.FIXED: 固定在右侧
settingButtonPosition: SettingButtonPositionEnum.AUTO,
// 权限模式,默认前端角色权限模式
// ROUTE_MAPPING: 前端模式(菜单由路由生成,默认)
// ROLE:前端模式(菜单路由分开)
// BACK: 后端模式
permissionMode: PermissionModeEnum.BACK,
// 权限缓存存放位置。默认存放于localStorage
permissionCacheType: CacheTypeEnum.LOCAL,
// 会话超时处理方案

67
src/store/modules/dict.ts

@ -1,67 +0,0 @@
import { defineStore } from 'pinia'
import type { DictState } from '@/types/store'
import { store } from '@/store'
import { DICT_KEY } from '@/enums/cacheEnum'
import { createLocalStorage } from '@/utils/cache'
import { listSimpleDictData } from '@/api/system/dict/data'
import type { DictDataVO } from '@/api/system/dict/types'
const ls = createLocalStorage()
export const useDictStore = defineStore({
id: 'app-dict',
state: (): DictState => ({
dictMap: new Map<string, any>(),
isSetDict: false,
}),
getters: {
getDictMap(state): Recordable {
const dictMap = ls.get(DICT_KEY)
if (dictMap)
state.dictMap = dictMap
return state.dictMap
},
getIsSetDict(state): boolean {
return state.isSetDict
},
},
actions: {
async setDictMap() {
const dictMap = ls.get(DICT_KEY)
if (dictMap) {
this.dictMap = dictMap
this.isSetDict = true
}
else {
const res = await listSimpleDictData()
// 设置数据
const dictDataMap = new Map<string, any>()
res.forEach((dictData: DictDataVO) => {
// 获得 dictType 层级
const enumValueObj = dictDataMap[dictData.dictType]
if (!enumValueObj)
dictDataMap[dictData.dictType] = []
// 处理 dictValue 层级
dictDataMap[dictData.dictType].push({
value: dictData.value,
label: dictData.label,
colorType: dictData.colorType,
cssClass: dictData.cssClass,
})
})
this.dictMap = dictDataMap
this.isSetDict = true
ls.set(DICT_KEY, dictDataMap, 60) // 60 秒 过期
}
},
},
})
// Need to be used outside the setup
export function useDictStoreWithOut() {
return useDictStore(store)
}

3
src/store/modules/lock.ts

@ -37,13 +37,12 @@ export const useLockStore = defineStore('app-lock', {
const tryLogin = async () => {
// TODO 滑块验证码
try {
const username = userStore.getUserInfo?.user.nickname
const username = userStore.getUserInfo?.user.realName
const res = await userStore.login({
username,
password: password!,
goHome: false,
mode: 'none',
captchaVerification: '',
})
if (res)
this.resetLockInfo()

104
src/store/modules/permission.ts

@ -1,12 +1,9 @@
/* eslint-disable no-case-declarations */
import { toRaw } from 'vue'
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { useAppStoreWithOut } from './app'
import { store } from '@/store'
import type { AppRouteRecordRaw, Menu } from '@/router/types'
import { asyncRoutes } from '@/router/routes'
import dashboard from '@/router/routes/modules/dashboard'
import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic'
import { transformRouteToMenu } from '@/router/helper/menuHelper'
@ -14,9 +11,7 @@ import { flatMultiLevelRoutes, transformObjToRoute } from '@/router/helper/route
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { filter } from '@/utils/helper/treeHelper'
import projectSetting from '@/settings/projectSetting'
import { PageEnum } from '@/enums/pageEnum'
import { PermissionModeEnum } from '@/enums/appEnum'
interface PermissionState {
// Permission code list
@ -31,8 +26,6 @@ interface PermissionState {
// Backstage menu list
// 后台菜单列表
backMenuList: Menu[]
// 菜单列表
frontMenuList: Menu[]
}
export const usePermissionStore = defineStore('app-permission', {
@ -48,9 +41,6 @@ export const usePermissionStore = defineStore('app-permission', {
// Backstage menu list
// 后台菜单列表
backMenuList: [],
// menu List
// 菜单列表
frontMenuList: [],
}),
getters: {
getPermCodeList(state): string[] | number[] {
@ -59,9 +49,6 @@ export const usePermissionStore = defineStore('app-permission', {
getBackMenuList(state): Menu[] {
return state.backMenuList
},
getFrontMenuList(state): Menu[] {
return state.frontMenuList
},
getLastBuildMenuTime(state): number {
return state.lastBuildMenuTime
},
@ -79,10 +66,6 @@ export const usePermissionStore = defineStore('app-permission', {
list?.length > 0 && this.setLastBuildMenuTime()
},
setFrontMenuList(list: Menu[]) {
this.frontMenuList = list
},
setLastBuildMenuTime() {
this.lastBuildMenuTime = new Date().getTime()
},
@ -104,29 +87,15 @@ export const usePermissionStore = defineStore('app-permission', {
async buildRoutesAction(): Promise<AppRouteRecordRaw[]> {
const { t } = useI18n()
const userStore = useUserStore()
const appStore = useAppStoreWithOut()
let routes: AppRouteRecordRaw[] = []
const roleList = toRaw(userStore.getRoleList) || []
const userInfo = toRaw(userStore.getUserInfo) || {}
const { permissionMode = projectSetting.permissionMode } = appStore.getProjectConfig
// 路由过滤器 在 函数filter 作为回调传入遍历使用
const routeFilter = (route: AppRouteRecordRaw) => {
const { meta } = route
// 抽出角色
const { roles } = meta || {}
if (!roles)
return true
// 进行角色权限判断
return roleList.some(role => roles.includes(role))
}
/**
* @description ignoreRoute
*/
const routeRemoveIgnoreFilter = (route: AppRouteRecordRaw) => {
const { meta } = route
// ignoreRoute 为true 则路由仅用于菜单生成,不会在实际的路由表中出现
const { ignoreRoute } = meta || {}
// arr.filter 返回 true 表示该元素通过测试
return !ignoreRoute
}
@ -165,81 +134,22 @@ export const usePermissionStore = defineStore('app-permission', {
}
}
switch (permissionMode) {
// 角色权限
case PermissionModeEnum.ROLE:
// 对非一级路由进行过滤
routes = filter(asyncRoutes, routeFilter)
// 对一级路由根据角色权限过滤
routes = routes.filter(routeFilter)
// Convert multi-level routing to level 2 routing
// 将多级路由转换为 2 级路由
routes = flatMultiLevelRoutes(routes)
break
// 路由映射, 默认进入该case
case PermissionModeEnum.ROUTE_MAPPING:
// 对非一级路由进行过滤
routes = filter(asyncRoutes, routeFilter)
// 对一级路由再次根据角色权限过滤
routes = routes.filter(routeFilter)
// 将路由转换成菜单
const menuList = transformRouteToMenu(routes, true)
// 移除掉 ignoreRoute: true 的路由 非一级路由
routes = filter(routes, routeRemoveIgnoreFilter)
// 移除掉 ignoreRoute: true 的路由 一级路由;
routes = routes.filter(routeRemoveIgnoreFilter)
// 对菜单进行排序
menuList.sort((a, b) => {
return (a.meta?.orderNo || 0) - (b.meta?.orderNo || 0)
})
// 设置菜单列表
this.setFrontMenuList(menuList)
// Convert multi-level routing to level 2 routing
// 将多级路由转换为 2 级路由
routes = flatMultiLevelRoutes(routes)
break
// If you are sure that you do not need to do background dynamic permissions, please comment the entire judgment below
// 如果确定不需要做后台动态权限,请在下方注释整个判断
case PermissionModeEnum.BACK:
const { createMessage } = useMessage()
createMessage.loading({ content: t('sys.app.menuLoading'), duration: 1 })
// !Simulate to obtain permission codes from the background,
// 模拟从后台获取权限码,
// this function may only need to be executed once, and the actual project can be put at the right time by itself
// 这个功能可能只需要执行一次,实际项目可以自己放在合适的时间
let routeList: AppRouteRecordRaw[] = []
try {
routeList = userInfo.menus as AppRouteRecordRaw[]
}
catch (error) {
console.error(error)
}
// Dynamically introduce components
// 动态引入组件
routeList = transformObjToRoute(routeList)
let routeList = transformObjToRoute(userInfo.menus)
// Background routing to menu structure
// 后台路由到菜单结构
const backMenuList = transformRouteToMenu([dashboard, ...routeList])
this.setBackMenuList(backMenuList)
// remove meta.ignoreRoute item
// 删除 meta.ignoreRoute 项
routeList = filter(routeList, routeRemoveIgnoreFilter)
routeList = routeList.filter(routeRemoveIgnoreFilter)
routeList = flatMultiLevelRoutes(routeList)
routes = [PAGE_NOT_FOUND_ROUTE, dashboard, ...routeList]
break
}
// 从用户中获取权限
if (userInfo)
this.setPermCodeList(userInfo.permissions)
routes = [PAGE_NOT_FOUND_ROUTE, dashboard, ...routeList]
patchHomeAffix(routes)
return routes
},

52
src/store/modules/user.ts

@ -12,14 +12,12 @@ import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic'
import { usePermissionStore } from '@/store/modules/permission'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { getAuthCache, setAuthCache } from '@/utils/auth'
import { doLogout, getUserInfo, loginApi, smsLogin } from '@/api/base/user'
import type { GetUserInfoModel, LoginParams, SmsLoginParams } from '@/api/base/model/userModel'
import { isArray } from '@/utils/is'
import { getAuthCache, setAuthCache, setTenantId } from '@/utils/auth'
import { doLogout, getUserInfo, loginApi } from '@/api/base/user'
import type { LoginParams, UserInfo } from '@/api/base/user/types'
interface UserState {
userInfo: Nullable<GetUserInfoModel>
userInfo: Nullable<UserInfo>
accessToken?: string
refreshToken?: string
roleList: RoleEnum[]
@ -42,8 +40,8 @@ export const useUserStore = defineStore('app-user', {
lastUpdateTime: 0,
}),
getters: {
getUserInfo(state): GetUserInfoModel {
return state.userInfo || getAuthCache<GetUserInfoModel>(USER_INFO_KEY) || {}
getUserInfo(state): UserInfo {
return state.userInfo || getAuthCache<UserInfo>(USER_INFO_KEY) || {}
},
getAccessToken(state): string {
return state.accessToken || getAuthCache<string>(ACCESS_TOKEN_KEY)
@ -74,7 +72,7 @@ export const useUserStore = defineStore('app-user', {
this.roleList = roleList
setAuthCache(ROLES_KEY, roleList)
},
setUserInfo(info: GetUserInfoModel | null) {
setUserInfo(info: UserInfo | null) {
this.userInfo = info
this.lastUpdateTime = new Date().getTime()
setAuthCache(USER_INFO_KEY, info)
@ -96,7 +94,7 @@ export const useUserStore = defineStore('app-user', {
goHome?: boolean
mode?: ErrorMessageMode
},
): Promise<GetUserInfoModel | null> {
): Promise<UserInfo | null> {
try {
const { goHome = true, mode, ...loginParams } = params
const data = await loginApi(loginParams, mode)
@ -111,26 +109,7 @@ export const useUserStore = defineStore('app-user', {
return Promise.reject(error)
}
},
async smsLogin(
params: SmsLoginParams & {
goHome?: boolean
mode?: ErrorMessageMode
},
): Promise<GetUserInfoModel | null> {
try {
const { goHome = true, mode, ...smsLoginParams } = params
const data = await smsLogin(smsLoginParams, mode)
const { accessToken, refreshToken } = data
// save token
this.setAccessToken(accessToken)
this.setRefreshToken(refreshToken)
return this.afterLoginAction(goHome)
}
catch (error) {
return Promise.reject(error)
}
},
async afterLoginAction(goHome?: boolean): Promise<GetUserInfoModel | null> {
async afterLoginAction(goHome?: boolean): Promise<UserInfo | null> {
if (!this.getAccessToken)
return null
// get user info
@ -157,19 +136,12 @@ export const useUserStore = defineStore('app-user', {
}
return userInfo
},
async getUserInfoAction(): Promise<GetUserInfoModel | null> {
async getUserInfoAction(): Promise<UserInfo | null> {
if (!this.getAccessToken)
return null
const userInfo = await getUserInfo()
const { roles = [] } = userInfo
if (isArray(roles)) {
const roleList = roles.map(item => item) as RoleEnum[]
this.setRoleList(roleList)
}
else {
userInfo.roles = []
this.setRoleList([])
}
setTenantId(userInfo.user.tenantId)
this.setUserInfo(userInfo)
return userInfo
},

24
src/store/modules/userMessage.ts

@ -1,24 +0,0 @@
import { defineStore } from 'pinia'
import { getUnreadNotifyMessageCount } from '@/api/system/notify/message'
interface MessageState {
unreadCount: number // 未读消息数量
}
export const useUserMessageStore = defineStore('userMessage', {
state: (): MessageState => ({
unreadCount: 0,
}),
getters: {
getUnreadCount(state) {
return state.unreadCount
},
},
actions: {
// 更新未读消息的数量
async updateUnreadCount() {
const count = await getUnreadNotifyMessageCount()
this.unreadCount = count
},
},
})

2
src/types/axios.d.ts vendored

@ -38,7 +38,7 @@ export interface RetryRequest {
export interface Result<T = any> {
code: number
msg: string
message: string
data: T
}

11
src/types/config.d.ts vendored

@ -1,7 +1,6 @@
import type { MenuModeEnum, MenuTypeEnum, MixSidebarTriggerEnum, TriggerEnum } from '@/enums/menuEnum'
import type {
ContentEnum,
PermissionModeEnum,
RouterTransitionEnum,
SessionTimeoutProcessingEnum,
SettingButtonPositionEnum,
@ -103,8 +102,6 @@ export interface ProjectConfig {
showDarkModeToggle: boolean
// Configure where the button is displayed
settingButtonPosition: SettingButtonPositionEnum
// Permission mode
permissionMode: PermissionModeEnum
// Session timeout processing
sessionTimeoutProcessing: SessionTimeoutProcessingEnum
// Website gray mode, open for possible mourning dates
@ -161,10 +158,6 @@ export interface GlobConfig {
urlPrefix?: string
// Project abbreviation
shortName: string
// 租户开关
tenantEnable: string
// 验证码开关
captchaEnable: string
}
export interface GlobEnvConfig {
// Site title
@ -177,8 +170,4 @@ export interface GlobEnvConfig {
VITE_GLOB_APP_SHORT_NAME: string
// Upload url
VITE_GLOB_UPLOAD_URL?: string
// 租户开关
VITE_GLOB_APP_TENANT_ENABLE: string
// 验证码开关
VITE_GLOB_APP_CAPTCHA_ENABLE: string
}

7
src/types/global.d.ts vendored

@ -1,6 +1,5 @@
import type { ComponentPublicInstance, ComponentRenderProxy, FunctionalComponent, VNode, VNodeChild, PropType as VuePropType } from 'vue'
import type { AttributifyAttributes } from '@unocss/preset-attributify'
import type { AttributifyAttributes } from 'unocss/preset-attributify'
declare global {
const __APP_INFO__: {
@ -12,9 +11,6 @@ declare global {
}
lastBuildTime: string
}
declare interface Window {
_hmt: [string, string][]
}
interface Document {
mozFullScreenElement?: Element
@ -30,6 +26,7 @@ declare global {
-readonly [P in keyof T]: T[P]
}
declare type BooleanFlag = 0 | 1
declare type Nullable<T> = T | null
declare type NonNullable<T> = T extends null | undefined ? never : T
declare type Recordable<T = any> = Record<string, T>

8
src/types/index.d.ts vendored

@ -9,12 +9,14 @@ declare interface PromiseFn<T = any, R = T> {
declare type RefType<T> = T | null
declare interface PageParam {
pageSize?: number
pageNo?: number
size?: number
current?: number
}
declare interface PageResult<T = any> {
list: T[]
records: T[]
current: number
size: number
total: number
}

5
src/types/store.d.ts vendored

@ -45,8 +45,3 @@ export interface BeforeMiniState {
menuMode?: MenuModeEnum
menuType?: MenuTypeEnum
}
export interface DictState {
dictMap: Map<string, any>
isSetDict: boolean
}

2
src/utils/auth/index.ts

@ -26,7 +26,7 @@ export function getTenantId(): string {
return getAuthCache(TENANT_ID_KEY)
}
export function setTenantId(value) {
export function setTenantId(value: string) {
return setAuthCache(TENANT_ID_KEY, value)
}

97
src/utils/dict.ts

@ -1,97 +0,0 @@
/**
*
*/
import { useDictStoreWithOut } from '@/store/modules/dict'
import type { StringLiteralsToType } from '@/types/utils'
const dictStore = useDictStoreWithOut()
/**
* dictType
*
* @param dictType
* @returns {*|Array}
*/
export interface DictDataType<T extends string | number | boolean = string> {
dictType: string
label: string
value: T
key?: any
colorType: string
cssClass: string
}
export function getDictDatas(dictType: string) {
return dictStore.getDictMap[dictType] || []
}
export function getDictOpts(dictType: string) {
/**
* Tag
* getDictOptions来处理
*
* bugfix:
* dictOption.push({
...dict,
value: parseInt(dict.value + '')
})
*/
return getDictDatas(dictType)
}
export function getDictOptions<T extends 'string' | 'number' | 'boolean' = 'string'>(dictType: string, valueType?: T) {
const dictOption: DictDataType<StringLiteralsToType<T>>[] = []
const dictOptions: DictDataType[] = getDictDatas(dictType)
if (dictOptions && dictOptions.length > 0) {
dictOptions.forEach((dict: DictDataType) => {
dictOption.push({
...dict,
key: dict.value,
value:
valueType === 'string'
? `${dict.value}`
: valueType === 'boolean'
? `${dict.value}` === 'true'
: Number.parseInt(`${dict.value}`),
} as DictDataType<StringLiteralsToType<T>>)
})
}
return dictOption
}
export function getDictObj(dictType: string, value: any) {
const dictOptions: DictDataType[] = getDictDatas(dictType)
if (dictOptions) {
dictOptions.forEach((dict: DictDataType) => {
if (dict.value === value.toString())
return dict
})
}
else {
return null
}
}
export enum DICT_TYPE {
USER_TYPE = 'user_type',
COMMON_STATUS = 'common_status',
SYSTEM_TENANT_PACKAGE_ID = 'system_tenant_package_id',
SYSTEM_USER_SEX = 'system_user_sex',
SYSTEM_MENU_TYPE = 'system_menu_type',
SYSTEM_ROLE_TYPE = 'system_role_type',
SYSTEM_DATA_SCOPE = 'system_data_scope',
SYSTEM_NOTICE_TYPE = 'system_notice_type',
SYSTEM_OPERATE_TYPE = 'system_operate_type',
SYSTEM_LOGIN_TYPE = 'system_login_type',
SYSTEM_LOGIN_RESULT = 'system_login_result',
SYSTEM_SMS_CHANNEL_CODE = 'system_sms_channel_code',
SYSTEM_SMS_TEMPLATE_TYPE = 'system_sms_template_type',
SYSTEM_SMS_SEND_STATUS = 'system_sms_send_status',
SYSTEM_SMS_RECEIVE_STATUS = 'system_sms_receive_status',
SYSTEM_ERROR_CODE_TYPE = 'system_error_code_type',
SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type',
SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',
SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type',
INFRA_BOOLEAN_STRING = 'infra_boolean_string',
}

4
src/utils/env.ts

@ -22,8 +22,6 @@ export function getAppEnvConfig() {
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_APP_TENANT_ENABLE,
VITE_GLOB_APP_CAPTCHA_ENABLE,
} = ENV
if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
@ -38,8 +36,6 @@ export function getAppEnvConfig() {
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_APP_TENANT_ENABLE,
VITE_GLOB_APP_CAPTCHA_ENABLE,
}
}

14
src/utils/http/axios/index.ts

@ -22,7 +22,6 @@ import { AxiosRetry } from '@/utils/http/axios/axiosRetry'
const globSetting = useGlobSetting()
const urlPrefix = globSetting.urlPrefix
const tenantEnable = globSetting.tenantEnable
const { createMessage, createErrorModal, createSuccessModal } = useMessage()
// 请求白名单,无须token的接口
@ -59,11 +58,11 @@ const transform: AxiosTransform = {
throw new Error(t('sys.api.apiRequestFailed'))
}
// 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
const { code, data: result, msg } = data
const { code, data: result, message } = data
// 这里逻辑可以根据项目进行修改
const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS
if (hasSuccess) {
let successMsg = msg
let successMsg = message
if (isNull(successMsg) || isUndefined(successMsg) || isEmpty(successMsg))
successMsg = t('sys.api.operationSuccess')
@ -89,8 +88,8 @@ const transform: AxiosTransform = {
userStore.logout(true)
break
default:
if (msg)
timeoutMsg = msg
if (message)
timeoutMsg = message
}
// errorMessageMode='modal' 的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
@ -201,11 +200,10 @@ const transform: AxiosTransform = {
: token
}
// 设置租户
if (tenantEnable && tenantEnable === 'true') {
const tenantId = getTenantId()
if (tenantId)
(config as Recordable).headers['tenant-id'] = tenantId
}
config.headers['tenant-id'] = tenantId
return config
},

13
src/utils/index.ts

@ -173,3 +173,16 @@ export function simpleDebounce(fn, delay = 100) {
}, delay)
}
}
export function toCamelCase(str: string, upperCaseFirst: boolean) {
str = (str || '')
.replace(/[-|\/](.)/g, (group1) => {
return group1.toUpperCase()
})
.replaceAll(/[-|\/]/g, '')
if (upperCaseFirst && str)
str = str.charAt(0).toUpperCase() + str.slice(1)
return str
}

2
src/views/base/lock/LockPage.vue

@ -85,7 +85,7 @@ function handleShowForm(show = false) {
<div :class="`${prefixCls}-entry__header enter-x`">
<img :src="userinfo.user.avatar || headerImg" :class="`${prefixCls}-entry__header-img`">
<p :class="`${prefixCls}-entry__header-name`">
{{ userinfo.user.nickname }}
{{ userinfo.user.realName }}
</p>
</div>
<InputPassword v-model:value="password" :placeholder="t('sys.lock.placeholder')" class="enter-x" />

4
src/views/base/login/Login.vue

@ -3,8 +3,6 @@ import { computed } from 'vue'
import LoginForm from './LoginForm.vue'
import ForgetPasswordForm from './ForgetPasswordForm.vue'
import RegisterForm from './RegisterForm.vue'
import MobileForm from './MobileForm.vue'
import QrCodeForm from './QrCodeForm.vue'
import { AppDarkModeToggle, AppLocalePicker, AppLogo } from '@/components/Application'
import { useGlobSetting } from '@/hooks/setting'
import { useI18n } from '@/hooks/web/useI18n'
@ -59,8 +57,6 @@ const title = computed(() => globSetting?.title ?? '')
<LoginForm />
<ForgetPasswordForm />
<RegisterForm />
<MobileForm />
<QrCodeForm />
</div>
</div>
</div>

107
src/views/base/login/LoginForm.vue

@ -1,25 +1,14 @@
<script lang="ts" setup>
import { computed, reactive, ref, unref } from 'vue'
import { Checkbox, Col, Divider, Form, Input, Row } from 'ant-design-vue'
import { AlipayCircleFilled, GithubFilled, WechatFilled } from '@ant-design/icons-vue'
import { Col, Form, Input, Row } from 'ant-design-vue'
import LoginFormTitle from './LoginFormTitle.vue'
import { LoginStateEnum, useFormRules, useFormValid, useLoginState } from './useLogin'
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 } from '@/api/base/login'
const FormItem = Form.Item
const InputPassword = Input.Password
@ -29,54 +18,21 @@ const { prefixCls } = useDesign('login')
const userStore = useUserStore()
const permissionStore = usePermissionStore()
const { tenantEnable, captchaEnable } = useGlobSetting()
const { setLoginState, getLoginState } = useLoginState()
const { getFormRules } = useFormRules()
const formRef = ref()
const loading = ref(false)
const rememberMe = ref(false)
const verify = ref()
const captchaType = ref('blockPuzzle') // blockPuzzle clickWord
const formData = reactive({
tenantName: '芋道源码',
username: 'admin',
password: 'admin123',
captchaVerification: '',
password: '123456',
})
const { validForm } = useFormValid(formRef)
// onKeyStroke('Enter', handleLogin);
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
//
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(params) {
await getTenantId()
async function handleLogin() {
const data = await validForm()
if (!data)
return
@ -85,14 +41,13 @@ async function handleLogin(params) {
const userInfo = await userStore.login({
password: data.password,
username: data.username,
captchaVerification: params.captchaVerification,
mode: 'none', //
})
if (userInfo) {
await permissionStore.changePermissionCode(userInfo.permissions)
permissionStore.changePermissionCode(userInfo.buttons)
notification.success({
message: t('sys.login.loginSuccessTitle'),
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.user.nickname}`,
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.user.realName}`,
duration: 3,
})
}
@ -116,15 +71,6 @@ async function handleLogin(params) {
v-show="getShow" ref="formRef" class="enter-x p-4" :model="formData" :rules="getFormRules"
@keypress.enter="handleLogin"
>
<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="username" class="enter-x">
<Input
v-model:value="formData.username" size="large" :placeholder="t('sys.login.userName')"
@ -144,15 +90,13 @@ async function handleLogin(params) {
<Row class="enter-x">
<Col :span="12">
<FormItem>
<!-- No logic, you need to deal with it yourself -->
<Checkbox v-model:checked="rememberMe" size="small">
{{ t('sys.login.rememberMe') }}
</Checkbox>
<a-button type="link" size="small" @click="setLoginState(LoginStateEnum.REGISTER)">
{{ t('sys.login.registerButton') }}
</a-button>
</FormItem>
</Col>
<Col :span="12">
<FormItem :style="{ 'text-align': 'right' }">
<!-- No logic, you need to deal with it yourself -->
<a-button type="link" size="small" @click="setLoginState(LoginStateEnum.RESET_PASSWORD)">
{{ t('sys.login.forgetPassword') }}
</a-button>
@ -161,42 +105,9 @@ async function handleLogin(params) {
</Row>
<FormItem class="enter-x">
<a-button type="primary" size="large" block :loading="loading" @click="getCode">
<a-button type="primary" size="large" block :loading="loading" @click="handleLogin">
{{ t('sys.login.loginButton') }}
</a-button>
<!-- <a-button size="large" class="mt-4 enter-x" block @click="handleRegister">
{{ t('sys.login.registerButton') }}
</a-button> -->
</FormItem>
<Row class="enter-x" :gutter="[16, 16]">
<Col :md="8" :xs="24">
<a-button block @click="setLoginState(LoginStateEnum.MOBILE)">
{{ t('sys.login.mobileSignInFormTitle') }}
</a-button>
</Col>
<Col :md="8" :xs="24">
<a-button block @click="setLoginState(LoginStateEnum.QR_CODE)">
{{ t('sys.login.qrSignInFormTitle') }}
</a-button>
</Col>
<Col :md="8" :xs="24">
<a-button block @click="setLoginState(LoginStateEnum.REGISTER)">
{{ t('sys.login.registerButton') }}
</a-button>
</Col>
</Row>
<Divider class="enter-x">
{{ t('sys.login.otherSignIn') }}
</Divider>
<div class="enter-x flex justify-evenly" :class="`${prefixCls}-sign-in-way`">
<GithubFilled />
<WechatFilled />
<AlipayCircleFilled />
<!-- <GoogleCircleFilled /> -->
<!-- <TwitterCircleFilled /> -->
</div>
</Form>
<Verify ref="verify" mode="pop" :captcha-type="captchaType" :img-size="{ width: '360px', height: '180px' }" @success="handleLogin" />
</template>

2
src/views/base/login/LoginFormTitle.vue

@ -12,8 +12,6 @@ const getFormTitle = computed(() => {
[LoginStateEnum.RESET_PASSWORD]: t('sys.login.forgetFormTitle'),
[LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'),
[LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'),
[LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'),
[LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle'),
}
return titleObj[unref(getLoginState)]
})

155
src/views/base/login/MobileForm.vue

@ -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>

37
src/views/base/login/QrCodeForm.vue

@ -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>

199
src/views/base/login/SSOForm.vue

@ -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_idscope
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>

16
src/views/base/login/SessionTimeoutLogin.vue

@ -3,32 +3,22 @@ import { onBeforeUnmount, onMounted, ref } from 'vue'
import Login from './Login.vue'
import { useUserStore } from '@/store/modules/user'
import { usePermissionStore } from '@/store/modules/permission'
import { useAppStore } from '@/store/modules/app'
import { PermissionModeEnum } from '@/enums/appEnum'
const userStore = useUserStore()
const permissionStore = usePermissionStore()
const appStore = useAppStore()
const userId = ref<Nullable<number | string>>(0)
function isBackMode() {
return appStore.getProjectConfig.permissionMode === PermissionModeEnum.BACK
}
onMounted(() => {
// UserId
userId.value = userStore.getUserInfo?.user.id
})
onBeforeUnmount(() => {
if (userId.value && userId.value !== userStore.getUserInfo.user.id) {
// 便
if (userId.value && userId.value !== userStore.getUserInfo.user.id)
document.location.reload()
}
else if (isBackMode() && permissionStore.getLastBuildMenuTime === 0) {
// F5
else if (permissionStore.getLastBuildMenuTime === 0)
document.location.reload()
}
})
</script>

200
src/views/base/login/sso.vue

@ -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>

Some files were not shown because too many files have changed in this diff Show More