commit
36ffbbbb95
|
|
@ -3,14 +3,11 @@ VITE_APP_TITLE=芋道管理系统
|
||||||
|
|
||||||
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
||||||
VITE_APP_NAMESPACE=yudao-vben-antd
|
VITE_APP_NAMESPACE=yudao-vben-antd
|
||||||
|
# 是否开启模拟数据
|
||||||
|
VITE_NITRO_MOCK=false
|
||||||
|
|
||||||
# 租户开关
|
# 租户开关
|
||||||
VITE_APP_TENANT_ENABLE=true
|
VITE_APP_TENANT_ENABLE=true
|
||||||
|
|
||||||
# 验证码的开关
|
# 验证码的开关
|
||||||
VITE_APP_CAPTCHA_ENABLE=false
|
VITE_APP_CAPTCHA_ENABLE=true
|
||||||
|
|
||||||
# 默认账户密码
|
|
||||||
VITE_APP_DEFAULT_LOGIN_TENANT=芋道源码
|
|
||||||
VITE_APP_DEFAULT_LOGIN_USERNAME=admin
|
|
||||||
VITE_APP_DEFAULT_LOGIN_PASSWORD=admin123
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,15 @@ VITE_BASE=/
|
||||||
|
|
||||||
# 接口地址
|
# 接口地址
|
||||||
VITE_GLOB_API_URL=/admin-api
|
VITE_GLOB_API_URL=/admin-api
|
||||||
|
|
||||||
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
|
||||||
VITE_NITRO_MOCK=false
|
|
||||||
|
|
||||||
# 是否打开 devtools,true 为打开,false 为关闭
|
# 是否打开 devtools,true 为打开,false 为关闭
|
||||||
VITE_DEVTOOLS=false
|
VITE_DEVTOOLS=false
|
||||||
|
|
||||||
# 是否注入全局loading
|
# 是否注入全局loading
|
||||||
VITE_INJECT_APP_LOADING=true
|
VITE_INJECT_APP_LOADING=true
|
||||||
|
|
||||||
|
# 默认租户名称
|
||||||
|
VITE_APP_DEFAULT_TENANT_NAME=芋道源码
|
||||||
|
# 默认登录用户名
|
||||||
|
VITE_APP_DEFAULT_USERNAME=admin
|
||||||
|
# 默认登录密码
|
||||||
|
VITE_APP_DEFAULT_PASSWORD=admin123
|
||||||
|
|
|
||||||
|
|
@ -42,13 +42,9 @@
|
||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "catalog:",
|
||||||
"ant-design-vue": "catalog:",
|
"ant-design-vue": "catalog:",
|
||||||
"crypto-js": "^4.2.0",
|
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"pinia": "catalog:",
|
"pinia": "catalog:",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-router": "catalog:"
|
"vue-router": "catalog:"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/crypto-js": "^4.2.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { CustomComponentType } from '#/components/form/types';
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
|
@ -36,6 +38,8 @@ import {
|
||||||
Upload,
|
Upload,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { registerComponent as registerCustomFormComponent } from '#/components/form/component-map';
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
type: 'input' | 'select',
|
type: 'input' | 'select',
|
||||||
|
|
@ -70,7 +74,8 @@ export type ComponentType =
|
||||||
| 'TimePicker'
|
| 'TimePicker'
|
||||||
| 'TreeSelect'
|
| 'TreeSelect'
|
||||||
| 'Upload'
|
| 'Upload'
|
||||||
| BaseFormComponentType;
|
| BaseFormComponentType
|
||||||
|
| CustomComponentType;
|
||||||
|
|
||||||
async function initComponentAdapter() {
|
async function initComponentAdapter() {
|
||||||
const components: Partial<Record<ComponentType, Component>> = {
|
const components: Partial<Record<ComponentType, Component>> = {
|
||||||
|
|
@ -108,6 +113,9 @@ async function initComponentAdapter() {
|
||||||
Upload,
|
Upload,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 注册自定义组件
|
||||||
|
registerCustomFormComponent(components);
|
||||||
|
|
||||||
// 将组件注册到全局共享状态中
|
// 将组件注册到全局共享状态中
|
||||||
globalShareState.setComponents(components);
|
globalShareState.setComponents(components);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,17 @@ setupVbenVxeTable({
|
||||||
// 全局禁用vxe-table的表单配置,使用formOptions
|
// 全局禁用vxe-table的表单配置,使用formOptions
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
import: true,
|
||||||
|
export: true,
|
||||||
|
refresh: true,
|
||||||
|
print: true,
|
||||||
|
zoom: true,
|
||||||
|
custom: true,
|
||||||
|
},
|
||||||
|
customConfig: {
|
||||||
|
mode: 'modal',
|
||||||
|
},
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
autoLoad: true,
|
autoLoad: true,
|
||||||
response: {
|
response: {
|
||||||
|
|
@ -29,6 +40,12 @@ setupVbenVxeTable({
|
||||||
showActiveMsg: true,
|
showActiveMsg: true,
|
||||||
showResponseMsg: false,
|
showResponseMsg: false,
|
||||||
},
|
},
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
sortConfig: {
|
||||||
|
multiple: true,
|
||||||
|
},
|
||||||
round: true,
|
round: true,
|
||||||
showOverflow: true,
|
showOverflow: true,
|
||||||
size: 'small',
|
size: 'small',
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import type { YudaoUserInfo } from '#/types';
|
import type { AuthPermissionInfo } from '@vben/types';
|
||||||
|
|
||||||
import { baseRequestClient, requestClient } from '#/api/request';
|
import { baseRequestClient, requestClient } from '#/api/request';
|
||||||
import { getRefreshToken } from '#/utils';
|
|
||||||
|
|
||||||
export namespace AuthApi {
|
export namespace AuthApi {
|
||||||
/** 登录接口参数 */
|
/** 登录接口参数 */
|
||||||
|
|
@ -13,40 +12,47 @@ export namespace AuthApi {
|
||||||
|
|
||||||
/** 登录接口返回值 */
|
/** 登录接口返回值 */
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
userId: number | string;
|
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
|
userId: number;
|
||||||
expiresTime: number;
|
expiresTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RefreshTokenResult {
|
export interface TenantSimple {
|
||||||
data: string;
|
id: number;
|
||||||
status: number;
|
name: string;
|
||||||
}
|
|
||||||
export interface SmsCodeVO {
|
|
||||||
mobile: string;
|
|
||||||
scene: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SmsLoginVO {
|
|
||||||
mobile: string;
|
|
||||||
code: string;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 登录
|
||||||
*/
|
*/
|
||||||
export function loginApi(data: AuthApi.LoginParams) {
|
export async function loginApi(data: AuthApi.LoginParams) {
|
||||||
return requestClient.post<AuthApi.LoginResult>('/system/auth/login', data);
|
return requestClient.post<AuthApi.LoginResult>('/system/auth/login', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新accessToken
|
* 刷新accessToken
|
||||||
*/
|
*/
|
||||||
export function refreshTokenApi() {
|
export async function refreshTokenApi(refreshToken: string) {
|
||||||
return requestClient.post<AuthApi.LoginResult>(
|
return requestClient.post<AuthApi.LoginResult>(
|
||||||
`/system/auth/refresh-token?refreshToken=${getRefreshToken()}`,
|
`/system/auth/refresh-token?refreshToken=${refreshToken}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
*/
|
||||||
|
export async function logoutApi() {
|
||||||
|
return requestClient.post('/system/auth/logout');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户权限信息
|
||||||
|
*/
|
||||||
|
export function getAuthPermissionInfoApi() {
|
||||||
|
return requestClient.get<AuthPermissionInfo>(
|
||||||
|
'/system/auth/get-permission-info',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,7 +61,7 @@ export function refreshTokenApi() {
|
||||||
* @param name 租户名
|
* @param name 租户名
|
||||||
* @returns 租户编号
|
* @returns 租户编号
|
||||||
*/
|
*/
|
||||||
export function getTenantIdByName(name: string) {
|
export async function getTenantIdByName(name: string) {
|
||||||
return requestClient.get<number>(
|
return requestClient.get<number>(
|
||||||
`/system/tenant/get-id-by-name?name=${name}`,
|
`/system/tenant/get-id-by-name?name=${name}`,
|
||||||
);
|
);
|
||||||
|
|
@ -66,68 +72,18 @@ export function getTenantIdByName(name: string) {
|
||||||
* @param website 域名
|
* @param website 域名
|
||||||
* @returns 租户信息
|
* @returns 租户信息
|
||||||
*/
|
*/
|
||||||
export function getTenantByWebsite(website: string) {
|
export async function getTenantByWebsite(website: string) {
|
||||||
return requestClient.get(`/system/tenant/get-by-website?website=${website}`);
|
return requestClient.get<AuthApi.TenantSimple>(
|
||||||
}
|
`/system/tenant/get-by-website?website=${website}`,
|
||||||
|
|
||||||
/**
|
|
||||||
* 退出登录
|
|
||||||
*/
|
|
||||||
export function logoutApi() {
|
|
||||||
return requestClient.post('/system/auth/logout');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户权限信息
|
|
||||||
*/
|
|
||||||
export function getUserInfo() {
|
|
||||||
return requestClient.get<YudaoUserInfo>('/system/auth/get-permission-info');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取登录验证码
|
|
||||||
*/
|
|
||||||
export function sendSmsCode(data: AuthApi.SmsCodeVO) {
|
|
||||||
return requestClient.post('/system/auth/send-sms-code', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信验证码登录
|
|
||||||
*/
|
|
||||||
export function smsLogin(data: AuthApi.SmsLoginVO) {
|
|
||||||
return requestClient.post('/system/auth/sms-login', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 社交快捷登录,使用 code 授权码
|
|
||||||
*/
|
|
||||||
export function socialLogin(type: string, code: string, state: string) {
|
|
||||||
return requestClient.post('/system/auth/social-login', {
|
|
||||||
type,
|
|
||||||
code,
|
|
||||||
state,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 社交授权的跳转
|
|
||||||
*/
|
|
||||||
export function socialAuthRedirect(type: number, redirectUri: string) {
|
|
||||||
return requestClient.get(
|
|
||||||
`/system/auth/social-auth-redirect?type=${type}&redirectUri=${redirectUri}`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 获取验证图片 以及token
|
||||||
* 获取验证图片 以及token
|
export async function getCaptcha(data: any) {
|
||||||
*/
|
|
||||||
export function getCaptcha(data: any) {
|
|
||||||
return baseRequestClient.post('/system/captcha/get', data);
|
return baseRequestClient.post('/system/captcha/get', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 滑动或者点选验证
|
||||||
* 滑动或者点选验证
|
export async function checkCaptcha(data: any) {
|
||||||
*/
|
|
||||||
export function checkCaptcha(data: any) {
|
|
||||||
return baseRequestClient.post('/system/captcha/check', data);
|
return baseRequestClient.post('/system/captcha/check', data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,312 @@
|
||||||
|
import { type PageParam, requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace CodegenApi {
|
||||||
|
/**
|
||||||
|
* 代码生成字段定义 Response VO
|
||||||
|
*/
|
||||||
|
export interface CodegenColumnRespVO {
|
||||||
|
/** 编号 */
|
||||||
|
id: number;
|
||||||
|
/** 表编号 */
|
||||||
|
tableId: number;
|
||||||
|
/** 字段名 */
|
||||||
|
columnName: string;
|
||||||
|
/** 字段类型 */
|
||||||
|
dataType: string;
|
||||||
|
/** 字段描述 */
|
||||||
|
columnComment: string;
|
||||||
|
/** 是否允许为空 */
|
||||||
|
nullable: boolean;
|
||||||
|
/** 是否主键 */
|
||||||
|
primaryKey: boolean;
|
||||||
|
/** 排序 */
|
||||||
|
ordinalPosition: number;
|
||||||
|
/** Java 属性类型 */
|
||||||
|
javaType: string;
|
||||||
|
/** Java 属性名 */
|
||||||
|
javaField: string;
|
||||||
|
/** 字典类型 */
|
||||||
|
dictType?: string;
|
||||||
|
/** 数据示例 */
|
||||||
|
example?: string;
|
||||||
|
/** 是否为 Create 创建操作的字段 */
|
||||||
|
createOperation: boolean;
|
||||||
|
/** 是否为 Update 更新操作的字段 */
|
||||||
|
updateOperation: boolean;
|
||||||
|
/** 是否为 List 查询操作的字段 */
|
||||||
|
listOperation: boolean;
|
||||||
|
/** List 查询操作的条件类型 */
|
||||||
|
listOperationCondition: string;
|
||||||
|
/** 是否为 List 查询操作的返回字段 */
|
||||||
|
listOperationResult: boolean;
|
||||||
|
/** 显示类型 */
|
||||||
|
htmlType: string;
|
||||||
|
/** 创建时间 */
|
||||||
|
createTime: string; // 假设为 ISO 字符串格式的 LocalDateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成字段定义创建/修改 Request VO
|
||||||
|
*/
|
||||||
|
export interface CodegenColumnSaveReqVO {
|
||||||
|
/** 编号 */
|
||||||
|
id: number;
|
||||||
|
/** 表编号 */
|
||||||
|
tableId: number;
|
||||||
|
/** 字段名 */
|
||||||
|
columnName: string;
|
||||||
|
/** 字段类型 */
|
||||||
|
dataType: string;
|
||||||
|
/** 字段描述 */
|
||||||
|
columnComment: string;
|
||||||
|
/** 是否允许为空 */
|
||||||
|
nullable: boolean;
|
||||||
|
/** 是否主键 */
|
||||||
|
primaryKey: boolean;
|
||||||
|
/** 排序 */
|
||||||
|
ordinalPosition: number;
|
||||||
|
/** Java 属性类型 */
|
||||||
|
javaType: string;
|
||||||
|
/** Java 属性名 */
|
||||||
|
javaField: string;
|
||||||
|
/** 字典类型 */
|
||||||
|
dictType?: string;
|
||||||
|
/** 数据示例 */
|
||||||
|
example?: string;
|
||||||
|
/** 是否为 Create 创建操作的字段 */
|
||||||
|
createOperation: boolean;
|
||||||
|
/** 是否为 Update 更新操作的字段 */
|
||||||
|
updateOperation: boolean;
|
||||||
|
/** 是否为 List 查询操作的字段 */
|
||||||
|
listOperation: boolean;
|
||||||
|
/** List 查询操作的条件类型 */
|
||||||
|
listOperationCondition: string;
|
||||||
|
/** 是否为 List 查询操作的返回字段 */
|
||||||
|
listOperationResult: boolean;
|
||||||
|
/** 显示类型 */
|
||||||
|
htmlType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表定义分页 Request VO
|
||||||
|
*/
|
||||||
|
export interface CodegenTablePageReqVO {
|
||||||
|
/** 表名称,模糊匹配 */
|
||||||
|
tableName?: string;
|
||||||
|
/** 表描述,模糊匹配 */
|
||||||
|
tableComment?: string;
|
||||||
|
/** 实体,模糊匹配 */
|
||||||
|
className?: string;
|
||||||
|
/** 创建时间 */
|
||||||
|
createTime?: [string, string]; // 假设为 ISO 字符串格式的 LocalDateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成表定义 Response VO
|
||||||
|
*/
|
||||||
|
export interface CodegenTableRespVO {
|
||||||
|
/** 编号 */
|
||||||
|
id: number;
|
||||||
|
/** 生成场景 */
|
||||||
|
scene: number;
|
||||||
|
/** 表名称 */
|
||||||
|
tableName: string;
|
||||||
|
/** 表描述 */
|
||||||
|
tableComment: string;
|
||||||
|
/** 备注 */
|
||||||
|
remark?: string;
|
||||||
|
/** 模块名 */
|
||||||
|
moduleName: string;
|
||||||
|
/** 业务名 */
|
||||||
|
businessName: string;
|
||||||
|
/** 类名称 */
|
||||||
|
className: string;
|
||||||
|
/** 类描述 */
|
||||||
|
classComment: string;
|
||||||
|
/** 作者 */
|
||||||
|
author: string;
|
||||||
|
/** 模板类型 */
|
||||||
|
templateType: number;
|
||||||
|
/** 前端类型 */
|
||||||
|
frontType: number;
|
||||||
|
/** 父菜单编号 */
|
||||||
|
parentMenuId?: number;
|
||||||
|
/** 主表的编号 */
|
||||||
|
masterTableId?: number;
|
||||||
|
/** 子表关联主表的字段编号 */
|
||||||
|
subJoinColumnId?: number;
|
||||||
|
/** 主表与子表是否一对多 */
|
||||||
|
subJoinMany?: boolean;
|
||||||
|
/** 树表的父字段编号 */
|
||||||
|
treeParentColumnId?: number;
|
||||||
|
/** 树表的名字字段编号 */
|
||||||
|
treeNameColumnId?: number;
|
||||||
|
/** 主键编号 */
|
||||||
|
dataSourceConfigId: number;
|
||||||
|
/** 创建时间 */
|
||||||
|
createTime: string; // 假设为 ISO 字符串格式的 LocalDateTime
|
||||||
|
/** 更新时间 */
|
||||||
|
updateTime: string; // 假设为 ISO 字符串格式的 LocalDateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成表定义创建/修改 Response VO
|
||||||
|
*/
|
||||||
|
export interface CodegenTableSaveReqVO {
|
||||||
|
/** 编号 */
|
||||||
|
id: number;
|
||||||
|
/** 生成场景 */
|
||||||
|
scene: number;
|
||||||
|
/** 表名称 */
|
||||||
|
tableName: string;
|
||||||
|
/** 表描述 */
|
||||||
|
tableComment: string;
|
||||||
|
/** 备注 */
|
||||||
|
remark?: string;
|
||||||
|
/** 模块名 */
|
||||||
|
moduleName: string;
|
||||||
|
/** 业务名 */
|
||||||
|
businessName: string;
|
||||||
|
/** 类名称 */
|
||||||
|
className: string;
|
||||||
|
/** 类描述 */
|
||||||
|
classComment: string;
|
||||||
|
/** 作者 */
|
||||||
|
author: string;
|
||||||
|
/** 模板类型 */
|
||||||
|
templateType: number;
|
||||||
|
/** 前端类型 */
|
||||||
|
frontType: number;
|
||||||
|
/** 父菜单编号 */
|
||||||
|
parentMenuId?: number;
|
||||||
|
/** 主表的编号 */
|
||||||
|
masterTableId?: number;
|
||||||
|
/** 子表关联主表的字段编号 */
|
||||||
|
subJoinColumnId?: number;
|
||||||
|
/** 主表与子表是否一对多 */
|
||||||
|
subJoinMany?: boolean;
|
||||||
|
/** 树表的父字段编号 */
|
||||||
|
treeParentColumnId?: number;
|
||||||
|
/** 树表的名字字段编号 */
|
||||||
|
treeNameColumnId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库的表定义 Response VO
|
||||||
|
*/
|
||||||
|
export interface DatabaseTableRespVO {
|
||||||
|
/** 表名称 */
|
||||||
|
name: string;
|
||||||
|
/** 表描述 */
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于数据库的表结构,创建代码生成器的表和字段定义 Request VO
|
||||||
|
*/
|
||||||
|
export interface CodegenCreateListReqVO {
|
||||||
|
/** 数据源配置的编号 */
|
||||||
|
dataSourceConfigId: number;
|
||||||
|
/** 表名数组 */
|
||||||
|
tableNames: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成表和字段的明细 Response VO
|
||||||
|
*/
|
||||||
|
export interface CodegenDetailRespVO {
|
||||||
|
/** 表定义 */
|
||||||
|
table: CodegenTableRespVO;
|
||||||
|
/** 字段定义 */
|
||||||
|
columns: CodegenColumnRespVO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成预览 Response VO
|
||||||
|
*/
|
||||||
|
export interface CodegenPreviewRespVO {
|
||||||
|
/** 文件路径 */
|
||||||
|
filePath: string;
|
||||||
|
/** 代码 */
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成表和字段的修改 Request VO
|
||||||
|
*/
|
||||||
|
export interface CodegenUpdateReqVO {
|
||||||
|
/** 表定义 */
|
||||||
|
table: CodegenTableSaveReqVO;
|
||||||
|
/** 字段定义 */
|
||||||
|
columns: CodegenColumnSaveReqVO[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询列表代码生成表定义
|
||||||
|
export const getCodegenTableList = (dataSourceConfigId: number) => {
|
||||||
|
return requestClient.get<CodegenApi.CodegenTableRespVO[]>(
|
||||||
|
`/infra/codegen/table/list?dataSourceConfigId=${dataSourceConfigId}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查询列表代码生成表定义
|
||||||
|
export const getCodegenTablePage = (params: PageParam) => {
|
||||||
|
return requestClient.get<CodegenApi.CodegenTableRespVO[]>(
|
||||||
|
'/infra/codegen/table/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查询详情代码生成表定义
|
||||||
|
export const getCodegenTable = (id: number) => {
|
||||||
|
return requestClient.get<CodegenApi.CodegenDetailRespVO>(
|
||||||
|
`/infra/codegen/detail?tableId=${id}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 新增代码生成表定义
|
||||||
|
export const createCodegenTable = (data: CodegenApi.CodegenCreateListReqVO) => {
|
||||||
|
return requestClient.post('/infra/codegen/create', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改代码生成表定义
|
||||||
|
export const updateCodegenTable = (data: CodegenApi.CodegenUpdateReqVO) => {
|
||||||
|
return requestClient.put('/infra/codegen/update', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 基于数据库的表结构,同步数据库的表和字段定义
|
||||||
|
export const syncCodegenFromDB = (id: number) => {
|
||||||
|
return requestClient.put(`/infra/codegen/sync-from-db?tableId=${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 预览生成代码
|
||||||
|
export const previewCodegen = (id: number) => {
|
||||||
|
return requestClient.get(`/infra/codegen/preview?tableId=${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下载生成代码
|
||||||
|
export const downloadCodegen = (id: number) => {
|
||||||
|
return requestClient.download(`/infra/codegen/download?tableId=${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获得表定义
|
||||||
|
export const getSchemaTableList = (params: {
|
||||||
|
comment?: string;
|
||||||
|
dataSourceConfigId: number;
|
||||||
|
name?: string;
|
||||||
|
}) => {
|
||||||
|
return requestClient.get<CodegenApi.DatabaseTableRespVO[]>(
|
||||||
|
'/infra/codegen/db/table/list',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 基于数据库的表结构,创建代码生成器的表定义
|
||||||
|
export const createCodegenList = (data: CodegenApi.CodegenCreateListReqVO) => {
|
||||||
|
return requestClient.post('/infra/codegen/create-list', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除代码生成表定义
|
||||||
|
export const deleteCodegenTable = (id: number) => {
|
||||||
|
return requestClient.delete(`/infra/codegen/delete?tableId=${id}`);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace DataSourceConfigApi {
|
||||||
|
export interface DataSourceConfigRespVO {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
username: string;
|
||||||
|
createTime?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DataSourceConfigSaveReqVO {
|
||||||
|
id?: number;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 新增数据源配置
|
||||||
|
export const createDataSourceConfig = (
|
||||||
|
data: DataSourceConfigApi.DataSourceConfigSaveReqVO,
|
||||||
|
) => {
|
||||||
|
return requestClient.post('/infra/data-source-config/create', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改数据源配置
|
||||||
|
export const updateDataSourceConfig = (
|
||||||
|
data: DataSourceConfigApi.DataSourceConfigSaveReqVO,
|
||||||
|
) => {
|
||||||
|
return requestClient.put('/infra/data-source-config/update', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除数据源配置
|
||||||
|
export const deleteDataSourceConfig = (id: number) => {
|
||||||
|
return requestClient.delete(`/infra/data-source-config/delete?id=${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查询数据源配置详情
|
||||||
|
export const getDataSourceConfig = (id: number) => {
|
||||||
|
return requestClient.get<DataSourceConfigApi.DataSourceConfigRespVO>(
|
||||||
|
`/infra/data-source-config/get?id=${id}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查询数据源配置列表
|
||||||
|
export const getDataSourceConfigList = () => {
|
||||||
|
return requestClient.get<DataSourceConfigApi.DataSourceConfigRespVO[]>(
|
||||||
|
'/infra/data-source-config/list',
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -10,19 +10,15 @@ import {
|
||||||
errorMessageResponseInterceptor,
|
errorMessageResponseInterceptor,
|
||||||
RequestClient,
|
RequestClient,
|
||||||
} from '@vben/request';
|
} from '@vben/request';
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore, useTenantStore } from '@vben/stores';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
import { getTenantId } from '#/utils';
|
|
||||||
|
|
||||||
import { refreshTokenApi } from './core';
|
import { refreshTokenApi } from './core';
|
||||||
|
|
||||||
const { apiURL, tenantEnable } = useAppConfig(
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
import.meta.env,
|
|
||||||
import.meta.env.PROD,
|
|
||||||
);
|
|
||||||
|
|
||||||
function createRequestClient(baseURL: string) {
|
function createRequestClient(baseURL: string) {
|
||||||
const client = new RequestClient({
|
const client = new RequestClient({
|
||||||
|
|
@ -52,9 +48,12 @@ function createRequestClient(baseURL: string) {
|
||||||
*/
|
*/
|
||||||
async function doRefreshToken() {
|
async function doRefreshToken() {
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const resp = await refreshTokenApi();
|
const resp = await refreshTokenApi(accessStore.refreshToken ?? '');
|
||||||
const newToken = resp.refreshToken;
|
const newToken = resp.accessToken;
|
||||||
|
const newRefreshToken = resp.refreshToken;
|
||||||
|
|
||||||
accessStore.setAccessToken(newToken);
|
accessStore.setAccessToken(newToken);
|
||||||
|
accessStore.setRefreshToken(newRefreshToken);
|
||||||
return newToken;
|
return newToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,11 +65,11 @@ function createRequestClient(baseURL: string) {
|
||||||
client.addRequestInterceptor({
|
client.addRequestInterceptor({
|
||||||
fulfilled: async (config) => {
|
fulfilled: async (config) => {
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const tenantId = getTenantId();
|
const tenantStore = useTenantStore();
|
||||||
|
|
||||||
config.headers.Authorization = formatToken(accessStore.accessToken);
|
config.headers.Authorization = formatToken(accessStore.accessToken);
|
||||||
config.headers['Accept-Language'] = preferences.app.locale;
|
config.headers['Accept-Language'] = preferences.app.locale;
|
||||||
config.headers['tenant-id'] =
|
config.headers['tenant-id'] = tenantStore.tenantId ?? undefined;
|
||||||
tenantEnable && tenantId ? tenantId : undefined;
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -78,32 +77,13 @@ function createRequestClient(baseURL: string) {
|
||||||
// response数据解构
|
// response数据解构
|
||||||
client.addResponseInterceptor<HttpResponse>({
|
client.addResponseInterceptor<HttpResponse>({
|
||||||
fulfilled: (response) => {
|
fulfilled: (response) => {
|
||||||
// const { config, data: responseData, status, request } = response;
|
const { data: responseData, status } = response;
|
||||||
const { data: responseData, request } = response;
|
|
||||||
// 这个判断的目的是:excel 导出等情况下,系统执行异常,此时返回的是 json,而不是二进制数据
|
|
||||||
if (
|
|
||||||
(request.responseType === 'blob' ||
|
|
||||||
request.responseType === 'arraybuffer') &&
|
|
||||||
responseData?.code === undefined
|
|
||||||
) {
|
|
||||||
return responseData;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { code, data: result } = responseData;
|
const { code, data } = responseData;
|
||||||
if (responseData && Reflect.has(responseData, 'code') && code === 0) {
|
if (status >= 200 && status < 400 && code === 0) {
|
||||||
return result;
|
return data;
|
||||||
}
|
|
||||||
|
|
||||||
switch (code) {
|
|
||||||
case 401: {
|
|
||||||
response.status = 401;
|
|
||||||
throw Object.assign({}, response, { response });
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
response.status = code;
|
|
||||||
throw Object.assign({}, response, { response });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
throw Object.assign({}, response, { response });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -124,7 +104,8 @@ function createRequestClient(baseURL: string) {
|
||||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||||
// 当前mock接口返回的错误字段是 error 或者 message
|
// 当前mock接口返回的错误字段是 error 或者 message
|
||||||
const responseData = error?.response?.data ?? {};
|
const responseData = error?.response?.data ?? {};
|
||||||
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
const errorMessage =
|
||||||
|
responseData?.error ?? responseData?.message ?? responseData.msg ?? '';
|
||||||
// 如果没有错误信息,则会根据状态码进行提示
|
// 如果没有错误信息,则会根据状态码进行提示
|
||||||
message.error(errorMessage || msg);
|
message.error(errorMessage || msg);
|
||||||
}),
|
}),
|
||||||
|
|
@ -133,11 +114,8 @@ function createRequestClient(baseURL: string) {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PageParam = {
|
|
||||||
pageNo?: number;
|
|
||||||
pageSize?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const requestClient = createRequestClient(apiURL);
|
export const requestClient = createRequestClient(apiURL);
|
||||||
|
|
||||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||||
|
|
||||||
|
export type * from '@vben/request';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { type PageParam, requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace DictDataApi {
|
||||||
|
/**
|
||||||
|
* 字典数据信息 Response VO
|
||||||
|
*/
|
||||||
|
export type DictDataRespVO = {
|
||||||
|
colorType?: string;
|
||||||
|
createTime?: Date;
|
||||||
|
cssClass?: string;
|
||||||
|
dictType: string;
|
||||||
|
id?: number;
|
||||||
|
label: string;
|
||||||
|
remark?: string;
|
||||||
|
sort?: number;
|
||||||
|
status?: number;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型分页列表 Request VO
|
||||||
|
*/
|
||||||
|
export interface DictDataPageReqVO extends PageParam {
|
||||||
|
dictType?: string;
|
||||||
|
label?: string;
|
||||||
|
status?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典数据创建/修改 Request VO
|
||||||
|
*/
|
||||||
|
export interface DictDataSaveReqVO {
|
||||||
|
colorType?: string;
|
||||||
|
cssClass?: string;
|
||||||
|
dictType: string;
|
||||||
|
id?: number;
|
||||||
|
label: string;
|
||||||
|
remark?: string;
|
||||||
|
sort?: number;
|
||||||
|
status?: number;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典数据(精简) Response VO
|
||||||
|
*/
|
||||||
|
export interface DictDataSimpleRespVO {
|
||||||
|
colorType?: string;
|
||||||
|
cssClass?: string;
|
||||||
|
dictType: string;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询字典数据(精简)列表
|
||||||
|
export const getSimpleDictDataList = () => {
|
||||||
|
return requestClient.get('/system/dict-data/simple-list');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查询字典数据列表
|
||||||
|
export const getDictDataPage = (params: PageParam) => {
|
||||||
|
return requestClient.get('/system/dict-data/page', { params });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查询字典数据详情
|
||||||
|
export const getDictData = (id: number) => {
|
||||||
|
return requestClient.get(`/system/dict-data/get?id=${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 新增字典数据
|
||||||
|
export const createDictData = (data: DictDataApi.DictDataSaveReqVO) => {
|
||||||
|
return requestClient.post('/system/dict-data/create', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改字典数据
|
||||||
|
export const updateDictData = (data: DictDataApi.DictDataSaveReqVO) => {
|
||||||
|
return requestClient.put('/system/dict-data/update', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除字典数据
|
||||||
|
export const deleteDictData = (id: number) => {
|
||||||
|
return requestClient.delete(`/system/dict-data/delete?id=${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出字典类型数据
|
||||||
|
export const exportDictData = (params: DictDataApi.DictDataPageReqVO) => {
|
||||||
|
return requestClient.download('/system/dict-data/export', { params });
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export namespace DictTypeApi {}
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
import { requestClient } from '#/api/request';
|
|
||||||
|
|
||||||
export type DictDataVO = {
|
|
||||||
colorType: string;
|
|
||||||
createTime: Date;
|
|
||||||
cssClass: string;
|
|
||||||
dictType: string;
|
|
||||||
id: number | undefined;
|
|
||||||
label: string;
|
|
||||||
remark: string;
|
|
||||||
sort: number | undefined;
|
|
||||||
status: number;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 查询字典数据(精简)列表
|
|
||||||
export function getSimpleDictDataList() {
|
|
||||||
return requestClient.get('/system/dict-data/simple-list');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询字典数据列表
|
|
||||||
export function getDictDataPage(params: any) {
|
|
||||||
return requestClient.get('/system/dict-data/page', params);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询字典数据详情
|
|
||||||
export function getDictData(id: number) {
|
|
||||||
return requestClient.get(`/system/dict-data/get?id=${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增字典数据
|
|
||||||
export function createDictData(data: DictDataVO) {
|
|
||||||
return requestClient.post('/system/dict-data/create', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改字典数据
|
|
||||||
export function updateDictData(data: DictDataVO) {
|
|
||||||
return requestClient.put('/system/dict-data/update', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除字典数据
|
|
||||||
export function deleteDictData(id: number) {
|
|
||||||
return requestClient.delete(`/system/dict-data/delete?id=${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出字典类型数据
|
|
||||||
export function exportDictData(params: any) {
|
|
||||||
return requestClient.download('/system/dict-data/export', params);
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import { requestClient } from '#/api/request';
|
|
||||||
|
|
||||||
export type DictTypeVO = {
|
|
||||||
createTime: Date;
|
|
||||||
id: number | undefined;
|
|
||||||
name: string;
|
|
||||||
remark: string;
|
|
||||||
status: number;
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 查询字典(精简)列表
|
|
||||||
export function getSimpleDictTypeList() {
|
|
||||||
return requestClient.get('/system/dict-type/list-all-simple');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询字典列表
|
|
||||||
export function getDictTypePage(params: any) {
|
|
||||||
return requestClient.get('/system/dict-type/page', params);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询字典详情
|
|
||||||
export function getDictType(id: number) {
|
|
||||||
return requestClient.get(`/system/dict-type/get?id=${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增字典
|
|
||||||
export function createDictType(data: DictTypeVO) {
|
|
||||||
return requestClient.post('/system/dict-type/create', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改字典
|
|
||||||
export function updateDictType(data: DictTypeVO) {
|
|
||||||
return requestClient.put('/system/dict-type/update', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除字典
|
|
||||||
export function deleteDictType(id: number) {
|
|
||||||
return requestClient.delete(`/system/dict-type/delete?id=${id}`);
|
|
||||||
}
|
|
||||||
// 导出字典类型
|
|
||||||
export function exportDictType(params: any) {
|
|
||||||
return requestClient.download('/system/dict-type/export', params);
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { default as Verify } from './src/Verify.vue';
|
|
||||||
|
|
@ -1,162 +0,0 @@
|
||||||
<script type="text/babel">
|
|
||||||
/**
|
|
||||||
* Verify 验证码组件
|
|
||||||
* @description 分发验证码使用
|
|
||||||
*/
|
|
||||||
import { computed, ref, toRefs, watchEffect } from 'vue';
|
|
||||||
|
|
||||||
// import { $t } from '@vben/locales';
|
|
||||||
|
|
||||||
import VerifyPoints from './Verify/VerifyPoints.vue';
|
|
||||||
import VerifySlide from './Verify/VerifySlide.vue';
|
|
||||||
|
|
||||||
import './style/verify.css';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Vue3Verify',
|
|
||||||
components: {
|
|
||||||
VerifyPoints,
|
|
||||||
VerifySlide,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
arith: {
|
|
||||||
default: 0,
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
barSize: {
|
|
||||||
default: () => {
|
|
||||||
return {
|
|
||||||
height: '40px',
|
|
||||||
width: '310px',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
blockSize: {
|
|
||||||
default() {
|
|
||||||
return {
|
|
||||||
height: '50px',
|
|
||||||
width: '50px',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
captchaType: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
explain: {
|
|
||||||
default: '',
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
figure: {
|
|
||||||
default: 0,
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
imgSize: {
|
|
||||||
default() {
|
|
||||||
return {
|
|
||||||
height: '155px',
|
|
||||||
width: '310px',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
default: 'pop',
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
vSpace: {
|
|
||||||
default: 5,
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { captchaType, mode } = toRefs(props);
|
|
||||||
const clickShow = ref(false);
|
|
||||||
const verifyType = ref(undefined);
|
|
||||||
const componentType = ref(undefined);
|
|
||||||
|
|
||||||
const instance = ref({});
|
|
||||||
|
|
||||||
const showBox = computed(() => {
|
|
||||||
return mode.value === 'pop' ? clickShow.value : true;
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* refresh
|
|
||||||
* @description 刷新
|
|
||||||
*/
|
|
||||||
const refresh = () => {
|
|
||||||
if (instance.value.refresh) instance.value.refresh();
|
|
||||||
};
|
|
||||||
const closeBox = () => {
|
|
||||||
clickShow.value = false;
|
|
||||||
refresh();
|
|
||||||
};
|
|
||||||
const show = () => {
|
|
||||||
if (mode.value === 'pop') clickShow.value = true;
|
|
||||||
};
|
|
||||||
watchEffect(() => {
|
|
||||||
switch (captchaType.value) {
|
|
||||||
case 'blockPuzzle': {
|
|
||||||
verifyType.value = '2';
|
|
||||||
componentType.value = 'VerifySlide';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'clickWord': {
|
|
||||||
verifyType.value = '';
|
|
||||||
componentType.value = 'VerifyPoints';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
clickShow,
|
|
||||||
closeBox,
|
|
||||||
componentType,
|
|
||||||
instance,
|
|
||||||
show,
|
|
||||||
showBox,
|
|
||||||
verifyType,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-show="showBox" :class="mode === 'pop' ? 'mask' : ''">
|
|
||||||
<div
|
|
||||||
:class="mode === 'pop' ? 'verifybox' : ''"
|
|
||||||
:style="{ 'max-width': `${parseInt(imgSize.width) + 20}px` }"
|
|
||||||
>
|
|
||||||
<div v-if="mode === 'pop'" class="verifybox-top">
|
|
||||||
请完成安全验证
|
|
||||||
<span class="verifybox-close" @click="closeBox">
|
|
||||||
<i class="iconfont icon-close"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
:style="{ padding: mode === 'pop' ? '10px' : '0' }"
|
|
||||||
class="verifybox-bottom"
|
|
||||||
>
|
|
||||||
<!-- 验证码容器 -->
|
|
||||||
<component
|
|
||||||
:is="componentType"
|
|
||||||
v-if="componentType"
|
|
||||||
ref="instance"
|
|
||||||
:arith="arith"
|
|
||||||
:bar-size="barSize"
|
|
||||||
:block-size="blockSize"
|
|
||||||
:captcha-type="captchaType"
|
|
||||||
:explain="explain"
|
|
||||||
:figure="figure"
|
|
||||||
:img-size="imgSize"
|
|
||||||
:mode="mode"
|
|
||||||
:type="verifyType"
|
|
||||||
:v-space="vSpace"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DescItem } from './types';
|
||||||
|
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
|
import { Descriptions, DescriptionsItem } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { componentMap } from '#/components/view/component-map';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
title: { type: String, default: '' },
|
||||||
|
bordered: { type: Boolean, default: true },
|
||||||
|
size: {
|
||||||
|
type: String as PropType<'default' | 'middle' | 'small'>,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
type: [Number, Object],
|
||||||
|
default: () => {
|
||||||
|
// return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 };
|
||||||
|
return 12;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelStyle: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {
|
||||||
|
width: '120px',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
contentStyle: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {
|
||||||
|
width: '0px',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
type: Array as PropType<DescItem[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
data: { type: Object, default: undefined },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Descriptions
|
||||||
|
:bordered="bordered"
|
||||||
|
:column="column"
|
||||||
|
:content-style="contentStyle"
|
||||||
|
:label-style="labelStyle"
|
||||||
|
:size="size"
|
||||||
|
:title="title ? title : undefined"
|
||||||
|
>
|
||||||
|
<template v-for="item in schema" :key="item.field">
|
||||||
|
<DescriptionsItem :label="item.label" :span="item.span">
|
||||||
|
<component
|
||||||
|
:is="(componentMap as Map<String, any>).get(item.component)"
|
||||||
|
v-if="(componentMap as Map<String, any>).has(item.component)"
|
||||||
|
:value="data?.[item.field]"
|
||||||
|
v-bind="{ ...item.componentProps }"
|
||||||
|
/>
|
||||||
|
<component
|
||||||
|
:is="item.render(data?.[item.field], data)"
|
||||||
|
v-else-if="
|
||||||
|
!(componentMap as Map<String, any>).has(item.component) &&
|
||||||
|
item.render
|
||||||
|
"
|
||||||
|
:value="data?.[item.field]"
|
||||||
|
v-bind="{ ...item.componentProps }"
|
||||||
|
/>
|
||||||
|
<template v-else>{{ data?.[item.field] }}</template>
|
||||||
|
</DescriptionsItem>
|
||||||
|
</template>
|
||||||
|
</Descriptions>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as Description } from './description.vue';
|
||||||
|
export type * from './types';
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import type { CollapseContainerOptions } from '@/components/Container';
|
||||||
|
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions';
|
||||||
|
|
||||||
|
import type { CSSProperties, VNode } from 'vue';
|
||||||
|
|
||||||
|
export interface DescItem {
|
||||||
|
labelMinWidth?: number;
|
||||||
|
contentMinWidth?: number;
|
||||||
|
labelStyle?: CSSProperties;
|
||||||
|
field: string;
|
||||||
|
label: JSX.Element | string | VNode;
|
||||||
|
// Merge column
|
||||||
|
span?: number;
|
||||||
|
show?: (...arg: any) => boolean;
|
||||||
|
// render
|
||||||
|
render?: (
|
||||||
|
val: any,
|
||||||
|
data: Recordable,
|
||||||
|
) => Element | JSX.Element | number | string | undefined | VNode;
|
||||||
|
component: string;
|
||||||
|
componentProps?: any;
|
||||||
|
children?: DescItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DescriptionProps extends DescriptionsProps {
|
||||||
|
// Whether to include the collapse component
|
||||||
|
useCollapse?: boolean;
|
||||||
|
/**
|
||||||
|
* item configuration
|
||||||
|
* @type DescItem
|
||||||
|
*/
|
||||||
|
schema: DescItem[];
|
||||||
|
/**
|
||||||
|
* 数据
|
||||||
|
* @type object
|
||||||
|
*/
|
||||||
|
data: Recordable;
|
||||||
|
/**
|
||||||
|
* Built-in CollapseContainer component configuration
|
||||||
|
* @type CollapseContainerOptions
|
||||||
|
*/
|
||||||
|
collapseOptions?: CollapseContainerOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DescInstance {
|
||||||
|
setDescProps(descProps: Partial<DescriptionProps>): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Register = (descInstance: DescInstance) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description:
|
||||||
|
*/
|
||||||
|
export type UseDescReturnType = [Register, DescInstance];
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import type { CustomComponentType } from './types';
|
||||||
|
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
import { kebabToCamelCase } from '@vben/utils';
|
||||||
|
|
||||||
|
const componentMap = new Map<CustomComponentType | string, Component>();
|
||||||
|
// import.meta.glob() 直接引入所有的模块 Vite 独有的功能
|
||||||
|
const modules = import.meta.glob('./components/**/*.vue', { eager: true });
|
||||||
|
// 加入到路由集合中
|
||||||
|
Object.keys(modules).forEach((key) => {
|
||||||
|
if (!key.includes('-ignore')) {
|
||||||
|
const mod = (modules as any)[key].default || {};
|
||||||
|
// ./components/ApiDict.vue
|
||||||
|
// 获取ApiDict
|
||||||
|
const compName = key.replace('./components/', '').replace('.vue', '');
|
||||||
|
componentMap.set(kebabToCamelCase(compName), mod);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function add(compName: string, component: Component) {
|
||||||
|
componentMap.set(compName, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function del(compName: string) {
|
||||||
|
componentMap.delete(compName);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 注册组件
|
||||||
|
* @param components
|
||||||
|
*/
|
||||||
|
export const registerComponent = (components: any) => {
|
||||||
|
componentMap.forEach((value, key) => {
|
||||||
|
components[key] = value as Component;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export { componentMap };
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { CheckboxValueType } from 'ant-design-vue/es/checkbox/interface';
|
||||||
|
|
||||||
|
import { computed, type PropType, ref, watch, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import { getNestedValue, isFunction } from '@vben/utils';
|
||||||
|
|
||||||
|
import { objectOmit, useVModel } from '@vueuse/core';
|
||||||
|
import { CheckboxGroup, Spin } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
type OptionsItem = { disabled?: boolean; label: string; value: string };
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [Array] as PropType<CheckboxValueType[]>,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
numberToString: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
(arg?: any) => Promise<OptionsItem[]> | String
|
||||||
|
>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
// api params
|
||||||
|
params: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
// 请求方法
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
// support xxx.xxx.xx
|
||||||
|
resultField: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'label',
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value',
|
||||||
|
},
|
||||||
|
immediate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['update:value', 'optionsChange']);
|
||||||
|
const mValue = useVModel(props, 'value', emits, {
|
||||||
|
defaultValue: props.value,
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
const options = ref<OptionsItem[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const isFirstLoad = ref(true);
|
||||||
|
const getOptions = computed(() => {
|
||||||
|
const { labelField, valueField, numberToString } = props;
|
||||||
|
const res: OptionsItem[] = [];
|
||||||
|
options.value.forEach((item: any) => {
|
||||||
|
const value = item[valueField];
|
||||||
|
res.push({
|
||||||
|
...objectOmit(item, [labelField, valueField]),
|
||||||
|
label: item[labelField],
|
||||||
|
value: numberToString ? `${value}` : value,
|
||||||
|
disabled: item.disabled || false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const api: any =
|
||||||
|
typeof props.api === 'string' && props.api
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: props.api;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get' ? { params: props.params } : props.params;
|
||||||
|
const res = await api(params);
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
options.value = res;
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
} else {
|
||||||
|
options.value = props.resultField
|
||||||
|
? getNestedValue(res, props.resultField)
|
||||||
|
: [];
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
watchEffect(() => {
|
||||||
|
props.immediate && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!isFirstLoad.value && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Spin :spinning="loading" style="margin-left: 20px">
|
||||||
|
<CheckboxGroup
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model:value="mValue"
|
||||||
|
:options="getOptions"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||||
|
<slot :name="item" v-bind="data || {}"></slot>
|
||||||
|
</template>
|
||||||
|
</CheckboxGroup>
|
||||||
|
</Spin>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, type PropType } from 'vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
import { ApiCheckboxGroup, ApiRadioGroup, ApiSelect } from '..';
|
||||||
|
|
||||||
|
type OptionsItem = { disabled?: boolean; label: string; value: string };
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
renderType: {
|
||||||
|
type: String as PropType<'CheckboxGroup' | 'RadioGroup' | 'Select'>,
|
||||||
|
default: 'Select',
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
(arg?: any) => Promise<OptionsItem[]> | String
|
||||||
|
>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
// 请求方法
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
// api params
|
||||||
|
params: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const DictComponent = computed(() => {
|
||||||
|
if (props.renderType === 'RadioGroup') {
|
||||||
|
return ApiRadioGroup;
|
||||||
|
} else if (props.renderType === 'CheckboxGroup') {
|
||||||
|
return ApiCheckboxGroup;
|
||||||
|
}
|
||||||
|
return ApiSelect;
|
||||||
|
});
|
||||||
|
const fetch = () => {
|
||||||
|
return requestClient.post('/sys/dict/getByDictType', {
|
||||||
|
dictType: props.code,
|
||||||
|
...props.params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<DictComponent :api="props.code ? fetch : props.api" />
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectValue } from 'ant-design-vue/es/select';
|
||||||
|
|
||||||
|
import { computed, type PropType, ref, watch, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import { getNestedValue, isFunction } from '@vben/utils';
|
||||||
|
|
||||||
|
import { objectOmit, useVModel } from '@vueuse/core';
|
||||||
|
import { RadioGroup, Spin } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
type OptionsItem = { disabled?: boolean; label: string; value: string };
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Array] as PropType<SelectValue>,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
numberToString: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
(arg?: any) => Promise<OptionsItem[]> | String
|
||||||
|
>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
// 请求方法
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
// api params
|
||||||
|
params: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
// support xxx.xxx.xx
|
||||||
|
resultField: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'label',
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value',
|
||||||
|
},
|
||||||
|
immediate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
isBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['update:value', 'optionsChange']);
|
||||||
|
const mValue = useVModel(props, 'value', emits, {
|
||||||
|
defaultValue: props.value,
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
const options = ref<OptionsItem[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const isFirstLoad = ref(true);
|
||||||
|
const getOptions = computed(() => {
|
||||||
|
const { labelField, valueField, numberToString } = props;
|
||||||
|
const res: OptionsItem[] = [];
|
||||||
|
options.value.forEach((item: any) => {
|
||||||
|
const value = item[valueField];
|
||||||
|
res.push({
|
||||||
|
...objectOmit(item, [labelField, valueField]),
|
||||||
|
label: item[labelField],
|
||||||
|
value: numberToString ? `${value}` : value,
|
||||||
|
disabled: item.disabled || false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const api: any =
|
||||||
|
typeof props.api === 'string' && props.api
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: props.api;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get' ? { params: props.params } : props.params;
|
||||||
|
const res = await api(params);
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
options.value = res;
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
} else {
|
||||||
|
options.value = props.resultField
|
||||||
|
? getNestedValue(res, props.resultField)
|
||||||
|
: [];
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
watchEffect(() => {
|
||||||
|
props.immediate && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!isFirstLoad.value && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Spin :spinning="loading" style="margin-left: 20px">
|
||||||
|
<RadioGroup
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model:value="mValue"
|
||||||
|
:button-style="isBtn ? 'solid' : 'outline'"
|
||||||
|
:option-type="isBtn ? 'button' : 'default'"
|
||||||
|
:options="getOptions"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||||
|
<slot :name="item" v-bind="data || {}"></slot>
|
||||||
|
</template>
|
||||||
|
</RadioGroup>
|
||||||
|
</Spin>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectValue } from 'ant-design-vue/es/select';
|
||||||
|
|
||||||
|
import { computed, type PropType, ref, watch, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
import { getNestedValue, isFunction } from '@vben/utils';
|
||||||
|
|
||||||
|
import { objectOmit, useVModel } from '@vueuse/core';
|
||||||
|
import { Select } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
type OptionsItem = { disabled?: boolean; label: string; value: string };
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Array] as PropType<SelectValue>,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
numberToString: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
(arg?: any) => Promise<OptionsItem[]> | String
|
||||||
|
>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
// api params
|
||||||
|
params: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
// support xxx.xxx.xx
|
||||||
|
resultField: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'label',
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value',
|
||||||
|
},
|
||||||
|
immediate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['update:value', 'optionsChange']);
|
||||||
|
const mValue = useVModel(props, 'value', emits, {
|
||||||
|
defaultValue: props.value,
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
const options = ref<OptionsItem[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const isFirstLoad = ref(true);
|
||||||
|
const getOptions = computed(() => {
|
||||||
|
const { labelField, valueField, numberToString } = props;
|
||||||
|
const res: OptionsItem[] = [];
|
||||||
|
options.value.forEach((item: any) => {
|
||||||
|
const value = item[valueField];
|
||||||
|
res.push({
|
||||||
|
...objectOmit(item, [labelField, valueField]),
|
||||||
|
label: item[labelField],
|
||||||
|
value: numberToString ? `${value}` : value,
|
||||||
|
disabled: item.disabled || false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const api: any =
|
||||||
|
typeof props.api === 'string' && props.api
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: props.api;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get' ? { params: props.params } : props.params;
|
||||||
|
const res = await api(params);
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
options.value = res;
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
} else {
|
||||||
|
options.value = props.resultField
|
||||||
|
? getNestedValue(res, props.resultField)
|
||||||
|
: [];
|
||||||
|
emits('optionsChange', options.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async function handleFetch() {
|
||||||
|
if (!props.immediate && isFirstLoad.value) {
|
||||||
|
await fetch();
|
||||||
|
isFirstLoad.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watchEffect(() => {
|
||||||
|
props.immediate && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!isFirstLoad.value && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Select
|
||||||
|
v-model:value="mValue"
|
||||||
|
:options="getOptions"
|
||||||
|
class="w-full"
|
||||||
|
@dropdown-visible-change="handleFetch"
|
||||||
|
>
|
||||||
|
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||||
|
<slot :name="item" v-bind="data || {}"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-if="loading" #suffixIcon>
|
||||||
|
<IconifyIcon icon="ant-design:loading-outlined" spin />
|
||||||
|
</template>
|
||||||
|
<template v-if="loading" #notFoundContent>
|
||||||
|
<span>
|
||||||
|
<IconifyIcon icon="ant-design:loading-outlined" spin />
|
||||||
|
请等待数据加载完成
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Select>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectValue } from 'ant-design-vue/es/select';
|
||||||
|
|
||||||
|
import { computed, type PropType, ref, watch, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
import { getNestedValue, isFunction } from '@vben/utils';
|
||||||
|
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
import { TreeSelect } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Array] as PropType<SelectValue>,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
type: [Function, String] as PropType<(arg?: any) => Promise<any> | String>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
// 请求方法
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
// api params
|
||||||
|
params: {
|
||||||
|
type: Object as PropType<any>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
// support xxx.xxx.xx
|
||||||
|
resultField: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'title',
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value',
|
||||||
|
},
|
||||||
|
childrenField: {
|
||||||
|
type: String,
|
||||||
|
default: 'children',
|
||||||
|
},
|
||||||
|
immediate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['update:value', 'treeDataChange']);
|
||||||
|
const mValue = useVModel(props, 'value', emits, {
|
||||||
|
defaultValue: props.value,
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
const treeData = ref<any>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const isFirstLoad = ref(true);
|
||||||
|
const fieldNames = computed(() => {
|
||||||
|
return {
|
||||||
|
label: props.labelField,
|
||||||
|
value: props.valueField,
|
||||||
|
children: props.childrenField,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const getTreeData = computed(() => {
|
||||||
|
return treeData.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const api: any =
|
||||||
|
typeof props.api === 'string' && props.api
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: props.api;
|
||||||
|
if (!api || !isFunction(api)) return;
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get' ? { params: props.params } : props.params;
|
||||||
|
const res = await api(params);
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
treeData.value = res;
|
||||||
|
emits('treeDataChange', treeData.value);
|
||||||
|
} else {
|
||||||
|
treeData.value = props.resultField
|
||||||
|
? getNestedValue(res, props.resultField)
|
||||||
|
: [];
|
||||||
|
emits('treeDataChange', treeData.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async function handleFetch() {
|
||||||
|
if (!props.immediate && isFirstLoad.value) {
|
||||||
|
await fetch();
|
||||||
|
isFirstLoad.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watchEffect(() => {
|
||||||
|
props.immediate && fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.params,
|
||||||
|
() => {
|
||||||
|
!isFirstLoad.value && fetch();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TreeSelect
|
||||||
|
v-model:value="mValue"
|
||||||
|
:field-names="fieldNames"
|
||||||
|
:tree-data="getTreeData"
|
||||||
|
:tree-node-filter-prop="labelField"
|
||||||
|
class="w-full"
|
||||||
|
@dropdown-visible-change="handleFetch"
|
||||||
|
>
|
||||||
|
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||||
|
<slot :name="item" v-bind="data || {}"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-if="loading" #suffixIcon>
|
||||||
|
<IconifyIcon icon="ant-design:loading-outlined" spin />
|
||||||
|
</template>
|
||||||
|
</TreeSelect>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export { default as ApiCheckboxGroup } from './components/api-checkbox-group.vue';
|
||||||
|
export { default as ApiDict } from './components/api-dict.vue';
|
||||||
|
export { default as ApiRadioGroup } from './components/api-radio-group.vue';
|
||||||
|
export { default as ApiSelect } from './components/api-select.vue';
|
||||||
|
export { default as ApiTreeSelect } from './components/api-tree-select.vue';
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export type CustomComponentType =
|
||||||
|
| 'ApiCheckboxGroup'
|
||||||
|
| 'ApiDict'
|
||||||
|
| 'ApiRadioGroup'
|
||||||
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect';
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
import { toPascalCase } from '#/util/tool';
|
||||||
|
|
||||||
|
const componentMap = new Map<string, Component>();
|
||||||
|
// import.meta.glob() 直接引入所有的模块 Vite 独有的功能
|
||||||
|
const modules = import.meta.glob('./components/**/*.vue', { eager: true });
|
||||||
|
// 加入到路由集合中
|
||||||
|
Object.keys(modules).forEach((key) => {
|
||||||
|
if (!key.includes('-ignore')) {
|
||||||
|
const mod = (modules as any)[key].default || {};
|
||||||
|
// ./components/ApiDict.vue
|
||||||
|
// 获取ApiDict
|
||||||
|
const compName = key.replace('./components/', '').replace('.vue', '');
|
||||||
|
componentMap.set(toPascalCase(compName), mod);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function add(compName: string, component: Component) {
|
||||||
|
componentMap.set(compName, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function del(compName: string) {
|
||||||
|
componentMap.delete(compName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { componentMap };
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ApiSelect from './api-select.vue';
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ApiSelect />
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { type DictItem, useDictStore } from '@vben/stores';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
code: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
// 值
|
||||||
|
type: [String, Number, Array],
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
split: {
|
||||||
|
// 分割符
|
||||||
|
type: String,
|
||||||
|
default: ',',
|
||||||
|
},
|
||||||
|
join: {
|
||||||
|
// 连接符
|
||||||
|
type: String,
|
||||||
|
default: ',',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
const cValue = computed(() => {
|
||||||
|
if (!props.value && props.value !== 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const arr: Array<any> = [];
|
||||||
|
if (Array.isArray(props.value)) {
|
||||||
|
arr.push(...props.value);
|
||||||
|
} else {
|
||||||
|
arr.push(...props.value.toString().split(props.split));
|
||||||
|
}
|
||||||
|
const dictData = dictStore.getDictData(props.code as string) as DictItem[];
|
||||||
|
const res: Array<any> = [];
|
||||||
|
arr.forEach((item) => {
|
||||||
|
for (let i = 0; i < dictData.length; i++) {
|
||||||
|
if (dictData[i]?.value?.toString() === item?.toString()) {
|
||||||
|
res.push(dictData[i]?.label);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i === dictData.length - 1) {
|
||||||
|
res.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.join(props.join);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>{{ cValue }}</div>
|
||||||
|
</template>
|
||||||
|
<style lang="less" scoped></style>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ApiSelect from './api-select.vue';
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ApiSelect />
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, type PropType, watch } from 'vue';
|
||||||
|
|
||||||
|
import { type DictItem, useDictStore } from '@vben/stores';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
code: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
// 值
|
||||||
|
type: [String, Number, Array],
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
split: {
|
||||||
|
// 分割符
|
||||||
|
type: String,
|
||||||
|
default: ',',
|
||||||
|
},
|
||||||
|
join: {
|
||||||
|
// 连接符
|
||||||
|
type: String,
|
||||||
|
default: ',',
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
// 接口请求对象
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
((...arg: any) => Promise<any>) | String
|
||||||
|
>,
|
||||||
|
default() {
|
||||||
|
return () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolve([]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cacheKey: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
/**
|
||||||
|
* 获取包含的id
|
||||||
|
*/
|
||||||
|
const getIncludeIds = () => {
|
||||||
|
if (!props.value && props.value !== 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const arr: Array<any> = [];
|
||||||
|
if (Array.isArray(props.value)) {
|
||||||
|
arr.push(...props.value);
|
||||||
|
} else {
|
||||||
|
arr.push(...props.value.toString().split(props.split));
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取缓存key
|
||||||
|
*/
|
||||||
|
const getCacheKey = () => {
|
||||||
|
let cacheKey = props.cacheKey;
|
||||||
|
if (typeof props.api === 'string' && !cacheKey) {
|
||||||
|
cacheKey = props.api as string;
|
||||||
|
}
|
||||||
|
return cacheKey;
|
||||||
|
};
|
||||||
|
const cValue = computed(() => {
|
||||||
|
if (!props.value && props.value !== 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const arr: Array<any> = getIncludeIds();
|
||||||
|
const cacheKey = getCacheKey();
|
||||||
|
const dictData = dictStore.getDictData(cacheKey) as DictItem[];
|
||||||
|
const res: Array<any> = [];
|
||||||
|
arr.forEach((item) => {
|
||||||
|
for (let i = 0; i < dictData.length; i++) {
|
||||||
|
if (dictData[i]?.value?.toString() === item?.toString()) {
|
||||||
|
res.push(dictData[i]?.label);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i === dictData.length - 1) {
|
||||||
|
res.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.join(props.join);
|
||||||
|
});
|
||||||
|
const requestData = () => {
|
||||||
|
const api: (...arg: any) => Promise<any> =
|
||||||
|
typeof props.api === 'string'
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: (props.api as (...arg: any) => Promise<any>);
|
||||||
|
const cacheKey = getCacheKey();
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get'
|
||||||
|
? {
|
||||||
|
params: {
|
||||||
|
...props.params,
|
||||||
|
dictType: cacheKey,
|
||||||
|
includeType: 2,
|
||||||
|
includeIds: getIncludeIds(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
data: {
|
||||||
|
...props.params,
|
||||||
|
dictType: cacheKey,
|
||||||
|
includeType: 2,
|
||||||
|
includeIds: getIncludeIds(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
dictStore.setDictCacheByApi(api, params);
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
requestData();
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
() => {
|
||||||
|
requestData();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>{{ cValue }}</div>
|
||||||
|
</template>
|
||||||
|
<style lang="less" scoped></style>
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, type PropType, ref } from 'vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
// 值
|
||||||
|
type: [String, Number, Array],
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
// 接口请求对象
|
||||||
|
type: [Function, String] as PropType<
|
||||||
|
((...arg: any) => Promise<any>) | String
|
||||||
|
>,
|
||||||
|
default() {
|
||||||
|
return () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolve([]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cacheKey: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
requestMethod: {
|
||||||
|
type: String,
|
||||||
|
default: 'post',
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'id',
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'name',
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const currentData = ref({});
|
||||||
|
const cValue = computed(() => {
|
||||||
|
return (currentData.value as any)[props.labelField] || props.value;
|
||||||
|
});
|
||||||
|
onMounted(() => {
|
||||||
|
const api: (...arg: any) => Promise<any> =
|
||||||
|
typeof props.api === 'string'
|
||||||
|
? (params: any) => {
|
||||||
|
return (requestClient as any)[props.requestMethod](
|
||||||
|
props.api as any,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: (props.api as (...arg: any) => Promise<any>);
|
||||||
|
const searchType = props.multiple ? 'IN' : 'EQ';
|
||||||
|
const params =
|
||||||
|
props.requestMethod === 'get'
|
||||||
|
? {
|
||||||
|
params: {
|
||||||
|
...props.params,
|
||||||
|
[`m_${searchType}_${props.valueField}`]: props.value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...props.params,
|
||||||
|
[`m_${searchType}_${props.valueField}`]: props.value,
|
||||||
|
};
|
||||||
|
api(params).then((res) => {
|
||||||
|
if (res.length > 0) {
|
||||||
|
currentData.value = res[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>{{ cValue }}</div>
|
||||||
|
</template>
|
||||||
|
<style lang="less" scoped></style>
|
||||||
|
|
@ -101,7 +101,7 @@ const menus = computed(() => [
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const avatar = computed(() => {
|
const avatar = computed(() => {
|
||||||
return userStore.userInfo?.user.avatar ?? preferences.app.defaultAvatar;
|
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
|
|
@ -138,7 +138,7 @@ watch(
|
||||||
<UserDropdown
|
<UserDropdown
|
||||||
:avatar
|
:avatar
|
||||||
:menus
|
:menus
|
||||||
:text="userStore.userInfo?.user.nickname"
|
:text="userStore.userInfo?.nickname"
|
||||||
tag-text="Admin"
|
tag-text="Admin"
|
||||||
@logout="handleLogout"
|
@logout="handleLogout"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||||
export const overridesPreferences = defineOverridesPreferences({
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
// overrides
|
// overrides
|
||||||
app: {
|
app: {
|
||||||
|
name: import.meta.env.VITE_APP_TITLE,
|
||||||
/** 后端路由模式 */
|
/** 后端路由模式 */
|
||||||
accessMode: 'backend',
|
accessMode: 'backend',
|
||||||
name: import.meta.env.VITE_APP_TITLE,
|
|
||||||
enableRefreshToken: true,
|
enableRefreshToken: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
import type {
|
import type {
|
||||||
ComponentRecordType,
|
ComponentRecordType,
|
||||||
GenerateMenuAndRoutesOptions,
|
GenerateMenuAndRoutesOptions,
|
||||||
RouteRecordStringComponent,
|
|
||||||
} from '@vben/types';
|
} from '@vben/types';
|
||||||
|
|
||||||
import { generateAccessible } from '@vben/access';
|
import { generateAccessible } from '@vben/access';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { useUserStore } from '@vben/stores';
|
|
||||||
import { cloneDeep } from '@vben/utils';
|
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getAuthPermissionInfoApi } from '#/api';
|
||||||
import { BasicLayout, IFrameView } from '#/layouts';
|
import { BasicLayout, IFrameView } from '#/layouts';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
|
@ -18,75 +16,6 @@ import { buildMenus } from './helper';
|
||||||
|
|
||||||
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
||||||
|
|
||||||
/**
|
|
||||||
* base路由
|
|
||||||
*/
|
|
||||||
const baseMenus: RouteRecordStringComponent[] = [
|
|
||||||
{
|
|
||||||
component: 'BasicLayout',
|
|
||||||
meta: {
|
|
||||||
order: -1,
|
|
||||||
title: 'page.dashboard.title',
|
|
||||||
},
|
|
||||||
name: 'Dashboard',
|
|
||||||
path: '/',
|
|
||||||
redirect: '/analytics',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Analytics',
|
|
||||||
path: '/analytics',
|
|
||||||
component: '/dashboard/analytics/index',
|
|
||||||
meta: {
|
|
||||||
affixTab: true,
|
|
||||||
icon: 'lucide:area-chart',
|
|
||||||
title: 'page.dashboard.analytics',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Workspace',
|
|
||||||
path: '/workspace',
|
|
||||||
component: '/dashboard/workspace/index',
|
|
||||||
meta: {
|
|
||||||
icon: 'carbon:workspace',
|
|
||||||
title: 'page.dashboard.workspace',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'VbenAbout',
|
|
||||||
path: '/about',
|
|
||||||
component: '/_core/about/index.vue',
|
|
||||||
meta: {
|
|
||||||
icon: 'lucide:copyright',
|
|
||||||
title: 'demos.vben.about',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'BasicLayout',
|
|
||||||
meta: {
|
|
||||||
icon: 'ant-design:user-outlined',
|
|
||||||
order: -1,
|
|
||||||
title: '个人中心',
|
|
||||||
hideInMenu: true,
|
|
||||||
},
|
|
||||||
name: 'profile',
|
|
||||||
path: '/profile',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'UserProfile',
|
|
||||||
path: '/profile/index',
|
|
||||||
component: '/_core/profile/profile.vue',
|
|
||||||
meta: {
|
|
||||||
icon: 'ant-design:user-outlined',
|
|
||||||
title: '个人中心',
|
|
||||||
hideInMenu: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||||
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
|
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
|
||||||
|
|
||||||
|
|
@ -102,10 +31,10 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||||
content: `${$t('common.loadingMenu')}...`,
|
content: `${$t('common.loadingMenu')}...`,
|
||||||
duration: 1.5,
|
duration: 1.5,
|
||||||
});
|
});
|
||||||
const userStore = useUserStore();
|
const authPermissionInfo = await getAuthPermissionInfoApi();
|
||||||
const menus = userStore.userInfo?.menus;
|
const menus = authPermissionInfo.menus;
|
||||||
const routes = buildMenus(menus);
|
const routes = buildMenus(menus);
|
||||||
const menuList = [...cloneDeep(baseMenus), ...routes];
|
const menuList = [...routes];
|
||||||
return menuList;
|
return menuList;
|
||||||
},
|
},
|
||||||
// 可以指定没有权限跳转403页面
|
// 可以指定没有权限跳转403页面
|
||||||
|
|
|
||||||
|
|
@ -87,9 +87,11 @@ function setupAccessGuard(router: Router) {
|
||||||
|
|
||||||
// 生成路由表
|
// 生成路由表
|
||||||
// 当前登录用户拥有的角色标识列表
|
// 当前登录用户拥有的角色标识列表
|
||||||
const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());
|
let userRoles = userStore.userRoles;
|
||||||
const userRoles = userInfo.roles ?? [];
|
if (!userRoles) {
|
||||||
|
const authPermissionInfo = await authStore.getAuthPermissionInfo();
|
||||||
|
userRoles = authPermissionInfo?.roles ?? [];
|
||||||
|
}
|
||||||
// 生成菜单和路由
|
// 生成菜单和路由
|
||||||
const { accessibleMenus, accessibleRoutes } = await generateAccess({
|
const { accessibleMenus, accessibleRoutes } = await generateAccess({
|
||||||
roles: userRoles,
|
roles: userRoles,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { RouteRecordStringComponent } from '@vben/types';
|
import type {
|
||||||
|
AppRouteRecordRaw,
|
||||||
import type { AppRouteRecordRaw } from '#/types';
|
RouteRecordStringComponent,
|
||||||
|
} from '@vben/types';
|
||||||
|
|
||||||
import { isHttpUrl } from '@vben/utils';
|
import { isHttpUrl } from '@vben/utils';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import type { Recordable } from '@vben/types';
|
import type { AuthPermissionInfo, Recordable } from '@vben/types';
|
||||||
|
|
||||||
import type { YudaoUserInfo } from '#/types';
|
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
@ -11,16 +9,12 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { notification } from 'ant-design-vue';
|
import { notification } from 'ant-design-vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
import { getUserInfo, loginApi, logoutApi } from '#/api';
|
import { getAuthPermissionInfoApi, loginApi, logoutApi } from '#/api';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import { setAccessToken, setRefreshToken } from '#/utils';
|
|
||||||
|
|
||||||
import { useDictStore } from './dict';
|
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const dictStore = useDictStore();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const loginLoading = ref(false);
|
const loginLoading = ref(false);
|
||||||
|
|
@ -35,44 +29,37 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
onSuccess?: () => Promise<void> | void,
|
onSuccess?: () => Promise<void> | void,
|
||||||
) {
|
) {
|
||||||
// 异步处理用户登录操作并获取 accessToken
|
// 异步处理用户登录操作并获取 accessToken
|
||||||
let userInfo: null | YudaoUserInfo = null;
|
let authPermissionInfo: AuthPermissionInfo | null = null;
|
||||||
try {
|
try {
|
||||||
loginLoading.value = true;
|
loginLoading.value = true;
|
||||||
const { accessToken, expiresTime, refreshToken } = await loginApi(params);
|
const { accessToken, refreshToken } = await loginApi(params);
|
||||||
|
|
||||||
// 如果成功获取到 accessToken
|
// 如果成功获取到 accessToken
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
|
// 将 accessToken 存储到 accessStore 中
|
||||||
accessStore.setAccessToken(accessToken);
|
accessStore.setAccessToken(accessToken);
|
||||||
accessStore.setRefreshToken(refreshToken);
|
accessStore.setRefreshToken(refreshToken);
|
||||||
setAccessToken(accessToken, expiresTime);
|
|
||||||
setRefreshToken(refreshToken);
|
|
||||||
|
|
||||||
// 获取用户信息并存储到 accessStore 中
|
authPermissionInfo = await getAuthPermissionInfo();
|
||||||
const fetchUserInfoResult = await fetchUserInfo();
|
|
||||||
userInfo = fetchUserInfoResult;
|
|
||||||
if (userInfo) {
|
|
||||||
if (userInfo.roles) {
|
|
||||||
userStore.setUserRoles(userInfo.roles);
|
|
||||||
}
|
|
||||||
// userStore.setMenus(userInfo.menus);
|
|
||||||
accessStore.setAccessCodes(userInfo.permissions);
|
|
||||||
if (accessStore.loginExpired) {
|
|
||||||
accessStore.setLoginExpired(false);
|
|
||||||
} else {
|
|
||||||
onSuccess
|
|
||||||
? await onSuccess?.()
|
|
||||||
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
dictStore.setDictMap();
|
if (accessStore.loginExpired) {
|
||||||
|
accessStore.setLoginExpired(false);
|
||||||
|
} else {
|
||||||
|
// 执行成功回调
|
||||||
|
await onSuccess?.();
|
||||||
|
// 跳转首页
|
||||||
|
await router.push(authPermissionInfo.homePath || DEFAULT_HOME_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
if (userInfo?.realName) {
|
if (
|
||||||
notification.success({
|
authPermissionInfo?.user.realName ||
|
||||||
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
|
authPermissionInfo.user.nickname
|
||||||
duration: 3,
|
) {
|
||||||
message: $t('authentication.loginSuccess'),
|
notification.success({
|
||||||
});
|
description: `${$t('authentication.loginSuccessDesc')}:${authPermissionInfo?.user.realName ?? authPermissionInfo?.user.nickname}`,
|
||||||
}
|
duration: 3,
|
||||||
|
message: $t('authentication.loginSuccess'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -80,7 +67,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userInfo,
|
authPermissionInfo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,11 +91,13 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUserInfo() {
|
async function getAuthPermissionInfo() {
|
||||||
let userInfo: null | YudaoUserInfo = null;
|
let authPermissionInfo: AuthPermissionInfo | null = null;
|
||||||
userInfo = await getUserInfo();
|
authPermissionInfo = await getAuthPermissionInfoApi();
|
||||||
userStore.setUserInfo(userInfo);
|
userStore.setUserInfo(authPermissionInfo.user);
|
||||||
return userInfo;
|
userStore.setUserRoles(authPermissionInfo.roles);
|
||||||
|
accessStore.setAccessCodes(authPermissionInfo.permissions);
|
||||||
|
return authPermissionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
function $reset() {
|
function $reset() {
|
||||||
|
|
@ -118,7 +107,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
return {
|
return {
|
||||||
$reset,
|
$reset,
|
||||||
authLogin,
|
authLogin,
|
||||||
fetchUserInfo,
|
getAuthPermissionInfo,
|
||||||
loginLoading,
|
loginLoading,
|
||||||
logout,
|
logout,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
import { StorageManager } from '@vben/utils';
|
|
||||||
|
|
||||||
import { acceptHMRUpdate, defineStore } from 'pinia';
|
|
||||||
|
|
||||||
import { getSimpleDictDataList } from '#/api/system/dict/dict.data';
|
|
||||||
|
|
||||||
const DICT_STORAGE_KEY = 'DICT_STORAGE__';
|
|
||||||
|
|
||||||
interface DictValueType {
|
|
||||||
value: any;
|
|
||||||
label: string;
|
|
||||||
colorType?: string;
|
|
||||||
cssClass?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// interface DictTypeType {
|
|
||||||
// dictType: string;
|
|
||||||
// dictValue: DictValueType[];
|
|
||||||
// }
|
|
||||||
|
|
||||||
interface DictState {
|
|
||||||
dictMap: Map<string, DictValueType[]>;
|
|
||||||
isSetDict: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const storage = new StorageManager({
|
|
||||||
prefix: import.meta.env.VITE_APP_NAMESPACE,
|
|
||||||
storageType: 'sessionStorage',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useDictStore = defineStore('dict', {
|
|
||||||
actions: {
|
|
||||||
async setDictMap() {
|
|
||||||
try {
|
|
||||||
const dataRes = await getSimpleDictDataList();
|
|
||||||
|
|
||||||
const dictDataMap = new Map<string, DictValueType[]>();
|
|
||||||
|
|
||||||
dataRes.forEach((item: any) => {
|
|
||||||
let dictTypeArray = dictDataMap.get(item.dictType);
|
|
||||||
if (!dictTypeArray) {
|
|
||||||
dictTypeArray = [];
|
|
||||||
}
|
|
||||||
dictTypeArray.push({
|
|
||||||
value: item.value,
|
|
||||||
label: item.label,
|
|
||||||
colorType: item.colorType,
|
|
||||||
cssClass: item.cssClass,
|
|
||||||
});
|
|
||||||
dictDataMap.set(item.dictType, dictTypeArray);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dictMap = dictDataMap;
|
|
||||||
this.isSetDict = true;
|
|
||||||
|
|
||||||
// 将字典数据存储到 sessionStorage 中
|
|
||||||
storage.setItem(DICT_STORAGE_KEY, dictDataMap, 60);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set dictionary values:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getters: {
|
|
||||||
getDictMap: (state) => state.dictMap,
|
|
||||||
getDictData: (state) => (dictType: string) => {
|
|
||||||
return state.dictMap.get(dictType);
|
|
||||||
},
|
|
||||||
getDictOptions: (state) => (dictType: string) => {
|
|
||||||
return state.dictMap.get(dictType);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
persist: [{ pick: ['dictMap', 'isSetDict'] }],
|
|
||||||
state: (): DictState => ({
|
|
||||||
dictMap: new Map<string, DictValueType[]>(),
|
|
||||||
isSetDict: false,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 解决热更新问题
|
|
||||||
const hot = import.meta.hot;
|
|
||||||
if (hot) {
|
|
||||||
hot.accept(acceptHMRUpdate(useDictStore, hot));
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
export * from './auth';
|
export * from './auth';
|
||||||
export * from './dict';
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './menus';
|
|
||||||
export * from './user';
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import type { BasicUserInfo } from '@vben/types';
|
|
||||||
|
|
||||||
import type { AppRouteRecordRaw } from '#/types';
|
|
||||||
|
|
||||||
/** 用户信息 */
|
|
||||||
type ExBasicUserInfo = {
|
|
||||||
deptId: number;
|
|
||||||
} & BasicUserInfo;
|
|
||||||
|
|
||||||
/** 用户信息 */
|
|
||||||
interface YudaoUserInfo extends ExBasicUserInfo {
|
|
||||||
permissions: string[];
|
|
||||||
menus: AppRouteRecordRaw[];
|
|
||||||
/**
|
|
||||||
* 首页地址
|
|
||||||
*/
|
|
||||||
homePath: string;
|
|
||||||
roles: string[];
|
|
||||||
user: ExBasicUserInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type { ExBasicUserInfo, YudaoUserInfo };
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import { StorageManager } from '@vben/utils';
|
|
||||||
// token key
|
|
||||||
const ACCESS_TOKEN_KEY = 'ACCESS_TOKEN__';
|
|
||||||
|
|
||||||
const REFRESH_TOKEN_KEY = 'REFRESH_TOKEN__';
|
|
||||||
|
|
||||||
const TENANT_ID_KEY = 'TENANT_ID__';
|
|
||||||
|
|
||||||
const storage = new StorageManager({
|
|
||||||
prefix: import.meta.env.VITE_APP_NAMESPACE,
|
|
||||||
storageType: 'sessionStorage',
|
|
||||||
});
|
|
||||||
|
|
||||||
function getAccessToken(): null | string {
|
|
||||||
return storage.getItem(ACCESS_TOKEN_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAccessToken(value: string, unix: number) {
|
|
||||||
return storage.setItem(ACCESS_TOKEN_KEY, value, unix - Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRefreshToken(): null | string {
|
|
||||||
return storage.getItem(REFRESH_TOKEN_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRefreshToken(value: string) {
|
|
||||||
return storage.setItem(REFRESH_TOKEN_KEY, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTenantId(): null | number {
|
|
||||||
return storage.getItem(TENANT_ID_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTenantId(value: number) {
|
|
||||||
return storage.setItem(TENANT_ID_KEY, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
getAccessToken,
|
|
||||||
getRefreshToken,
|
|
||||||
getTenantId,
|
|
||||||
setAccessToken,
|
|
||||||
setRefreshToken,
|
|
||||||
setTenantId,
|
|
||||||
};
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './auth';
|
|
||||||
|
|
@ -1,129 +1,132 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VbenFormSchema } from '@vben/common-ui';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { computed, reactive, ref } from 'vue';
|
import { computed, ref, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationLogin, z } from '@vben/common-ui';
|
import { AuthenticationLogin, Verification, z } from '@vben/common-ui';
|
||||||
import { useAppConfig } from '@vben/hooks';
|
import { useAppConfig } from '@vben/hooks';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
import { useDictStore, useTenantStore } from '@vben/stores';
|
||||||
|
|
||||||
import { getTenantByWebsite, getTenantIdByName } from '#/api/core/auth';
|
import {
|
||||||
import { Verify } from '#/components/Verification';
|
checkCaptcha,
|
||||||
|
getCaptcha,
|
||||||
|
getTenantByWebsite,
|
||||||
|
getTenantIdByName,
|
||||||
|
} from '#/api';
|
||||||
|
import { getSimpleDictDataList } from '#/api/system/dict-data';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
import { setTenantId } from '#/utils';
|
|
||||||
|
|
||||||
defineOptions({ name: 'Login' });
|
defineOptions({ name: 'Login' });
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
/**
|
|
||||||
* 初始化验证码
|
|
||||||
* blockPuzzle 滑块
|
|
||||||
* clickWord 点击文字
|
|
||||||
*/
|
|
||||||
const verify = ref();
|
|
||||||
const captchaType = ref('blockPuzzle');
|
|
||||||
|
|
||||||
const { tenantEnable, captchaEnable } = useAppConfig(
|
const { tenantEnable, captchaEnable } = useAppConfig(
|
||||||
import.meta.env,
|
import.meta.env,
|
||||||
import.meta.env.PROD,
|
import.meta.env.PROD,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const tenantStore = useTenantStore();
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
|
||||||
|
const captchaType = 'blockPuzzle';
|
||||||
|
const loginData = ref<Recordable<any>>({});
|
||||||
|
|
||||||
|
const verifyRef = ref();
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
component: 'VbenInput',
|
component: 'VbenInput',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: $t('page.auth.tenantNameTip'),
|
placeholder: $t('authentication.tenantName'),
|
||||||
},
|
},
|
||||||
fieldName: 'tenantName',
|
fieldName: 'tenantName',
|
||||||
label: $t('page.auth.tenantname'),
|
label: $t('authentication.tenantName'),
|
||||||
rules: z.string().min(1, { message: $t('page.auth.tenantNameTip') }),
|
rules: z
|
||||||
defaultValue: import.meta.env.VITE_APP_DEFAULT_LOGIN_TENANT || '',
|
.string()
|
||||||
|
.min(1, { message: $t('authentication.tenantNameTip') })
|
||||||
|
.default(import.meta.env.VITE_APP_DEFAULT_TENANT_NAME),
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['tenantName'],
|
||||||
|
if: tenantEnable && !tenantStore.tenantId,
|
||||||
|
trigger: (values) => {
|
||||||
|
tenantStore.setTenantName(values.tenantName);
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'VbenInput',
|
component: 'VbenInput',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: $t('page.auth.usernameTip'),
|
placeholder: $t('authentication.usernameTip'),
|
||||||
},
|
},
|
||||||
fieldName: 'username',
|
fieldName: 'username',
|
||||||
label: $t('page.auth.username'),
|
label: $t('authentication.username'),
|
||||||
rules: z.string().min(1, { message: $t('page.auth.usernameTip') }),
|
rules: z
|
||||||
defaultValue: import.meta.env.VITE_APP_DEFAULT_LOGIN_USERNAME || '',
|
.string()
|
||||||
|
.min(1, { message: $t('authentication.usernameTip') })
|
||||||
|
.default(import.meta.env.VITE_APP_DEFAULT_USERNAME),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'VbenInputPassword',
|
component: 'VbenInputPassword',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: $t('page.auth.passwordTip'),
|
placeholder: $t('authentication.password'),
|
||||||
},
|
},
|
||||||
fieldName: 'password',
|
fieldName: 'password',
|
||||||
label: $t('page.auth.password'),
|
label: $t('authentication.password'),
|
||||||
rules: z.string().min(1, { message: $t('page.auth.passwordTip') }),
|
rules: z
|
||||||
defaultValue: import.meta.env.VITE_APP_DEFAULT_LOGIN_PASSWORD || '',
|
.string()
|
||||||
|
.min(1, { message: $t('authentication.passwordTip') })
|
||||||
|
.default(import.meta.env.VITE_APP_DEFAULT_PASSWORD),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
const loginData = reactive({
|
|
||||||
loginForm: {
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
tenantName: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const captchaVerification = ref('');
|
|
||||||
// 获取验证码
|
|
||||||
async function getCode(params: any) {
|
|
||||||
if (params) {
|
|
||||||
loginData.loginForm = params;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await getTenant();
|
|
||||||
if (captchaEnable) {
|
|
||||||
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录
|
|
||||||
// 弹出验证码
|
|
||||||
verify.value.show();
|
|
||||||
} else {
|
|
||||||
// 情况一,未开启:则直接登录
|
|
||||||
await handleLogin({});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in getCode:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据域名,获得租户信息 && 获取租户ID
|
/**
|
||||||
async function getTenant() {
|
* 处理登录
|
||||||
|
*/
|
||||||
|
const handleLogin = async (values: any) => {
|
||||||
|
// 是否开启租户
|
||||||
|
if (tenantEnable && !tenantStore.tenantId) {
|
||||||
|
const tenantId = await getTenantIdByName(values.tenantName);
|
||||||
|
if (tenantId) {
|
||||||
|
tenantStore.setTenantId(tenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 是否开启验证码
|
||||||
|
if (captchaEnable) {
|
||||||
|
loginData.value = values;
|
||||||
|
verifyRef.value.show();
|
||||||
|
} else {
|
||||||
|
authStore.authLogin(values);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVerifySuccess = async ({ captchaVerification }: any) => {
|
||||||
|
await authStore.authLogin(
|
||||||
|
{
|
||||||
|
...loginData.value,
|
||||||
|
captchaVerification,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// 设置字典数据
|
||||||
|
dictStore.setDictCacheByApi(getSimpleDictDataList, 'label', 'value');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
watchEffect(async () => {
|
||||||
if (tenantEnable) {
|
if (tenantEnable) {
|
||||||
const website = location.host;
|
const website = window.location.hostname;
|
||||||
try {
|
const tenant = await getTenantByWebsite(website);
|
||||||
const tenant = await getTenantByWebsite(website);
|
if (tenant) {
|
||||||
if (tenant) {
|
tenantStore.setTenant({
|
||||||
loginData.loginForm.tenantName = tenant.name;
|
tenantId: tenant.id,
|
||||||
setTenantId(tenant.id);
|
tenantName: tenant.name,
|
||||||
} else {
|
});
|
||||||
const res = await getTenantIdByName(loginData.loginForm.tenantName);
|
|
||||||
setTenantId(res);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in getTenant:', error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
async function handleLogin(params: any) {
|
|
||||||
if (!params.captchaVerification && captchaEnable) {
|
|
||||||
console.error('Captcha verification is required');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
captchaVerification.value = params.captchaVerification;
|
|
||||||
try {
|
|
||||||
await authStore.authLogin({
|
|
||||||
...loginData.loginForm,
|
|
||||||
captchaVerification: captchaVerification.value,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in handleLogin:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -131,14 +134,16 @@ async function handleLogin(params: any) {
|
||||||
<AuthenticationLogin
|
<AuthenticationLogin
|
||||||
:form-schema="formSchema"
|
:form-schema="formSchema"
|
||||||
:loading="authStore.loginLoading"
|
:loading="authStore.loginLoading"
|
||||||
@submit="getCode"
|
@submit="handleLogin"
|
||||||
/>
|
/>
|
||||||
<Verify
|
<Verification
|
||||||
ref="verify"
|
ref="verifyRef"
|
||||||
:captcha-type="captchaType"
|
:captcha-type="captchaType"
|
||||||
|
:check-captcha-api="checkCaptcha"
|
||||||
|
:get-captcha-api="getCaptcha"
|
||||||
:img-size="{ width: '400px', height: '200px' }"
|
:img-size="{ width: '400px', height: '200px' }"
|
||||||
mode="pop"
|
mode="pop"
|
||||||
@success="handleLogin"
|
@on-success="handleVerifySuccess"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
import type { VbenFormProps } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||||
|
import type { CodegenApi } from '#/api/infra/codegen';
|
||||||
|
|
||||||
|
export namespace CodegenDefaultData {
|
||||||
|
/**
|
||||||
|
* 代码生成字段定义 表格配置
|
||||||
|
*/
|
||||||
|
export const tableColumns: VxeGridProps<CodegenApi.CodegenTableRespVO>['columns'] =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'seq',
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
{ field: 'id', title: '编号', width: 100 },
|
||||||
|
{ field: 'tableName', title: '表名' },
|
||||||
|
{ field: 'tableComment', title: '表描述' },
|
||||||
|
{ field: 'className', title: '实体类名' },
|
||||||
|
{ field: 'createTime', title: '创建时间', formatter: 'formatDateTime' },
|
||||||
|
{ field: 'updateTime', title: '更新时间', formatter: 'formatDateTime' },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
width: 'auto',
|
||||||
|
fixed: 'right',
|
||||||
|
slots: { default: 'action' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* 代码生成表定义 表格查询表单配置
|
||||||
|
*/
|
||||||
|
export const formSchema: VbenFormProps['schema'] = [
|
||||||
|
{
|
||||||
|
label: '表名称',
|
||||||
|
fieldName: 'tableName',
|
||||||
|
component: 'Input',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '表描述',
|
||||||
|
fieldName: 'tableComment',
|
||||||
|
component: 'Input',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '创建时间',
|
||||||
|
fieldName: 'createTime',
|
||||||
|
component: 'DatePicker',
|
||||||
|
componentProps: {
|
||||||
|
type: 'daterange',
|
||||||
|
format: 'YYYY-MM-DD',
|
||||||
|
valueFormat: 'YYYY-MM-DD',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
//* ****************************** ImportTableModal.vue *******************************//
|
||||||
|
|
||||||
|
export namespace CodegenImportTableModalData {
|
||||||
|
/**
|
||||||
|
* 导入表 表格配置
|
||||||
|
*/
|
||||||
|
export const tableColumns: VxeGridProps<CodegenApi.DatabaseTableRespVO>['columns'] =
|
||||||
|
[
|
||||||
|
{ type: 'checkbox', width: 50 },
|
||||||
|
{ type: 'seq', title: '序号', width: 50 },
|
||||||
|
{ field: 'name', title: '表名' },
|
||||||
|
{ field: 'comment', title: '表描述' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入表 表格查询表单配置
|
||||||
|
*/
|
||||||
|
export const formSchema: VbenFormProps['schema'] = [
|
||||||
|
{
|
||||||
|
label: '数据源',
|
||||||
|
fieldName: 'dataSourceConfigId',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请选择数据源',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '表名称',
|
||||||
|
fieldName: 'name',
|
||||||
|
component: 'Input',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '表描述',
|
||||||
|
fieldName: 'comment',
|
||||||
|
component: 'Input',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal, type VbenFormProps } from '@vben/common-ui';
|
||||||
|
import {
|
||||||
|
useVbenVxeGrid,
|
||||||
|
type VxeGridListeners,
|
||||||
|
type VxeGridProps,
|
||||||
|
} from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import { getSchemaTableList } from '#/api/infra/codegen';
|
||||||
|
import {
|
||||||
|
type DataSourceConfigApi,
|
||||||
|
getDataSourceConfigList,
|
||||||
|
} from '#/api/infra/data-source-config';
|
||||||
|
|
||||||
|
import { CodegenImportTableModalData } from '../codegen.data';
|
||||||
|
|
||||||
|
// checked
|
||||||
|
const checkedStatus = ref<boolean>(false);
|
||||||
|
|
||||||
|
const dataSourceConfigList =
|
||||||
|
ref<DataSourceConfigApi.DataSourceConfigRespVO[]>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格查询表单配置
|
||||||
|
*/
|
||||||
|
const formOptions = reactive<any>({
|
||||||
|
// 默认展开
|
||||||
|
collapsed: false,
|
||||||
|
schema: CodegenImportTableModalData.formSchema,
|
||||||
|
// 控制表单是否显示折叠按钮
|
||||||
|
showCollapseButton: true,
|
||||||
|
submitButtonOptions: {
|
||||||
|
content: '查询',
|
||||||
|
},
|
||||||
|
// 按下回车时是否提交表单
|
||||||
|
submitOnEnter: false,
|
||||||
|
} as VbenFormProps);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格配置
|
||||||
|
*/
|
||||||
|
const gridOptions = reactive<any>({
|
||||||
|
columns: CodegenImportTableModalData.tableColumns,
|
||||||
|
toolbarConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
height: 'auto',
|
||||||
|
proxyConfig: {
|
||||||
|
autoLoad: false,
|
||||||
|
ajax: {
|
||||||
|
query: async (_, values) => {
|
||||||
|
return await getSchemaTableList(values);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
} as VxeGridProps);
|
||||||
|
|
||||||
|
const gridEvents = reactive<any>({
|
||||||
|
checkboxChange: (params) => {
|
||||||
|
const { checked } = params;
|
||||||
|
checkedStatus.value = checked;
|
||||||
|
},
|
||||||
|
checkboxAll: (params) => {
|
||||||
|
const { checked } = params;
|
||||||
|
checkedStatus.value = checked;
|
||||||
|
},
|
||||||
|
} as VxeGridListeners);
|
||||||
|
|
||||||
|
// 使用表格组件
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid(
|
||||||
|
reactive({
|
||||||
|
formOptions,
|
||||||
|
gridOptions,
|
||||||
|
gridEvents,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [Modal] = useVbenModal({
|
||||||
|
class: 'w-[800px] h-[800px]',
|
||||||
|
onOpenChange: async (isOpen) => {
|
||||||
|
if (isOpen) {
|
||||||
|
// 获取数据源配置列表
|
||||||
|
dataSourceConfigList.value = await getDataSourceConfigList();
|
||||||
|
// 设置下拉框
|
||||||
|
gridApi.formApi.updateSchema([
|
||||||
|
{
|
||||||
|
fieldName: 'dataSourceConfigId',
|
||||||
|
componentProps: {
|
||||||
|
options: dataSourceConfigList.value?.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// 设置默认值
|
||||||
|
gridApi.formApi.setFieldValue(
|
||||||
|
'dataSourceConfigId',
|
||||||
|
dataSourceConfigList.value?.[0]?.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 加载表格数据
|
||||||
|
gridApi.reload(await gridApi.formApi.getValues());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal>
|
||||||
|
<Grid />
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useVbenVxeGrid,
|
||||||
|
type VxeGridListeners,
|
||||||
|
type VxeGridProps,
|
||||||
|
} from '#/adapter/vxe-table';
|
||||||
|
import { type CodegenApi, getCodegenTablePage } from '#/api/infra/codegen';
|
||||||
|
|
||||||
|
import { CodegenDefaultData } from './codegen.data';
|
||||||
|
|
||||||
|
// 使用导入表弹窗组件
|
||||||
|
const [ImportTableModal, importTableModalApi] = useVbenModal({
|
||||||
|
connectedComponent: defineAsyncComponent(
|
||||||
|
() => import('./components/import-table-modal.vue'),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// checked
|
||||||
|
const checkedStatus = ref<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查看详情
|
||||||
|
*/
|
||||||
|
const handleView = (_row: CodegenApi.CodegenTableRespVO) => {
|
||||||
|
// console.log('查看详情', row);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑
|
||||||
|
*/
|
||||||
|
const handleEdit = (_row: CodegenApi.CodegenTableRespVO) => {
|
||||||
|
// console.log('编辑', row);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*/
|
||||||
|
const handleDelete = (_row: CodegenApi.CodegenTableRespVO) => {
|
||||||
|
// console.log('删除', row);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除
|
||||||
|
*/
|
||||||
|
const handleBatchDelete = (_rows: CodegenApi.CodegenTableRespVO[]) => {
|
||||||
|
// console.log('批量删除', rows);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入表
|
||||||
|
*/
|
||||||
|
const handleImportTable = () => {
|
||||||
|
// console.log('导入表', importTableModalApi);
|
||||||
|
importTableModalApi.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步
|
||||||
|
*/
|
||||||
|
const handleSync = (_row: CodegenApi.CodegenTableRespVO) => {
|
||||||
|
// console.log('同步', row);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量同步
|
||||||
|
*/
|
||||||
|
const handleBatchSync = (_rows: CodegenApi.CodegenTableRespVO[]) => {
|
||||||
|
// console.log('批量同步', rows);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成代码
|
||||||
|
*/
|
||||||
|
const handleGenerateCode = (_row: CodegenApi.CodegenTableRespVO) => {
|
||||||
|
// console.log('生成代码', row);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量生成代码
|
||||||
|
*/
|
||||||
|
const handleBatchGenerateCode = (_rows: CodegenApi.CodegenTableRespVO[]) => {
|
||||||
|
// console.log('批量生成代码', rows);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格查询表单配置
|
||||||
|
*/
|
||||||
|
const formOptions = reactive<any>({
|
||||||
|
// 默认展开
|
||||||
|
collapsed: false,
|
||||||
|
schema: CodegenDefaultData.formSchema,
|
||||||
|
// 控制表单是否显示折叠按钮
|
||||||
|
showCollapseButton: true,
|
||||||
|
submitButtonOptions: {
|
||||||
|
content: '查询',
|
||||||
|
},
|
||||||
|
// 按下回车时是否提交表单
|
||||||
|
submitOnEnter: false,
|
||||||
|
} as VbenFormProps);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格配置
|
||||||
|
*/
|
||||||
|
const gridOptions = reactive<any>({
|
||||||
|
columns: CodegenDefaultData.tableColumns,
|
||||||
|
toolbarConfig: {
|
||||||
|
slots: {
|
||||||
|
buttons: 'toolbar_buttons',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, params) => {
|
||||||
|
const data = await getCodegenTablePage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as VxeGridProps);
|
||||||
|
|
||||||
|
const gridEvents = reactive<any>({
|
||||||
|
checkboxChange: (params) => {
|
||||||
|
const { checked } = params;
|
||||||
|
checkedStatus.value = checked;
|
||||||
|
},
|
||||||
|
checkboxAll: (params) => {
|
||||||
|
const { checked } = params;
|
||||||
|
checkedStatus.value = checked;
|
||||||
|
},
|
||||||
|
} as VxeGridListeners);
|
||||||
|
|
||||||
|
// 使用表格组件
|
||||||
|
const [Grid] = useVbenVxeGrid({
|
||||||
|
formOptions,
|
||||||
|
gridOptions,
|
||||||
|
gridEvents,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<Grid>
|
||||||
|
<template #toolbar_buttons="{ $grid }">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Button type="primary" @click="handleImportTable">导入表</Button>
|
||||||
|
<Button
|
||||||
|
:disabled="!checkedStatus"
|
||||||
|
type="primary"
|
||||||
|
@click="handleBatchSync($grid.getCheckboxRecords())"
|
||||||
|
>
|
||||||
|
批量同步
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
:disabled="!checkedStatus"
|
||||||
|
type="primary"
|
||||||
|
@click="handleBatchGenerateCode($grid.getCheckboxRecords())"
|
||||||
|
>
|
||||||
|
批量生成
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
:disabled="!checkedStatus"
|
||||||
|
danger
|
||||||
|
type="primary"
|
||||||
|
@click="handleBatchDelete($grid.getCheckboxRecords())"
|
||||||
|
>
|
||||||
|
批量删除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #action="{ row }">
|
||||||
|
<div class="flex w-fit items-center justify-around">
|
||||||
|
<Button type="link" @click="handleView(row)">查看</Button>
|
||||||
|
<Button type="link" @click="handleEdit(row)">编辑</Button>
|
||||||
|
<Button type="link" @click="handleSync(row)">同步</Button>
|
||||||
|
<Button type="link" @click="handleGenerateCode(row)">
|
||||||
|
生成代码
|
||||||
|
</Button>
|
||||||
|
<Button danger type="link" @click="handleDelete(row)">删除</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
<ImportTableModal />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
@ -31,11 +31,13 @@
|
||||||
"@vben/types": "workspace:*",
|
"@vben/types": "workspace:*",
|
||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "catalog:",
|
||||||
"@vueuse/integrations": "catalog:",
|
"@vueuse/integrations": "catalog:",
|
||||||
|
"crypto-js": "catalog:",
|
||||||
"qrcode": "catalog:",
|
"qrcode": "catalog:",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-router": "catalog:"
|
"vue-router": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/crypto-js": "catalog:",
|
||||||
"@types/qrcode": "catalog:"
|
"@types/qrcode": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
export { default as PointSelectionCaptcha } from './point-selection-captcha/index.vue';
|
export { default as PointSelectionCaptcha } from './point-selection-captcha/index.vue';
|
||||||
export { default as PointSelectionCaptchaCard } from './point-selection-captcha/index.vue';
|
|
||||||
|
|
||||||
|
export { default as PointSelectionCaptchaCard } from './point-selection-captcha/index.vue';
|
||||||
export { default as SliderCaptcha } from './slider-captcha/index.vue';
|
export { default as SliderCaptcha } from './slider-captcha/index.vue';
|
||||||
export { default as SliderRotateCaptcha } from './slider-rotate-captcha/index.vue';
|
export { default as SliderRotateCaptcha } from './slider-rotate-captcha/index.vue';
|
||||||
export type * from './types';
|
export type * from './types';
|
||||||
|
|
||||||
|
export { default as Verification } from './verification/index.vue';
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
<script type="text/babel" setup>
|
<script lang="ts" setup>
|
||||||
/**
|
import type { VerificationProps } from '../types';
|
||||||
* VerifyPoints
|
|
||||||
* @description 点选
|
|
||||||
*/
|
|
||||||
import {
|
import {
|
||||||
|
type ComponentInternalInstance,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
nextTick,
|
nextTick,
|
||||||
onMounted,
|
onMounted,
|
||||||
|
|
@ -14,68 +13,91 @@ import {
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { checkCaptcha, getCaptcha } from '#/api/core/auth';
|
import { aesEncrypt } from '../utils/ase';
|
||||||
|
import { resetSize } from '../utils/util';
|
||||||
|
|
||||||
import { aesEncrypt } from './../utils/ase';
|
/**
|
||||||
import { resetSize } from './../utils/util';
|
* VerifyPoints
|
||||||
|
* @description 点选
|
||||||
|
*/
|
||||||
|
|
||||||
const props = defineProps({
|
// const props = defineProps({
|
||||||
barSize: {
|
// barSize: {
|
||||||
default() {
|
// default() {
|
||||||
return {
|
// return {
|
||||||
height: '40px',
|
// height: '40px',
|
||||||
width: '310px',
|
// width: '310px',
|
||||||
};
|
// };
|
||||||
},
|
// },
|
||||||
type: Object,
|
// type: Object,
|
||||||
},
|
// },
|
||||||
captchaType: {
|
// captchaType: {
|
||||||
default() {
|
// default() {
|
||||||
return 'VerifyPoints';
|
// return 'VerifyPoints';
|
||||||
},
|
// },
|
||||||
type: String,
|
// type: String,
|
||||||
},
|
// },
|
||||||
imgSize: {
|
// imgSize: {
|
||||||
default() {
|
// default() {
|
||||||
return {
|
// return {
|
||||||
height: '155px',
|
// height: '155px',
|
||||||
width: '310px',
|
// width: '310px',
|
||||||
};
|
// };
|
||||||
},
|
// },
|
||||||
type: Object,
|
// type: Object,
|
||||||
},
|
// },
|
||||||
// 弹出式pop,固定fixed
|
// // 弹出式pop,固定fixed
|
||||||
mode: {
|
// mode: {
|
||||||
default: 'fixed',
|
// default: 'fixed',
|
||||||
type: String,
|
// type: String,
|
||||||
},
|
// },
|
||||||
// 间隔
|
// // 间隔
|
||||||
vSpace: {
|
// vSpace: {
|
||||||
default: 5,
|
// default: 5,
|
||||||
type: Number,
|
// type: Number,
|
||||||
},
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'VerifyPoints',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { captchaType, mode } = toRefs(props);
|
const props = withDefaults(defineProps<VerificationProps>(), {
|
||||||
const { proxy } = getCurrentInstance();
|
barSize: () => ({
|
||||||
const secretKey = ref(''); // 后端返回的ase加密秘钥
|
height: '40px',
|
||||||
|
width: '310px',
|
||||||
|
}),
|
||||||
|
captchaType: 'clickWord',
|
||||||
|
imgSize: () => ({
|
||||||
|
height: '155px',
|
||||||
|
width: '310px',
|
||||||
|
}),
|
||||||
|
mode: 'fixed',
|
||||||
|
space: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['onSuccess', 'onError', 'onClose', 'onReady']);
|
||||||
|
|
||||||
|
const { captchaType, mode, checkCaptchaApi, getCaptchaApi } = toRefs(props);
|
||||||
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
const secretKey = ref(); // 后端返回的ase加密秘钥
|
||||||
const checkNum = ref(3); // 默认需要点击的字数
|
const checkNum = ref(3); // 默认需要点击的字数
|
||||||
const fontPos = reactive([]); // 选中的坐标信息
|
const fontPos = reactive<any[]>([]); // 选中的坐标信息
|
||||||
const checkPosArr = reactive([]); // 用户点击的坐标
|
const checkPosArr = reactive<any[]>([]); // 用户点击的坐标
|
||||||
const num = ref(1); // 点击的记数
|
const num = ref(1); // 点击的记数
|
||||||
const pointBackImgBase = ref(''); // 后端获取到的背景图片
|
const pointBackImgBase = ref(); // 后端获取到的背景图片
|
||||||
const poinTextList = reactive([]); // 后端返回的点击字体顺序
|
const poinTextList = ref<any[]>([]); // 后端返回的点击字体顺序
|
||||||
const backToken = ref(''); // 后端返回的token值
|
const backToken = ref(); // 后端返回的token值
|
||||||
const setSize = reactive({
|
const setSize = reactive({
|
||||||
barHeight: 0,
|
barHeight: 0,
|
||||||
barWidth: 0,
|
barWidth: 0,
|
||||||
imgHeight: 0,
|
imgHeight: 0,
|
||||||
imgWidth: 0,
|
imgWidth: 0,
|
||||||
});
|
});
|
||||||
const tempPoints = reactive([]);
|
const tempPoints = reactive<any[]>([]);
|
||||||
const text = ref('');
|
const text = ref();
|
||||||
const barAreaColor = ref(undefined);
|
const barAreaColor = ref();
|
||||||
const barAreaBorderColor = ref(undefined);
|
const barAreaBorderColor = ref();
|
||||||
const showRefresh = ref(true);
|
const showRefresh = ref(true);
|
||||||
const bindingClick = ref(true);
|
const bindingClick = ref(true);
|
||||||
|
|
||||||
|
|
@ -91,33 +113,34 @@ function init() {
|
||||||
setSize.imgWidth = imgWidth;
|
setSize.imgWidth = imgWidth;
|
||||||
setSize.barHeight = barHeight;
|
setSize.barHeight = barHeight;
|
||||||
setSize.barWidth = barWidth;
|
setSize.barWidth = barWidth;
|
||||||
proxy.$parent.$emit('ready', proxy);
|
emit('onReady', proxy);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 禁止拖拽
|
// 禁止拖拽
|
||||||
init();
|
init();
|
||||||
proxy.$el.addEventListener('selectstart', () => {
|
proxy?.$el?.addEventListener('selectstart', () => {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const canvas = ref(null);
|
const canvas = ref(null);
|
||||||
|
|
||||||
// 获取坐标
|
// 获取坐标
|
||||||
const getMousePos = function (obj, e) {
|
const getMousePos = function (obj: any, e: any) {
|
||||||
const x = e.offsetX;
|
const x = e.offsetX;
|
||||||
const y = e.offsetY;
|
const y = e.offsetY;
|
||||||
return { x, y };
|
return { x, y };
|
||||||
};
|
};
|
||||||
// 创建坐标点
|
// 创建坐标点
|
||||||
const createPoint = function (pos) {
|
const createPoint = function (pos: any) {
|
||||||
tempPoints.push(Object.assign({}, pos));
|
tempPoints.push(Object.assign({}, pos));
|
||||||
return num.value + 1;
|
return num.value + 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 坐标转换函数
|
// 坐标转换函数
|
||||||
const pointTransfrom = function (pointArr, imgSize) {
|
const pointTransfrom = function (pointArr: any, imgSize: any) {
|
||||||
const newPointArr = pointArr.map((p) => {
|
const newPointArr = pointArr.map((p: any) => {
|
||||||
const x = Math.round((310 * p.x) / Number.parseInt(imgSize.imgWidth));
|
const x = Math.round((310 * p.x) / Number.parseInt(imgSize.imgWidth));
|
||||||
const y = Math.round((155 * p.y) / Number.parseInt(imgSize.imgHeight));
|
const y = Math.round((155 * p.y) / Number.parseInt(imgSize.imgHeight));
|
||||||
return { x, y };
|
return { x, y };
|
||||||
|
|
@ -137,7 +160,7 @@ const refresh = async function () {
|
||||||
showRefresh.value = true;
|
showRefresh.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
function canvasClick(e) {
|
function canvasClick(e: any) {
|
||||||
checkPosArr.push(getMousePos(canvas, e));
|
checkPosArr.push(getMousePos(canvas, e));
|
||||||
if (num.value === checkNum.value) {
|
if (num.value === checkNum.value) {
|
||||||
num.value = createPoint(getMousePos(canvas, e));
|
num.value = createPoint(getMousePos(canvas, e));
|
||||||
|
|
@ -162,25 +185,25 @@ function canvasClick(e) {
|
||||||
: JSON.stringify(checkPosArr),
|
: JSON.stringify(checkPosArr),
|
||||||
token: backToken.value,
|
token: backToken.value,
|
||||||
};
|
};
|
||||||
checkCaptcha(data).then((response) => {
|
checkCaptchaApi?.value?.(data).then((response: any) => {
|
||||||
const res = response.data;
|
const res = response.data;
|
||||||
if (res.repCode === '0000') {
|
if (res.repCode === '0000') {
|
||||||
barAreaColor.value = '#4cae4c';
|
barAreaColor.value = '#4cae4c';
|
||||||
barAreaBorderColor.value = '#5cb85c';
|
barAreaBorderColor.value = '#5cb85c';
|
||||||
text.value = $t('components.captcha.success');
|
text.value = $t('ui.captcha.success');
|
||||||
bindingClick.value = false;
|
bindingClick.value = false;
|
||||||
if (mode.value === 'pop') {
|
if (mode.value === 'pop') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
proxy.$parent.clickShow = false;
|
emit('onClose');
|
||||||
refresh();
|
refresh();
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
proxy.$parent.$emit('success', { captchaVerification });
|
emit('onSuccess', { captchaVerification });
|
||||||
} else {
|
} else {
|
||||||
proxy.$parent.$emit('error', proxy);
|
emit('onError', proxy);
|
||||||
barAreaColor.value = '#d9534f';
|
barAreaColor.value = '#d9534f';
|
||||||
barAreaBorderColor.value = '#d9534f';
|
barAreaBorderColor.value = '#d9534f';
|
||||||
text.value = $t('components.captcha.fail');
|
text.value = $t('ui.captcha.sliderRotateFailTip');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
refresh();
|
refresh();
|
||||||
}, 700);
|
}, 700);
|
||||||
|
|
@ -197,17 +220,22 @@ async function getPictrue() {
|
||||||
const data = {
|
const data = {
|
||||||
captchaType: captchaType.value,
|
captchaType: captchaType.value,
|
||||||
};
|
};
|
||||||
const res = await getCaptcha(data);
|
const res = await getCaptchaApi?.value?.(data);
|
||||||
if (res.data.repCode === '0000') {
|
|
||||||
pointBackImgBase.value = res.data.repData.originalImageBase64;
|
if (res?.data?.repCode === '0000') {
|
||||||
|
pointBackImgBase.value = `data:image/png;base64,${res?.data?.repData?.originalImageBase64}`;
|
||||||
backToken.value = res.data.repData.token;
|
backToken.value = res.data.repData.token;
|
||||||
secretKey.value = res.data.repData.secretKey;
|
secretKey.value = res.data.repData.secretKey;
|
||||||
poinTextList.value = res.data.repData.wordList;
|
poinTextList.value = res.data.repData.wordList;
|
||||||
text.value = `${$t('components.captcha.point')}【${poinTextList.value.join(',')}】`;
|
text.value = `${$t('ui.captcha.point')}【${poinTextList.value.join(',')}】`;
|
||||||
} else {
|
} else {
|
||||||
text.value = res.data.repMsg;
|
text.value = res?.data?.repMsg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
defineExpose({
|
||||||
|
init,
|
||||||
|
refresh,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -218,7 +246,7 @@ async function getPictrue() {
|
||||||
width: setSize.imgWidth,
|
width: setSize.imgWidth,
|
||||||
height: setSize.imgHeight,
|
height: setSize.imgHeight,
|
||||||
'background-size': `${setSize.imgWidth} ${setSize.imgHeight}`,
|
'background-size': `${setSize.imgWidth} ${setSize.imgHeight}`,
|
||||||
'margin-bottom': `${vSpace}px`,
|
'margin-bottom': `${space}px`,
|
||||||
}"
|
}"
|
||||||
class="verify-img-panel"
|
class="verify-img-panel"
|
||||||
>
|
>
|
||||||
|
|
@ -232,7 +260,7 @@ async function getPictrue() {
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
:src="`data:image/png;base64,${pointBackImgBase}`"
|
:src="pointBackImgBase"
|
||||||
alt=""
|
alt=""
|
||||||
style="display: block; width: 100%; height: 100%"
|
style="display: block; width: 100%; height: 100%"
|
||||||
@click="bindingClick ? canvasClick($event) : undefined"
|
@click="bindingClick ? canvasClick($event) : undefined"
|
||||||
|
|
@ -251,8 +279,8 @@ async function getPictrue() {
|
||||||
'line-height': '20px',
|
'line-height': '20px',
|
||||||
'border-radius': '50%',
|
'border-radius': '50%',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: `${parseInt(tempPoint.y - 10)}px`,
|
top: `${tempPoint.y - 10}px`,
|
||||||
left: `${parseInt(tempPoint.x - 10)}px`,
|
left: `${tempPoint.x - 10}px`,
|
||||||
}"
|
}"
|
||||||
class="point-area"
|
class="point-area"
|
||||||
>
|
>
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<script type="text/babel" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { VerificationProps } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VerifySlide
|
* VerifySlide
|
||||||
* @description 滑块
|
* @description 滑块
|
||||||
|
|
@ -11,107 +13,81 @@ import {
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
toRefs,
|
toRefs,
|
||||||
watch,
|
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { checkCaptcha, getCaptcha } from '#/api/core/auth';
|
|
||||||
|
|
||||||
import { aesEncrypt } from './../utils/ase';
|
import { aesEncrypt } from './../utils/ase';
|
||||||
import { resetSize } from './../utils/util';
|
import { resetSize } from './../utils/util';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = withDefaults(defineProps<VerificationProps>(), {
|
||||||
barSize: {
|
barSize: () => ({
|
||||||
default() {
|
height: '40px',
|
||||||
return {
|
width: '310px',
|
||||||
height: '30px',
|
}),
|
||||||
width: '310px',
|
blockSize: () => ({
|
||||||
};
|
height: '50px',
|
||||||
},
|
width: '50px',
|
||||||
type: Object,
|
}),
|
||||||
},
|
captchaType: 'blockPuzzle',
|
||||||
blockSize: {
|
explain: '',
|
||||||
default() {
|
imgSize: () => ({
|
||||||
return {
|
height: '155px',
|
||||||
height: '50px',
|
width: '310px',
|
||||||
width: '50px',
|
}),
|
||||||
};
|
mode: 'fixed',
|
||||||
},
|
type: '1',
|
||||||
type: Object,
|
space: 5,
|
||||||
},
|
|
||||||
captchaType: {
|
|
||||||
default() {
|
|
||||||
return 'VerifySlide';
|
|
||||||
},
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
explain: {
|
|
||||||
default: '',
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
imgSize: {
|
|
||||||
default() {
|
|
||||||
return {
|
|
||||||
height: '155px',
|
|
||||||
width: '310px',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
// 弹出式pop,固定fixed
|
|
||||||
mode: {
|
|
||||||
default: 'fixed',
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
default: '1',
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
vSpace: {
|
|
||||||
default: 5,
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { blockSize, captchaType, explain, mode, type } = toRefs(props);
|
const emit = defineEmits(['onSuccess', 'onError', 'onClose']);
|
||||||
const { proxy } = getCurrentInstance();
|
|
||||||
const secretKey = ref(''); // 后端返回的ase加密秘钥
|
const {
|
||||||
const passFlag = ref(''); // 是否通过的标识
|
blockSize,
|
||||||
const backImgBase = ref(''); // 验证码背景图片
|
captchaType,
|
||||||
const blockBackImgBase = ref(''); // 验证滑块的背景图片
|
explain,
|
||||||
const backToken = ref(''); // 后端返回的唯一token值
|
mode,
|
||||||
const startMoveTime = ref(''); // 移动开始的时间
|
checkCaptchaApi,
|
||||||
const endMovetime = ref(''); // 移动结束的时间
|
getCaptchaApi,
|
||||||
const tipWords = ref('');
|
} = toRefs(props);
|
||||||
const text = ref('');
|
|
||||||
const finishText = ref('');
|
const { proxy } = getCurrentInstance()!;
|
||||||
|
const secretKey = ref(); // 后端返回的ase加密秘钥
|
||||||
|
const passFlag = ref(); // 是否通过的标识
|
||||||
|
const backImgBase = ref(); // 验证码背景图片
|
||||||
|
const blockBackImgBase = ref(); // 验证滑块的背景图片
|
||||||
|
const backToken = ref(); // 后端返回的唯一token值
|
||||||
|
const startMoveTime = ref(); // 移动开始的时间
|
||||||
|
const endMovetime = ref(); // 移动结束的时间
|
||||||
|
const tipWords = ref();
|
||||||
|
const text = ref();
|
||||||
|
const finishText = ref();
|
||||||
const setSize = reactive({
|
const setSize = reactive({
|
||||||
barHeight: 0,
|
barHeight: '0px',
|
||||||
barWidth: 0,
|
barWidth: '0px',
|
||||||
imgHeight: 0,
|
imgHeight: '0px',
|
||||||
imgWidth: 0,
|
imgWidth: '0px',
|
||||||
});
|
});
|
||||||
const moveBlockLeft = ref(undefined);
|
const moveBlockLeft = ref();
|
||||||
const leftBarWidth = ref(undefined);
|
const leftBarWidth = ref();
|
||||||
// 移动中样式
|
// 移动中样式
|
||||||
const moveBlockBackgroundColor = ref(undefined);
|
const moveBlockBackgroundColor = ref();
|
||||||
const leftBarBorderColor = ref('#ddd');
|
const leftBarBorderColor = ref('#ddd');
|
||||||
const iconColor = ref(undefined);
|
const iconColor = ref();
|
||||||
const iconClass = ref('icon-right');
|
const iconClass = ref('icon-right');
|
||||||
const status = ref(false); // 鼠标状态
|
const status = ref(false); // 鼠标状态
|
||||||
const isEnd = ref(false); // 是够验证完成
|
const isEnd = ref(false); // 是够验证完成
|
||||||
const showRefresh = ref(true);
|
const showRefresh = ref(true);
|
||||||
const transitionLeft = ref('');
|
const transitionLeft = ref();
|
||||||
const transitionWidth = ref('');
|
const transitionWidth = ref();
|
||||||
const startLeft = ref(0);
|
const startLeft = ref(0);
|
||||||
|
|
||||||
const barArea = computed(() => {
|
const barArea = computed(() => {
|
||||||
return proxy.$el.querySelector('.verify-bar-area');
|
return proxy?.$el.querySelector('.verify-bar-area');
|
||||||
});
|
});
|
||||||
function init() {
|
function init() {
|
||||||
text.value =
|
text.value =
|
||||||
explain.value === '' ? $t('components.captcha.slide') : explain.value;
|
explain.value === '' ? $t('ui.captcha.sliderDefaultText') : explain.value;
|
||||||
|
|
||||||
getPictrue();
|
getPictrue();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
|
@ -120,7 +96,7 @@ function init() {
|
||||||
setSize.imgWidth = imgWidth;
|
setSize.imgWidth = imgWidth;
|
||||||
setSize.barHeight = barHeight;
|
setSize.barHeight = barHeight;
|
||||||
setSize.barWidth = barWidth;
|
setSize.barWidth = barWidth;
|
||||||
proxy.$parent.$emit('ready', proxy);
|
proxy?.$parent?.$emit('ready', proxy);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.removeEventListener('touchmove', move);
|
window.removeEventListener('touchmove', move);
|
||||||
|
|
@ -137,20 +113,21 @@ function init() {
|
||||||
window.addEventListener('touchend', end);
|
window.addEventListener('touchend', end);
|
||||||
window.addEventListener('mouseup', end);
|
window.addEventListener('mouseup', end);
|
||||||
}
|
}
|
||||||
watch(type, () => {
|
|
||||||
init();
|
|
||||||
});
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 禁止拖拽
|
// 禁止拖拽
|
||||||
init();
|
init();
|
||||||
proxy.$el.addEventListener('selectstart', () => {
|
proxy?.$el.addEventListener('selectstart', () => {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 鼠标按下
|
// 鼠标按下
|
||||||
function start(e) {
|
function start(e: MouseEvent | TouchEvent) {
|
||||||
e = e || window.event;
|
const x =
|
||||||
const x = e.touches ? e.touches[0].pageX : e.clientX;
|
((e as TouchEvent).touches
|
||||||
|
? (e as TouchEvent).touches[0]?.pageX
|
||||||
|
: (e as MouseEvent).clientX) || 0;
|
||||||
startLeft.value = Math.floor(x - barArea.value.getBoundingClientRect().left);
|
startLeft.value = Math.floor(x - barArea.value.getBoundingClientRect().left);
|
||||||
startMoveTime.value = Date.now(); // 开始滑动的时间
|
startMoveTime.value = Date.now(); // 开始滑动的时间
|
||||||
if (isEnd.value === false) {
|
if (isEnd.value === false) {
|
||||||
|
|
@ -163,27 +140,25 @@ function start(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 鼠标移动
|
// 鼠标移动
|
||||||
function move(e) {
|
function move(e: MouseEvent | TouchEvent) {
|
||||||
e = e || window.event;
|
|
||||||
if (status.value && isEnd.value === false) {
|
if (status.value && isEnd.value === false) {
|
||||||
const x = e.touches ? e.touches[0].pageX : e.clientX;
|
const x =
|
||||||
|
((e as TouchEvent).touches
|
||||||
|
? (e as TouchEvent).touches[0]?.pageX
|
||||||
|
: (e as MouseEvent).clientX) || 0;
|
||||||
const bar_area_left = barArea.value.getBoundingClientRect().left;
|
const bar_area_left = barArea.value.getBoundingClientRect().left;
|
||||||
let move_block_left = x - bar_area_left; // 小方块相对于父元素的left值
|
let move_block_left = x - bar_area_left; // 小方块相对于父元素的left值
|
||||||
if (
|
if (
|
||||||
move_block_left >=
|
move_block_left >=
|
||||||
barArea.value.offsetWidth -
|
barArea.value.offsetWidth - Number.parseInt(blockSize.value.width) / 2 - 2
|
||||||
Number.parseInt(Number.parseInt(blockSize.value.width) / 2) -
|
|
||||||
2
|
|
||||||
)
|
)
|
||||||
move_block_left =
|
move_block_left =
|
||||||
barArea.value.offsetWidth -
|
barArea.value.offsetWidth -
|
||||||
Number.parseInt(Number.parseInt(blockSize.value.width) / 2) -
|
Number.parseInt(blockSize.value.width) / 2 -
|
||||||
2;
|
2;
|
||||||
|
|
||||||
if (move_block_left <= 0)
|
if (move_block_left <= 0)
|
||||||
move_block_left = Number.parseInt(
|
move_block_left = Number.parseInt(blockSize.value.width) / 2;
|
||||||
Number.parseInt(blockSize.value.width) / 2,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 拖动后小方块的left值
|
// 拖动后小方块的left值
|
||||||
moveBlockLeft.value = `${move_block_left - startLeft.value}px`;
|
moveBlockLeft.value = `${move_block_left - startLeft.value}px`;
|
||||||
|
|
@ -211,7 +186,7 @@ function end() {
|
||||||
: JSON.stringify({ x: moveLeftDistance, y: 5 }),
|
: JSON.stringify({ x: moveLeftDistance, y: 5 }),
|
||||||
token: backToken.value,
|
token: backToken.value,
|
||||||
};
|
};
|
||||||
checkCaptcha(data).then((response) => {
|
checkCaptchaApi?.value?.(data).then((response) => {
|
||||||
const res = response.data;
|
const res = response.data;
|
||||||
if (res.repCode === '0000') {
|
if (res.repCode === '0000') {
|
||||||
moveBlockBackgroundColor.value = '#5cb85c';
|
moveBlockBackgroundColor.value = '#5cb85c';
|
||||||
|
|
@ -222,13 +197,13 @@ function end() {
|
||||||
isEnd.value = true;
|
isEnd.value = true;
|
||||||
if (mode.value === 'pop') {
|
if (mode.value === 'pop') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
proxy.$parent.clickShow = false;
|
emit('onClose');
|
||||||
refresh();
|
refresh();
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
passFlag.value = true;
|
passFlag.value = true;
|
||||||
tipWords.value = `${((endMovetime.value - startMoveTime.value) / 1000).toFixed(2)}s
|
tipWords.value = `${((endMovetime.value - startMoveTime.value) / 1000).toFixed(2)}s
|
||||||
${$t('components.captcha.success')}`;
|
${$t('ui.captcha.title')}`;
|
||||||
const captchaVerification = secretKey.value
|
const captchaVerification = secretKey.value
|
||||||
? aesEncrypt(
|
? aesEncrypt(
|
||||||
`${backToken.value}---${JSON.stringify({ x: moveLeftDistance, y: 5 })}`,
|
`${backToken.value}---${JSON.stringify({ x: moveLeftDistance, y: 5 })}`,
|
||||||
|
|
@ -237,8 +212,8 @@ function end() {
|
||||||
: `${backToken.value}---${JSON.stringify({ x: moveLeftDistance, y: 5 })}`;
|
: `${backToken.value}---${JSON.stringify({ x: moveLeftDistance, y: 5 })}`;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tipWords.value = '';
|
tipWords.value = '';
|
||||||
proxy.$parent.closeBox();
|
emit('onSuccess', { captchaVerification });
|
||||||
proxy.$parent.$emit('success', { captchaVerification });
|
emit('onClose');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
moveBlockBackgroundColor.value = '#d9534f';
|
moveBlockBackgroundColor.value = '#d9534f';
|
||||||
|
|
@ -249,8 +224,8 @@ function end() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
refresh();
|
refresh();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
proxy.$parent.$emit('error', proxy);
|
emit('onError', proxy);
|
||||||
tipWords.value = $t('components.captcha.fail');
|
tipWords.value = $t('ui.captcha.sliderRotateFailTip');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tipWords.value = '';
|
tipWords.value = '';
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
@ -289,23 +264,28 @@ async function getPictrue() {
|
||||||
const data = {
|
const data = {
|
||||||
captchaType: captchaType.value,
|
captchaType: captchaType.value,
|
||||||
};
|
};
|
||||||
const res = await getCaptcha(data);
|
const res = await getCaptchaApi?.value?.(data);
|
||||||
if (res.data.repCode === '0000') {
|
|
||||||
backImgBase.value = res.data.repData.originalImageBase64;
|
if (res?.data?.repCode === '0000') {
|
||||||
blockBackImgBase.value = `data:image/png;base64,${res.data.repData.jigsawImageBase64}`;
|
backImgBase.value = `data:image/png;base64,${res?.data?.repData?.originalImageBase64}`;
|
||||||
|
blockBackImgBase.value = `data:image/png;base64,${res?.data?.repData?.jigsawImageBase64}`;
|
||||||
backToken.value = res.data.repData.token;
|
backToken.value = res.data.repData.token;
|
||||||
secretKey.value = res.data.repData.secretKey;
|
secretKey.value = res.data.repData.secretKey;
|
||||||
} else {
|
} else {
|
||||||
tipWords.value = res.data.repMsg;
|
tipWords.value = res?.data?.repMsg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
defineExpose({
|
||||||
|
init,
|
||||||
|
refresh,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div style="position: relative">
|
<div style="position: relative">
|
||||||
<div
|
<div
|
||||||
v-if="type === '2'"
|
v-if="type === '2'"
|
||||||
:style="{ height: `${parseInt(setSize.imgHeight) + vSpace}px` }"
|
:style="{ height: `${Number.parseInt(setSize.imgHeight) + space}px` }"
|
||||||
class="verify-img-out"
|
class="verify-img-out"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
@ -313,7 +293,7 @@ async function getPictrue() {
|
||||||
class="verify-img-panel"
|
class="verify-img-panel"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="`data:image/png;base64,${backImgBase}`"
|
:src="backImgBase"
|
||||||
alt=""
|
alt=""
|
||||||
style="display: block; width: 100%; height: 100%"
|
style="display: block; width: 100%; height: 100%"
|
||||||
/>
|
/>
|
||||||
|
|
@ -346,7 +326,7 @@ async function getPictrue() {
|
||||||
width: leftBarWidth !== undefined ? leftBarWidth : barSize.height,
|
width: leftBarWidth !== undefined ? leftBarWidth : barSize.height,
|
||||||
height: barSize.height,
|
height: barSize.height,
|
||||||
'border-color': leftBarBorderColor,
|
'border-color': leftBarBorderColor,
|
||||||
transaction: transitionWidth,
|
transition: transitionWidth,
|
||||||
}"
|
}"
|
||||||
class="verify-left-bar"
|
class="verify-left-bar"
|
||||||
>
|
>
|
||||||
|
|
@ -371,9 +351,9 @@ async function getPictrue() {
|
||||||
<div
|
<div
|
||||||
v-if="type === '2'"
|
v-if="type === '2'"
|
||||||
:style="{
|
:style="{
|
||||||
width: `${Math.floor((parseInt(setSize.imgWidth) * 47) / 310)}px`,
|
width: `${Math.floor((Number.parseInt(setSize.imgWidth) * 47) / 310)}px`,
|
||||||
height: setSize.imgHeight,
|
height: setSize.imgHeight,
|
||||||
top: `-${parseInt(setSize.imgHeight) + vSpace}px`,
|
top: `-${Number.parseInt(setSize.imgHeight) + space}px`,
|
||||||
'background-size': `${setSize.imgWidth} ${setSize.imgHeight}`,
|
'background-size': `${setSize.imgWidth} ${setSize.imgHeight}`,
|
||||||
}"
|
}"
|
||||||
class="verify-sub-block"
|
class="verify-sub-block"
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* Verify 验证码组件
|
||||||
|
* @description 分发验证码使用
|
||||||
|
*/
|
||||||
|
import type { VerificationProps } from './types';
|
||||||
|
|
||||||
|
import { defineAsyncComponent, markRaw, ref, toRefs, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import './style/verify.css';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'Verification',
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<VerificationProps>(), {
|
||||||
|
arith: 0,
|
||||||
|
barSize: () => ({
|
||||||
|
height: '40px',
|
||||||
|
width: '310px',
|
||||||
|
}),
|
||||||
|
blockSize: () => ({
|
||||||
|
height: '50px',
|
||||||
|
width: '50px',
|
||||||
|
}),
|
||||||
|
captchaType: 'blockPuzzle',
|
||||||
|
explain: '',
|
||||||
|
figure: 0,
|
||||||
|
imgSize: () => ({
|
||||||
|
height: '155px',
|
||||||
|
width: '310px',
|
||||||
|
}),
|
||||||
|
mode: 'fixed',
|
||||||
|
space: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['onSuccess', 'onError', 'onClose', 'onReady']);
|
||||||
|
|
||||||
|
const VerifyPoints = defineAsyncComponent(
|
||||||
|
() => import('./Verify/VerifyPoints.vue'),
|
||||||
|
);
|
||||||
|
const VerifySlide = defineAsyncComponent(
|
||||||
|
() => import('./Verify/VerifySlide.vue'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { captchaType, mode, checkCaptchaApi, getCaptchaApi } = toRefs(props);
|
||||||
|
const verifyType = ref();
|
||||||
|
const componentType = ref();
|
||||||
|
|
||||||
|
const instance = ref<InstanceType<typeof VerifyPoints | typeof VerifySlide>>();
|
||||||
|
|
||||||
|
const showBox = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* refresh
|
||||||
|
* @description 刷新
|
||||||
|
*/
|
||||||
|
const refresh = () => {
|
||||||
|
if (instance.value && instance.value.refresh) instance.value.refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
const show = () => {
|
||||||
|
if (mode.value === 'pop') showBox.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onError = (proxy: any) => {
|
||||||
|
emit('onError', proxy);
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onReady = (proxy: any) => {
|
||||||
|
emit('onReady', proxy);
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
emit('onClose');
|
||||||
|
showBox.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSuccess = (data: any) => {
|
||||||
|
emit('onSuccess', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
switch (captchaType.value) {
|
||||||
|
case 'blockPuzzle': {
|
||||||
|
verifyType.value = '2';
|
||||||
|
componentType.value = markRaw(VerifySlide);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'clickWord': {
|
||||||
|
verifyType.value = '';
|
||||||
|
componentType.value = markRaw(VerifyPoints);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
onClose,
|
||||||
|
onError,
|
||||||
|
onReady,
|
||||||
|
onSuccess,
|
||||||
|
show,
|
||||||
|
refresh,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-show="showBox">
|
||||||
|
<div
|
||||||
|
:class="mode === 'pop' ? 'verifybox' : ''"
|
||||||
|
:style="{ 'max-width': `${parseInt(imgSize.width) + 20}px` }"
|
||||||
|
>
|
||||||
|
<div v-if="mode === 'pop'" class="verifybox-top">
|
||||||
|
{{ $t('ui.captcha.title') }}
|
||||||
|
<span class="verifybox-close" @click="onClose">
|
||||||
|
<i class="iconfont icon-close"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:style="{ padding: mode === 'pop' ? '10px' : '0' }"
|
||||||
|
class="verifybox-bottom"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="componentType"
|
||||||
|
v-if="componentType"
|
||||||
|
ref="instance"
|
||||||
|
:arith="arith"
|
||||||
|
:bar-size="barSize"
|
||||||
|
:block-size="blockSize"
|
||||||
|
:captcha-type="captchaType"
|
||||||
|
:check-captcha-api="checkCaptchaApi"
|
||||||
|
:explain="explain"
|
||||||
|
:figure="figure"
|
||||||
|
:get-captcha-api="getCaptchaApi"
|
||||||
|
:img-size="imgSize"
|
||||||
|
:mode="mode"
|
||||||
|
:space="space"
|
||||||
|
:type="verifyType"
|
||||||
|
@on-close="onClose"
|
||||||
|
@on-error="onError"
|
||||||
|
@on-ready="onReady"
|
||||||
|
@on-success="onSuccess"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
.verifybox {
|
.verifybox {
|
||||||
position: relative;
|
position: absolute;
|
||||||
top: 25%;
|
top: 25%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
25
packages/effects/common-ui/src/components/captcha/verification/types/index.d.ts
vendored
Normal file
25
packages/effects/common-ui/src/components/captcha/verification/types/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
interface VerificationProps {
|
||||||
|
arith?: number;
|
||||||
|
barSize?: {
|
||||||
|
height: string;
|
||||||
|
width: string;
|
||||||
|
};
|
||||||
|
blockSize?: {
|
||||||
|
height: string;
|
||||||
|
width: string;
|
||||||
|
};
|
||||||
|
captchaType?: 'blockPuzzle' | 'clickWord';
|
||||||
|
explain?: string;
|
||||||
|
figure?: number;
|
||||||
|
imgSize?: {
|
||||||
|
height: string;
|
||||||
|
width: string;
|
||||||
|
};
|
||||||
|
mode?: 'fixed' | 'pop';
|
||||||
|
space?: number;
|
||||||
|
type?: '1' | '2';
|
||||||
|
checkCaptchaApi?: (data: any) => Promise<any>;
|
||||||
|
getCaptchaApi?: (data: any) => Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { VerificationProps };
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
VxeInput,
|
VxeInput,
|
||||||
VxeLoading,
|
VxeLoading,
|
||||||
VxeModal,
|
VxeModal,
|
||||||
|
VxeNumberInput,
|
||||||
VxePager,
|
VxePager,
|
||||||
// VxeList,
|
// VxeList,
|
||||||
// VxeModal,
|
// VxeModal,
|
||||||
|
|
@ -70,6 +71,7 @@ export function initVxeTable() {
|
||||||
VxeUI.component(VxeGrid);
|
VxeUI.component(VxeGrid);
|
||||||
VxeUI.component(VxeToolbar);
|
VxeUI.component(VxeToolbar);
|
||||||
|
|
||||||
|
VxeUI.component(VxeNumberInput);
|
||||||
VxeUI.component(VxeButton);
|
VxeUI.component(VxeButton);
|
||||||
// VxeUI.component(VxeButtonGroup);
|
// VxeUI.component(VxeButtonGroup);
|
||||||
VxeUI.component(VxeCheckbox);
|
VxeUI.component(VxeCheckbox);
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,11 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vben/locales": "workspace:*",
|
"@vben/locales": "workspace:*",
|
||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"axios": "catalog:"
|
"axios": "catalog:",
|
||||||
|
"qs": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/qs": "catalog:",
|
||||||
"axios-mock-adapter": "catalog:"
|
"axios-mock-adapter": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ export const authenticateResponseInterceptor = ({
|
||||||
}): ResponseInterceptorConfig => {
|
}): ResponseInterceptorConfig => {
|
||||||
return {
|
return {
|
||||||
rejected: async (error) => {
|
rejected: async (error) => {
|
||||||
const { config, response } = error;
|
const { config, response, data: responseData } = error;
|
||||||
// 如果不是 401 错误,直接抛出异常
|
// 如果不是 401 错误,直接抛出异常
|
||||||
if (response?.status !== 401) {
|
if (response?.status !== 401 && responseData.code !== 401) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
// 判断是否启用了 refreshToken 功能
|
// 判断是否启用了 refreshToken 功能
|
||||||
|
|
@ -92,7 +92,7 @@ export const errorMessageResponseInterceptor = (
|
||||||
}
|
}
|
||||||
|
|
||||||
let errorMessage = '';
|
let errorMessage = '';
|
||||||
const status = error?.response?.status;
|
const status = error?.response?.data?.code || error?.response?.status;
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 400: {
|
case 400: {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import type {
|
||||||
import { bindMethods, merge } from '@vben/utils';
|
import { bindMethods, merge } from '@vben/utils';
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import qs from 'qs';
|
||||||
|
|
||||||
import { FileDownloader } from './modules/downloader';
|
import { FileDownloader } from './modules/downloader';
|
||||||
import { InterceptorManager } from './modules/interceptor';
|
import { InterceptorManager } from './modules/interceptor';
|
||||||
|
|
@ -39,6 +40,10 @@ class RequestClient {
|
||||||
},
|
},
|
||||||
// 默认超时时间
|
// 默认超时时间
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
|
// 处理请求参数 默认使用qs库处理
|
||||||
|
paramsSerializer: (params) => {
|
||||||
|
return qs.stringify(params, { arrayFormat: 'repeat' });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const { ...axiosConfig } = options;
|
const { ...axiosConfig } = options;
|
||||||
const requestConfig = merge(axiosConfig, defaultConfig);
|
const requestConfig = merge(axiosConfig, defaultConfig);
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,25 @@ interface HttpResponse<T = any> {
|
||||||
*/
|
*/
|
||||||
code: number;
|
code: number;
|
||||||
data: T;
|
data: T;
|
||||||
message: string;
|
msg: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageParam {
|
||||||
|
[key: string]: any;
|
||||||
|
pageNo: number;
|
||||||
|
pageSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageResult<T> {
|
||||||
|
list: T[];
|
||||||
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
MakeErrorMessageFn,
|
MakeErrorMessageFn,
|
||||||
|
PageParam,
|
||||||
|
PageResult,
|
||||||
RequestClientOptions,
|
RequestClientOptions,
|
||||||
RequestContentType,
|
RequestContentType,
|
||||||
RequestInterceptorConfig,
|
RequestInterceptorConfig,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { acceptHMRUpdate, defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export interface DictItem {
|
||||||
|
colorType?: string;
|
||||||
|
cssClass?: string;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Dict = Record<string, DictItem[]>;
|
||||||
|
|
||||||
|
interface DictState {
|
||||||
|
dictCache: Dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDictStore = defineStore('dict', {
|
||||||
|
actions: {
|
||||||
|
getDictData(dictType: string, value?: string) {
|
||||||
|
const dict = this.dictCache[dictType];
|
||||||
|
if (!dict) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return value ? dict.find((d) => d.value === value) : dict;
|
||||||
|
},
|
||||||
|
setDictCache(dicts: Dict) {
|
||||||
|
this.dictCache = dicts;
|
||||||
|
},
|
||||||
|
setDictCacheByApi(
|
||||||
|
api: (params: Record<string, any>) => Promise<Record<string, any>[]>,
|
||||||
|
params: Record<string, any>,
|
||||||
|
labelField: string = 'label',
|
||||||
|
valueField: string = 'value',
|
||||||
|
) {
|
||||||
|
api(params).then((dicts) => {
|
||||||
|
const dictCacheData: Dict = {};
|
||||||
|
dicts.forEach((dict) => {
|
||||||
|
dictCacheData[dict.dictType] = dicts
|
||||||
|
.filter((d) => d.dictType === dict.dictType)
|
||||||
|
.map((d) => ({
|
||||||
|
colorType: d.colorType,
|
||||||
|
cssClass: d.cssClass,
|
||||||
|
label: d[labelField],
|
||||||
|
value: d[valueField],
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
this.setDictCache(dictCacheData);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
persist: {
|
||||||
|
// 持久化
|
||||||
|
pick: ['dictCache'],
|
||||||
|
},
|
||||||
|
state: (): DictState => ({
|
||||||
|
dictCache: {},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 解决热更新问题
|
||||||
|
const hot = import.meta.hot;
|
||||||
|
if (hot) {
|
||||||
|
hot.accept(acceptHMRUpdate(useDictStore, hot));
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
export * from './access';
|
export * from './access';
|
||||||
|
export * from './dict';
|
||||||
export * from './lock';
|
export * from './lock';
|
||||||
export * from './tabbar';
|
export * from './tabbar';
|
||||||
|
export * from './tenant';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export interface TenantState {
|
||||||
|
tenantId?: number;
|
||||||
|
tenantName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTenantStore = defineStore('tenant', {
|
||||||
|
actions: {
|
||||||
|
$reset() {
|
||||||
|
this.tenantId = undefined;
|
||||||
|
this.tenantName = undefined;
|
||||||
|
},
|
||||||
|
setTenant(tenant: TenantState) {
|
||||||
|
this.tenantId = tenant.tenantId;
|
||||||
|
this.tenantName = tenant.tenantName;
|
||||||
|
},
|
||||||
|
setTenantId(id: number) {
|
||||||
|
this.tenantId = id;
|
||||||
|
},
|
||||||
|
setTenantName(name: string) {
|
||||||
|
this.tenantName = name;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
persist: {
|
||||||
|
// 持久化
|
||||||
|
pick: ['tenantId', 'tenantName'],
|
||||||
|
},
|
||||||
|
state: (): TenantState => ({
|
||||||
|
tenantId: undefined,
|
||||||
|
tenantName: undefined,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
@ -51,6 +51,10 @@ export const useUserStore = defineStore('core-user', {
|
||||||
this.userRoles = roles;
|
this.userRoles = roles;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
persist: {
|
||||||
|
// 持久化
|
||||||
|
pick: ['userInfo', 'userRoles'],
|
||||||
|
},
|
||||||
state: (): AccessState => ({
|
state: (): AccessState => ({
|
||||||
userInfo: null,
|
userInfo: null,
|
||||||
userRoles: [],
|
userRoles: [],
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
|
export type * from './menu';
|
||||||
export type * from './user';
|
export type * from './user';
|
||||||
export type * from '@vben-core/typings';
|
export type * from '@vben-core/typings';
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,18 @@
|
||||||
import type { BasicUserInfo } from '@vben-core/typings';
|
import type { BasicUserInfo } from '@vben-core/typings';
|
||||||
|
|
||||||
/** 用户信息 */
|
import type { AppRouteRecordRaw } from './menu';
|
||||||
interface UserInfo extends BasicUserInfo {
|
|
||||||
/**
|
|
||||||
* 用户描述
|
|
||||||
*/
|
|
||||||
desc: string;
|
|
||||||
/**
|
|
||||||
* 首页地址
|
|
||||||
*/
|
|
||||||
homePath: string;
|
|
||||||
|
|
||||||
/**
|
interface ExUserInfo extends BasicUserInfo {
|
||||||
* accessToken
|
deptId: number;
|
||||||
*/
|
nickname: string;
|
||||||
token: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { UserInfo };
|
interface AuthPermissionInfo {
|
||||||
|
permissions: string[];
|
||||||
|
menus: AppRouteRecordRaw[];
|
||||||
|
roles: string[];
|
||||||
|
homePath: string;
|
||||||
|
user: ExUserInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { AuthPermissionInfo, ExUserInfo };
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ async function generateRoutesByBackend(
|
||||||
|
|
||||||
const routes = convertRoutes(menuRoutes, layoutMap, normalizePageMap);
|
const routes = convertRoutes(menuRoutes, layoutMap, normalizePageMap);
|
||||||
|
|
||||||
return routes;
|
return [...options.routes, ...routes];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,9 @@ catalogs:
|
||||||
'@types/archiver':
|
'@types/archiver':
|
||||||
specifier: ^6.0.3
|
specifier: ^6.0.3
|
||||||
version: 6.0.3
|
version: 6.0.3
|
||||||
|
'@types/crypto-js':
|
||||||
|
specifier: ^4.2.2
|
||||||
|
version: 4.2.2
|
||||||
'@types/eslint':
|
'@types/eslint':
|
||||||
specifier: ^9.6.1
|
specifier: ^9.6.1
|
||||||
version: 9.6.1
|
version: 9.6.1
|
||||||
|
|
@ -102,6 +105,9 @@ catalogs:
|
||||||
'@types/qrcode':
|
'@types/qrcode':
|
||||||
specifier: ^1.5.5
|
specifier: ^1.5.5
|
||||||
version: 1.5.5
|
version: 1.5.5
|
||||||
|
'@types/qs':
|
||||||
|
specifier: ^6.9.17
|
||||||
|
version: 6.9.17
|
||||||
'@types/sortablejs':
|
'@types/sortablejs':
|
||||||
specifier: ^1.15.8
|
specifier: ^1.15.8
|
||||||
version: 1.15.8
|
version: 1.15.8
|
||||||
|
|
@ -174,6 +180,9 @@ catalogs:
|
||||||
cross-env:
|
cross-env:
|
||||||
specifier: ^7.0.3
|
specifier: ^7.0.3
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
|
crypto-js:
|
||||||
|
specifier: ^4.2.0
|
||||||
|
version: 4.2.0
|
||||||
cspell:
|
cspell:
|
||||||
specifier: ^8.16.0
|
specifier: ^8.16.0
|
||||||
version: 8.16.0
|
version: 8.16.0
|
||||||
|
|
@ -348,6 +357,9 @@ catalogs:
|
||||||
qrcode:
|
qrcode:
|
||||||
specifier: ^1.5.4
|
specifier: ^1.5.4
|
||||||
version: 1.5.4
|
version: 1.5.4
|
||||||
|
qs:
|
||||||
|
specifier: ^6.13.1
|
||||||
|
version: 6.13.1
|
||||||
radix-vue:
|
radix-vue:
|
||||||
specifier: ^1.9.10
|
specifier: ^1.9.10
|
||||||
version: 1.9.10
|
version: 1.9.10
|
||||||
|
|
@ -661,9 +673,6 @@ importers:
|
||||||
ant-design-vue:
|
ant-design-vue:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.2.6(vue@3.5.13(typescript@5.7.2))
|
version: 4.2.6(vue@3.5.13(typescript@5.7.2))
|
||||||
crypto-js:
|
|
||||||
specifier: ^4.2.0
|
|
||||||
version: 4.2.0
|
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
|
|
@ -676,10 +685,6 @@ importers:
|
||||||
vue-router:
|
vue-router:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.4.5(vue@3.5.13(typescript@5.7.2))
|
version: 4.4.5(vue@3.5.13(typescript@5.7.2))
|
||||||
devDependencies:
|
|
||||||
'@types/crypto-js':
|
|
||||||
specifier: ^4.2.2
|
|
||||||
version: 4.2.2
|
|
||||||
|
|
||||||
apps/web-ele:
|
apps/web-ele:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -1494,6 +1499,9 @@ importers:
|
||||||
'@vueuse/integrations':
|
'@vueuse/integrations':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 11.3.0(async-validator@4.2.5)(axios@1.7.7)(change-case@5.4.4)(focus-trap@7.6.2)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.4)(vue@3.5.13(typescript@5.7.2))
|
version: 11.3.0(async-validator@4.2.5)(axios@1.7.7)(change-case@5.4.4)(focus-trap@7.6.2)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.4)(vue@3.5.13(typescript@5.7.2))
|
||||||
|
crypto-js:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 4.2.0
|
||||||
qrcode:
|
qrcode:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.5.4
|
version: 1.5.4
|
||||||
|
|
@ -1504,6 +1512,9 @@ importers:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.4.5(vue@3.5.13(typescript@5.7.2))
|
version: 4.4.5(vue@3.5.13(typescript@5.7.2))
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/crypto-js':
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 4.2.2
|
||||||
'@types/qrcode':
|
'@types/qrcode':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.5.5
|
version: 1.5.5
|
||||||
|
|
@ -1651,7 +1662,13 @@ importers:
|
||||||
axios:
|
axios:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.7.7
|
version: 1.7.7
|
||||||
|
qs:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 6.13.1
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/qs':
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 6.9.17
|
||||||
axios-mock-adapter:
|
axios-mock-adapter:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 2.1.0(axios@1.7.7)
|
version: 2.1.0(axios@1.7.7)
|
||||||
|
|
@ -4349,6 +4366,9 @@ packages:
|
||||||
'@types/qrcode@1.5.5':
|
'@types/qrcode@1.5.5':
|
||||||
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
|
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
|
||||||
|
|
||||||
|
'@types/qs@6.9.17':
|
||||||
|
resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
|
||||||
|
|
||||||
'@types/readdir-glob@1.1.5':
|
'@types/readdir-glob@1.1.5':
|
||||||
resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==}
|
resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==}
|
||||||
|
|
||||||
|
|
@ -8597,6 +8617,10 @@ packages:
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
qs@6.13.1:
|
||||||
|
resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==}
|
||||||
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
|
|
@ -13095,6 +13119,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.9.3
|
'@types/node': 22.9.3
|
||||||
|
|
||||||
|
'@types/qs@6.9.17': {}
|
||||||
|
|
||||||
'@types/readdir-glob@1.1.5':
|
'@types/readdir-glob@1.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.9.3
|
'@types/node': 22.9.3
|
||||||
|
|
@ -17799,6 +17825,10 @@ snapshots:
|
||||||
pngjs: 5.0.0
|
pngjs: 5.0.0
|
||||||
yargs: 15.4.1
|
yargs: 15.4.1
|
||||||
|
|
||||||
|
qs@6.13.1:
|
||||||
|
dependencies:
|
||||||
|
side-channel: 1.0.6
|
||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
queue-tick@1.0.1: {}
|
queue-tick@1.0.1: {}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ catalog:
|
||||||
'@tanstack/vue-query': ^5.61.3
|
'@tanstack/vue-query': ^5.61.3
|
||||||
'@tanstack/vue-store': ^0.5.7
|
'@tanstack/vue-store': ^0.5.7
|
||||||
'@types/archiver': ^6.0.3
|
'@types/archiver': ^6.0.3
|
||||||
|
'@types/crypto-js': ^4.2.2
|
||||||
'@types/eslint': ^9.6.1
|
'@types/eslint': ^9.6.1
|
||||||
'@types/html-minifier-terser': ^7.0.2
|
'@types/html-minifier-terser': ^7.0.2
|
||||||
'@types/jsonwebtoken': ^9.0.7
|
'@types/jsonwebtoken': ^9.0.7
|
||||||
|
|
@ -47,6 +48,7 @@ catalog:
|
||||||
'@types/nprogress': ^0.2.3
|
'@types/nprogress': ^0.2.3
|
||||||
'@types/postcss-import': ^14.0.3
|
'@types/postcss-import': ^14.0.3
|
||||||
'@types/qrcode': ^1.5.5
|
'@types/qrcode': ^1.5.5
|
||||||
|
'@types/qs': ^6.9.17
|
||||||
'@types/sortablejs': ^1.15.8
|
'@types/sortablejs': ^1.15.8
|
||||||
'@typescript-eslint/eslint-plugin': ^8.15.0
|
'@typescript-eslint/eslint-plugin': ^8.15.0
|
||||||
'@typescript-eslint/parser': ^8.15.0
|
'@typescript-eslint/parser': ^8.15.0
|
||||||
|
|
@ -73,6 +75,7 @@ catalog:
|
||||||
commitlint-plugin-function-rules: ^4.0.1
|
commitlint-plugin-function-rules: ^4.0.1
|
||||||
consola: ^3.2.3
|
consola: ^3.2.3
|
||||||
cross-env: ^7.0.3
|
cross-env: ^7.0.3
|
||||||
|
crypto-js: ^4.2.0
|
||||||
cspell: ^8.16.0
|
cspell: ^8.16.0
|
||||||
cssnano: ^7.0.6
|
cssnano: ^7.0.6
|
||||||
cz-git: ^1.11.0
|
cz-git: ^1.11.0
|
||||||
|
|
@ -132,6 +135,7 @@ catalog:
|
||||||
prettier-plugin-tailwindcss: ^0.6.9
|
prettier-plugin-tailwindcss: ^0.6.9
|
||||||
publint: ^0.2.12
|
publint: ^0.2.12
|
||||||
qrcode: ^1.5.4
|
qrcode: ^1.5.4
|
||||||
|
qs: ^6.13.1
|
||||||
radix-vue: ^1.9.10
|
radix-vue: ^1.9.10
|
||||||
resolve.exports: ^2.0.2
|
resolve.exports: ^2.0.2
|
||||||
rimraf: ^6.0.1
|
rimraf: ^6.0.1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue