chore(release): unify to wwjcloud across backend/frontend; routes, DTO/VO paths, docs/links; remove niucloud; naming fixes
This commit is contained in:
@@ -1,11 +1,6 @@
|
||||
# NestJS后端API地址
|
||||
VITE_APP_BASE_URL=http://localhost:3000
|
||||
|
||||
# 开发模式
|
||||
NODE_ENV=development
|
||||
|
||||
# API请求超时(毫秒)
|
||||
VITE_APP_TIMEOUT=30000
|
||||
|
||||
# 是否开启Mock数据
|
||||
VITE_APP_MOCK=false
|
||||
VITE_REQUEST_HEADER_TOKEN_KEY='token'
|
||||
VITE_REQUEST_HEADER_SITEID_KEY='site-id'
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
# NestJS后端API地址(生产环境)
|
||||
VITE_APP_BASE_URL=http://localhost:3000
|
||||
|
||||
# 生产模式
|
||||
NODE_ENV=production
|
||||
|
||||
# API请求超时(毫秒)
|
||||
VITE_APP_TIMEOUT=30000
|
||||
|
||||
# 是否开启Mock数据
|
||||
VITE_APP_MOCK=false
|
||||
VITE_REQUEST_HEADER_TOKEN_KEY='token'
|
||||
VITE_REQUEST_HEADER_SITEID_KEY='site-id'
|
||||
|
||||
160
admin-vben/MIGRATION_GUIDE.md
Normal file
160
admin-vben/MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Java Admin前端迁移到Vben框架 - 迁移指南
|
||||
|
||||
## 项目概述
|
||||
|
||||
本项目将基于Java + Vue3 + Element Plus的admin前端系统迁移到Vben框架(Vue3 + Ant Design Vue + Vben组件库)。
|
||||
|
||||
## 迁移状态
|
||||
|
||||
### ✅ 已完成迁移
|
||||
|
||||
1. **登录认证系统**
|
||||
- 迁移了登录页面 (`login-migrated.vue`)
|
||||
- 适配了Java admin的登录逻辑和双端登录(平台端/站点端)
|
||||
- 创建了认证API接口 (`auth.ts`)
|
||||
- 创建了适配的认证状态管理 (`auth-migrated.ts`)
|
||||
|
||||
2. **系统管理模块**
|
||||
- 用户管理页面 (`system/user/index.vue`)
|
||||
- 用户编辑模态框 (`system/user/components/user-edit-modal.vue`)
|
||||
- 用户管理API接口 (`user.ts`)
|
||||
- 创建了系统管理相关的中文翻译
|
||||
|
||||
3. **路由配置**
|
||||
- 创建了迁移后的系统管理路由配置 (`system-migrated.ts`)
|
||||
|
||||
### 🚧 待完成迁移
|
||||
|
||||
1. **角色管理模块**
|
||||
- 角色列表页面
|
||||
- 角色权限配置
|
||||
- 角色编辑功能
|
||||
|
||||
2. **菜单管理模块**
|
||||
- 菜单列表页面
|
||||
- 菜单编辑功能
|
||||
- 菜单权限配置
|
||||
|
||||
3. **部门管理模块**
|
||||
- 部门列表页面
|
||||
- 部门编辑功能
|
||||
|
||||
4. **站点管理模块**
|
||||
- 站点列表页面
|
||||
- 站点分组管理
|
||||
- 站点配置功能
|
||||
|
||||
5. **DIY装修模块**
|
||||
- 页面编辑器
|
||||
- 组件库管理
|
||||
- 预览与发布功能
|
||||
|
||||
6. **渠道管理模块**
|
||||
- 微信小程序配置
|
||||
- 微信公众号配置
|
||||
- APP配置
|
||||
- H5配置
|
||||
- PC配置
|
||||
|
||||
## 技术栈对比
|
||||
|
||||
| 功能 | Java Admin | Vben |
|
||||
|------|-----------|------|
|
||||
| UI框架 | Element Plus | Ant Design Vue |
|
||||
| 状态管理 | Pinia | Pinia + @vben/stores |
|
||||
| 路由 | Vue Router | Vue Router + 动态路由 |
|
||||
| 请求库 | Axios | Axios + @vben/request |
|
||||
| 国际化 | vue-i18n | @vben/locales |
|
||||
| 表单 | Element Plus Form | Vben Form + Ant Design Form |
|
||||
| 表格 | Element Plus Table | Ant Design Table + vxe-table |
|
||||
|
||||
## 迁移策略
|
||||
|
||||
### 1. 保持API兼容性
|
||||
- 所有API接口保持与Java后端一致
|
||||
- 请求参数和响应数据结构不变
|
||||
- 错误处理机制保持一致
|
||||
|
||||
### 2. UI组件替换
|
||||
- Element Plus组件 → Ant Design Vue组件
|
||||
- 保持相同的用户体验和交互逻辑
|
||||
- 适配响应式设计
|
||||
|
||||
### 3. 状态管理适配
|
||||
- 保持业务逻辑不变
|
||||
- 适配Vben的状态管理架构
|
||||
- 保持数据流的一致性
|
||||
|
||||
### 4. 路由配置
|
||||
- 保持路由结构不变
|
||||
- 适配Vben的动态路由系统
|
||||
- 保持权限控制逻辑
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
admin-vben/
|
||||
├── apps/web-antd/src/
|
||||
│ ├── api/
|
||||
│ │ ├── core/
|
||||
│ │ │ ├── auth.ts # 认证API
|
||||
│ │ │ └── user.ts # 用户管理API
|
||||
│ │ └── index.ts # API导出
|
||||
│ ├── views/
|
||||
│ │ ├── _core/authentication/
|
||||
│ │ │ └── login-migrated.vue # 迁移后的登录页
|
||||
│ │ └── system/
|
||||
│ │ └── user/
|
||||
│ │ ├── index.vue # 用户管理页面
|
||||
│ │ └── components/
|
||||
│ │ └── user-edit-modal.vue # 用户编辑模态框
|
||||
│ ├── store/
|
||||
│ │ └── auth-migrated.ts # 适配的认证状态管理
|
||||
│ ├── locales/langs/zh-CN/
|
||||
│ │ └── system.json # 系统管理中文翻译
|
||||
│ └── router/routes/modules/
|
||||
│ └── system-migrated.ts # 迁移后的系统管理路由
|
||||
```
|
||||
|
||||
## 下一步计划
|
||||
|
||||
1. **完成核心模块迁移**
|
||||
- 角色管理
|
||||
- 菜单管理
|
||||
- 部门管理
|
||||
|
||||
2. **业务模块迁移**
|
||||
- 站点管理
|
||||
- DIY装修
|
||||
- 渠道管理
|
||||
|
||||
3. **测试与优化**
|
||||
- 功能测试
|
||||
- 性能优化
|
||||
- 用户体验优化
|
||||
|
||||
4. **部署与上线**
|
||||
- 构建配置
|
||||
- 部署脚本
|
||||
- 监控配置
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **保持向后兼容**
|
||||
- 不要修改后端API接口
|
||||
- 保持数据格式一致
|
||||
- 保持业务逻辑一致
|
||||
|
||||
2. **用户体验**
|
||||
- 保持操作习惯一致
|
||||
- 优化响应速度
|
||||
- 改善界面美观度
|
||||
|
||||
3. **代码质量**
|
||||
- 遵循Vben的开发规范
|
||||
- 保持代码整洁
|
||||
- 添加必要的注释
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题或建议,请联系开发团队。
|
||||
@@ -1,57 +1,50 @@
|
||||
import { baseRequestClient, requestClient } from '#/api/request';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
export namespace AuthApi {
|
||||
/** 登录接口参数 */
|
||||
export interface LoginParams {
|
||||
password?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/** 登录接口返回值 */
|
||||
export interface LoginResult {
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
data: string;
|
||||
status: number;
|
||||
}
|
||||
}
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* 登录接口
|
||||
* @param params 登录参数
|
||||
* @param loginType 登录类型: admin | site
|
||||
*/
|
||||
export async function loginApi(data: AuthApi.LoginParams) {
|
||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data, {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新accessToken
|
||||
*/
|
||||
export async function refreshTokenApi() {
|
||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>(
|
||||
'/auth/refresh',
|
||||
null,
|
||||
{
|
||||
withCredentials: true,
|
||||
},
|
||||
);
|
||||
export function loginApi(
|
||||
params: { username: string; password: string; captcha_code?: string },
|
||||
loginType: string,
|
||||
): Promise<AxiosResponse<any>> {
|
||||
return requestClient.get(`login/${loginType}`, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
export async function logoutApi() {
|
||||
return baseRequestClient.post('/auth/logout', null, {
|
||||
withCredentials: true,
|
||||
});
|
||||
export function logoutApi(): Promise<AxiosResponse<any>> {
|
||||
return requestClient.put('auth/logout', {}, { showErrorMessage: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户权限码
|
||||
* 获取用户权限菜单
|
||||
*/
|
||||
export async function getAccessCodesApi() {
|
||||
return requestClient.get<string[]>('/auth/codes');
|
||||
export function getAuthMenusApi(params?: Record<string, any>): Promise<AxiosResponse<any>> {
|
||||
return requestClient.get('auth/authmenu', { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取站点信息
|
||||
*/
|
||||
export function getSiteInfoApi(): Promise<AxiosResponse<any>> {
|
||||
return requestClient.get('auth/site');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录配置
|
||||
*/
|
||||
export function getLoginConfigApi(): Promise<AxiosResponse<any>> {
|
||||
return requestClient.get('login/config');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统版本信息
|
||||
*/
|
||||
export function getVersionsApi(): Promise<AxiosResponse<any>> {
|
||||
return requestClient.get('sys/info');
|
||||
}
|
||||
@@ -1,10 +1,57 @@
|
||||
import type { UserInfo } from '@vben/types';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* 获取用户列表
|
||||
*/
|
||||
export async function getUserInfoApi() {
|
||||
return requestClient.get<UserInfo>('/user/info');
|
||||
export function getUserListApi(params: {
|
||||
page: number;
|
||||
limit: number;
|
||||
username?: string;
|
||||
user_type?: string;
|
||||
}): Promise<AxiosResponse<any>> {
|
||||
return requestClient.get('site/user', { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户详情
|
||||
*/
|
||||
export function getUserInfoApi(userId: number): Promise<AxiosResponse<any>> {
|
||||
return requestClient.get(`site/user/${userId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户
|
||||
*/
|
||||
export function addUserApi(params: Record<string, any>): Promise<AxiosResponse<any>> {
|
||||
return requestClient.post('site/user', params, { showSuccessMessage: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑用户
|
||||
*/
|
||||
export function editUserApi(params: Record<string, any>): Promise<AxiosResponse<any>> {
|
||||
return requestClient.put(`site/user/${params.uid}`, params, { showSuccessMessage: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定用户
|
||||
*/
|
||||
export function lockUserApi(userId: number): Promise<AxiosResponse<any>> {
|
||||
return requestClient.put(`site/user/lock/${userId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解锁用户
|
||||
*/
|
||||
export function unlockUserApi(userId: number): Promise<AxiosResponse<any>> {
|
||||
return requestClient.put(`site/user/unlock/${userId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
export function deleteUserApi(userId: number): Promise<AxiosResponse<any>> {
|
||||
return requestClient.delete(`site/user/${userId}`);
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './core';
|
||||
export * from './examples';
|
||||
export * from './system';
|
||||
export * from './core/auth';
|
||||
export * from './core/user';
|
||||
@@ -1,73 +1,86 @@
|
||||
{
|
||||
"dept": {
|
||||
"list": "部门列表",
|
||||
"createTime": "创建时间",
|
||||
"deptName": "部门名称",
|
||||
"name": "部门",
|
||||
"operation": "操作",
|
||||
"parentDept": "上级部门",
|
||||
"remark": "备注",
|
||||
"status": "状态",
|
||||
"title": "部门管理"
|
||||
},
|
||||
"menu": {
|
||||
"list": "菜单列表",
|
||||
"activeIcon": "激活图标",
|
||||
"activePath": "激活路径",
|
||||
"activePathHelp": "跳转到当前路由时,需要激活的菜单路径。\n当不在导航菜单中显示时,需要指定激活路径",
|
||||
"activePathMustExist": "该路径未能找到有效的菜单",
|
||||
"advancedSettings": "其它设置",
|
||||
"affixTab": "固定在标签",
|
||||
"authCode": "权限标识",
|
||||
"badge": "徽章内容",
|
||||
"badgeVariants": "徽标样式",
|
||||
"badgeType": {
|
||||
"dot": "点",
|
||||
"none": "无",
|
||||
"normal": "文字",
|
||||
"title": "徽标类型"
|
||||
"system": {
|
||||
"title": "系统管理",
|
||||
"user": {
|
||||
"title": "用户管理",
|
||||
"accountNumber": "账号",
|
||||
"accountNumberPlaceholder": "请输入账号",
|
||||
"accountNumberRequired": "请输入账号",
|
||||
"realName": "真实姓名",
|
||||
"realNamePlaceholder": "请输入真实姓名",
|
||||
"realNameRequired": "请输入真实姓名",
|
||||
"password": "密码",
|
||||
"passwordPlaceholder": "请输入密码",
|
||||
"passwordPlaceholderEdit": "留空则不修改密码",
|
||||
"passwordRequired": "请输入密码",
|
||||
"role": "角色",
|
||||
"rolePlaceholder": "请选择角色",
|
||||
"roleRequired": "请选择角色",
|
||||
"mobile": "手机号",
|
||||
"mobilePlaceholder": "请输入手机号",
|
||||
"email": "邮箱",
|
||||
"emailPlaceholder": "请输入邮箱",
|
||||
"status": "状态",
|
||||
"statusUnlock": "正常",
|
||||
"statusLock": "锁定",
|
||||
"headImg": "头像",
|
||||
"roleName": "角色名称",
|
||||
"lastLoginTime": "最后登录时间",
|
||||
"lastLoginIP": "最后登录IP",
|
||||
"addUser": "新增用户",
|
||||
"editUser": "编辑用户",
|
||||
"lock": "锁定",
|
||||
"unlock": "解锁",
|
||||
"delete": "删除",
|
||||
"lockTips": "确定要锁定该用户吗?",
|
||||
"unlockTips": "确定要解锁该用户吗?",
|
||||
"deleteTips": "确定要删除该用户吗?",
|
||||
"administrator": "超级管理员",
|
||||
"adminDisabled": "系统管理员不可操作"
|
||||
},
|
||||
"component": "页面组件",
|
||||
"hideChildrenInMenu": "隐藏子菜单",
|
||||
"hideInBreadcrumb": "在面包屑中隐藏",
|
||||
"hideInMenu": "隐藏菜单",
|
||||
"hideInTab": "在标签栏中隐藏",
|
||||
"icon": "图标",
|
||||
"keepAlive": "缓存标签页",
|
||||
"linkSrc": "链接地址",
|
||||
"menuName": "菜单名称",
|
||||
"menuTitle": "标题",
|
||||
"name": "菜单",
|
||||
"operation": "操作",
|
||||
"parent": "上级菜单",
|
||||
"path": "路由地址",
|
||||
"status": "状态",
|
||||
"title": "菜单管理",
|
||||
"type": "类型",
|
||||
"typeButton": "按钮",
|
||||
"typeCatalog": "目录",
|
||||
"typeEmbedded": "内嵌",
|
||||
"typeLink": "外链",
|
||||
"typeMenu": "菜单"
|
||||
"role": {
|
||||
"title": "角色管理"
|
||||
},
|
||||
"menu": {
|
||||
"title": "菜单管理"
|
||||
},
|
||||
"dept": {
|
||||
"title": "部门管理"
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"title": "角色管理",
|
||||
"list": "角色列表",
|
||||
"name": "角色",
|
||||
"roleName": "角色名称",
|
||||
"id": "角色ID",
|
||||
"status": "状态",
|
||||
"remark": "备注",
|
||||
"createTime": "创建时间",
|
||||
"common": {
|
||||
"search": "搜索",
|
||||
"reset": "重置",
|
||||
"add": "新增",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"lock": "锁定",
|
||||
"unlock": "解锁",
|
||||
"confirm": "确定",
|
||||
"cancel": "取消",
|
||||
"save": "保存",
|
||||
"close": "关闭",
|
||||
"operation": "操作",
|
||||
"permissions": "权限",
|
||||
"setPermissions": "授权"
|
||||
"total": "共 {total} 条",
|
||||
"enable": "启用",
|
||||
"disable": "禁用",
|
||||
"warning": "提示"
|
||||
},
|
||||
"title": "系统管理",
|
||||
"layout": {
|
||||
"header": "头部",
|
||||
"sider": "侧边栏",
|
||||
"footer": "底部",
|
||||
"content": "内容"
|
||||
"authentication": {
|
||||
"username": "用户名",
|
||||
"password": "密码",
|
||||
"usernameTip": "请输入用户名",
|
||||
"passwordTip": "请输入密码",
|
||||
"platformLogin": "平台登录",
|
||||
"siteLogin": "站点登录",
|
||||
"welcome": "欢迎登录",
|
||||
"welcomeLogin": "欢迎登录",
|
||||
"platform": "管理后台",
|
||||
"platformDesc": "专业的管理系统",
|
||||
"siteTitle": "管理系统",
|
||||
"loginSuccess": "登录成功",
|
||||
"loginSuccessDesc": "欢迎回来",
|
||||
"selectAccount": "选择账号",
|
||||
"verifyRequiredTip": "请完成验证"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'ion:settings-outline',
|
||||
order: 9997,
|
||||
title: $t('system.title'),
|
||||
},
|
||||
name: 'System',
|
||||
path: '/system',
|
||||
children: [
|
||||
{
|
||||
path: '/system/user',
|
||||
name: 'SystemUser',
|
||||
meta: {
|
||||
icon: 'mdi:account-circle-outline',
|
||||
title: $t('system.user.title'),
|
||||
},
|
||||
component: () => import('#/views/system/user/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/role',
|
||||
name: 'SystemRole',
|
||||
meta: {
|
||||
icon: 'mdi:account-group',
|
||||
title: $t('system.role.title'),
|
||||
},
|
||||
component: () => import('#/views/system/role/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/menu',
|
||||
name: 'SystemMenu',
|
||||
meta: {
|
||||
icon: 'mdi:menu',
|
||||
title: $t('system.menu.title'),
|
||||
},
|
||||
component: () => import('#/views/system/menu/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/dept',
|
||||
name: 'SystemDept',
|
||||
meta: {
|
||||
icon: 'charm:organisation',
|
||||
title: $t('system.dept.title'),
|
||||
},
|
||||
component: () => import('#/views/system/dept/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
204
admin-vben/apps/web-antd/src/store/auth-migrated.ts
Normal file
204
admin-vben/apps/web-antd/src/store/auth-migrated.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import type { Recordable, UserInfo } from '@vben/types';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { getAuthMenusApi, getSiteInfoApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const accessStore = useAccessStore();
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
|
||||
const loginLoading = ref(false);
|
||||
|
||||
/**
|
||||
* 异步处理登录操作(适配Java admin逻辑)
|
||||
* @param params 登录表单数据 { username, password, loginType }
|
||||
* @param onSuccess 成功之后的回调函数
|
||||
*/
|
||||
async function authLogin(
|
||||
params: Recordable<any>,
|
||||
onSuccess?: () => Promise<void> | void,
|
||||
) {
|
||||
let userInfo: null | UserInfo & { siteInfo?: any; userrole?: any[] } = null;
|
||||
try {
|
||||
loginLoading.value = true;
|
||||
|
||||
// 调用Java admin的登录API
|
||||
const loginResponse = await loginApi(
|
||||
{
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
captcha_code: params.captcha_code,
|
||||
},
|
||||
params.loginType || 'admin',
|
||||
);
|
||||
|
||||
// Java admin返回的数据结构处理
|
||||
const { data } = loginResponse;
|
||||
|
||||
if (data && data.token) {
|
||||
// 设置访问令牌
|
||||
accessStore.setAccessToken(data.token);
|
||||
|
||||
// 获取用户信息和权限信息
|
||||
const [fetchUserInfoResult, authMenus, siteInfo] = await Promise.all([
|
||||
getUserInfoApi(),
|
||||
getAuthMenusApi(),
|
||||
getSiteInfoApi(),
|
||||
]);
|
||||
|
||||
userInfo = {
|
||||
...fetchUserInfoResult,
|
||||
siteInfo: siteInfo.data,
|
||||
userrole: data.userrole || [],
|
||||
};
|
||||
|
||||
// 存储用户信息
|
||||
userStore.setUserInfo(userInfo);
|
||||
|
||||
// 存储权限信息到accessStore
|
||||
if (authMenus.data) {
|
||||
accessStore.setAccessCodes(authMenus.data);
|
||||
}
|
||||
|
||||
// 处理登录过期状态
|
||||
if (accessStore.loginExpired) {
|
||||
accessStore.setLoginExpired(false);
|
||||
} else {
|
||||
// 登录成功后的跳转逻辑
|
||||
if (onSuccess) {
|
||||
await onSuccess?.();
|
||||
} else {
|
||||
// Java admin的跳转逻辑
|
||||
if (params.loginType === 'admin' && (!data.userrole || data.userrole.length === 0)) {
|
||||
// 平台端登录且没有角色,跳转到首页
|
||||
await router.push('/home/index');
|
||||
} else {
|
||||
// 根据登录类型跳转到对应首页
|
||||
const homePath = params.loginType === 'admin' ? '/admin' : '/site';
|
||||
await router.push(homePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 登录成功提示
|
||||
if (userInfo?.realName) {
|
||||
notification.success({
|
||||
description: `${$t('authentication.loginSuccessDesc')}:${userInfo.realName}`,
|
||||
duration: 3,
|
||||
message: $t('authentication.loginSuccess'),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
loginLoading.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
userInfo,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录(适配Java admin逻辑)
|
||||
*/
|
||||
async function logout(redirect: boolean = true) {
|
||||
try {
|
||||
await logoutApi();
|
||||
} catch {
|
||||
// 不做任何处理
|
||||
}
|
||||
|
||||
// 重置所有状态
|
||||
resetAllStores();
|
||||
accessStore.setLoginExpired(false);
|
||||
|
||||
// 清除本地存储的Java admin相关数据
|
||||
localStorage.removeItem('admin.token');
|
||||
localStorage.removeItem('admin.userinfo');
|
||||
localStorage.removeItem('admin.siteInfo');
|
||||
localStorage.removeItem('site.token');
|
||||
localStorage.removeItem('site.userinfo');
|
||||
localStorage.removeItem('site.siteInfo');
|
||||
localStorage.removeItem('siteId');
|
||||
localStorage.removeItem('comparisonSiteIdStorage');
|
||||
localStorage.removeItem('comparisonTokenStorage');
|
||||
|
||||
// 回登录页带上当前路由地址
|
||||
await router.replace({
|
||||
path: LOGIN_PATH,
|
||||
query: redirect
|
||||
? {
|
||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||
}
|
||||
: {},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
async function fetchUserInfo() {
|
||||
let userInfo: null | UserInfo = null;
|
||||
try {
|
||||
userInfo = await getUserInfoApi();
|
||||
userStore.setUserInfo(userInfo);
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限菜单(适配Java admin逻辑)
|
||||
*/
|
||||
async function fetchAuthMenus() {
|
||||
try {
|
||||
const response = await getAuthMenusApi();
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取权限菜单失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取站点信息
|
||||
*/
|
||||
async function fetchSiteInfo() {
|
||||
try {
|
||||
const response = await getSiteInfoApi();
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取站点信息失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function $reset() {
|
||||
loginLoading.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
$reset,
|
||||
authLogin,
|
||||
fetchUserInfo,
|
||||
fetchAuthMenus,
|
||||
fetchSiteInfo,
|
||||
loginLoading,
|
||||
logout,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,256 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
import { getLoginConfig } from '#/api';
|
||||
|
||||
// 定义组件名称
|
||||
defineOptions({ name: 'LoginMigrated' });
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const router = useRouter();
|
||||
|
||||
// 登录类型:admin(平台) 或 site(站点)
|
||||
const loginType = ref<'admin' | 'site'>('admin');
|
||||
const loading = ref(false);
|
||||
const loginConfig = ref<any>(null);
|
||||
|
||||
// 获取登录配置
|
||||
const getLoginConfigFn = async () => {
|
||||
try {
|
||||
const res = await getLoginConfig();
|
||||
loginConfig.value = res.data;
|
||||
} catch (error) {
|
||||
console.error('获取登录配置失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 组件挂载时获取配置
|
||||
getLoginConfigFn();
|
||||
|
||||
// 动态背景样式
|
||||
const backgroundStyle = computed(() => {
|
||||
if (loginType.value === 'site' && loginConfig.value?.site_login_bg_img) {
|
||||
return {
|
||||
backgroundImage: `url(${loginConfig.value.site_login_bg_img})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
// 登录表单配置
|
||||
const formSchema = computed((): VbenFormSchema[] => [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.username'),
|
||||
size: 'large',
|
||||
allowClear: true,
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.password'),
|
||||
size: 'large',
|
||||
allowClear: true,
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
]);
|
||||
|
||||
// 登录提交处理
|
||||
async function onSubmit(params: Recordable<any>) {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
// 检查是否需要验证码
|
||||
const needCaptcha = loginType.value === 'admin'
|
||||
? loginConfig.value?.is_captcha
|
||||
: loginConfig.value?.is_site_captcha;
|
||||
|
||||
if (needCaptcha) {
|
||||
// TODO: 集成验证码组件
|
||||
console.log('需要验证码验证');
|
||||
}
|
||||
|
||||
// 调用登录API
|
||||
await authStore.authLogin({
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
loginType: loginType.value,
|
||||
});
|
||||
|
||||
// 登录成功后的跳转逻辑
|
||||
const redirect = router.currentRoute.value.query.redirect as string;
|
||||
if (redirect) {
|
||||
router.push(redirect);
|
||||
} else {
|
||||
// 根据登录类型跳转到对应首页
|
||||
if (loginType.value === 'admin') {
|
||||
router.push('/admin');
|
||||
} else {
|
||||
router.push('/site');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 切换登录类型
|
||||
const toggleLoginType = (type: 'admin' | 'site') => {
|
||||
loginType.value = type;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center relative" :style="backgroundStyle">
|
||||
<!-- 登录类型切换 -->
|
||||
<div class="absolute top-4 right-4">
|
||||
<a-space>
|
||||
<a-button
|
||||
:type="loginType === 'admin' ? 'primary' : 'default'"
|
||||
@click="toggleLoginType('admin')"
|
||||
>
|
||||
{{ $t('authentication.platformLogin') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
:type="loginType === 'site' ? 'primary' : 'default'"
|
||||
@click="toggleLoginType('site')"
|
||||
>
|
||||
{{ $t('authentication.siteLogin') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- 平台端登录 -->
|
||||
<div v-if="loginType === 'admin'" class="w-full max-w-4xl mx-auto">
|
||||
<div class="bg-white rounded-lg shadow-lg overflow-hidden">
|
||||
<div class="flex">
|
||||
<!-- 左侧图片区域 -->
|
||||
<div class="w-1/2 hidden md:block">
|
||||
<img
|
||||
v-if="loginConfig?.bg"
|
||||
:src="loginConfig.bg"
|
||||
alt="Login Background"
|
||||
class="w-full h-96 object-cover"
|
||||
/>
|
||||
<div v-else class="w-full h-96 bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
|
||||
<div class="text-white text-center">
|
||||
<h2 class="text-2xl font-bold mb-2">{{ $t('authentication.welcome') }}</h2>
|
||||
<p class="text-blue-100">{{ $t('authentication.platformDesc') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧登录表单 -->
|
||||
<div class="w-full md:w-1/2 p-8">
|
||||
<div class="max-w-md mx-auto">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">
|
||||
{{ loginConfig?.site_name || $t('authentication.siteTitle') }}
|
||||
</h1>
|
||||
<p class="text-gray-600">{{ $t('authentication.platform') }}</p>
|
||||
</div>
|
||||
|
||||
<AuthenticationLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:submit-button-props="{ size: 'large', block: true }"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 站点端登录 -->
|
||||
<div v-else class="w-full max-w-md mx-auto">
|
||||
<div class="bg-white rounded-lg shadow-lg p-8">
|
||||
<!-- Logo区域 -->
|
||||
<div class="text-center mb-8">
|
||||
<div v-if="loginConfig?.site_login_logo" class="w-32 h-12 mx-auto mb-4">
|
||||
<img
|
||||
:src="loginConfig.site_login_logo"
|
||||
alt="Site Logo"
|
||||
class="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">
|
||||
{{ loginConfig?.site_name || $t('authentication.siteTitle') }}
|
||||
</h1>
|
||||
<p class="text-gray-600 mt-2">{{ $t('authentication.welcomeLogin') }}</p>
|
||||
</div>
|
||||
|
||||
<AuthenticationLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:submit-button-props="{ size: 'large', block: true }"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 版权信息 -->
|
||||
<div class="absolute bottom-4 left-1/2 transform -translate-x-1/2 text-center text-sm text-gray-500">
|
||||
<div v-if="loginConfig?.copyright" class="space-x-4">
|
||||
<a v-if="loginConfig.copyright.copyright_link"
|
||||
:href="loginConfig.copyright.copyright_link"
|
||||
target="_blank"
|
||||
class="hover:text-blue-600"
|
||||
>
|
||||
{{ loginConfig.copyright.copyright_desc }}
|
||||
</a>
|
||||
<span v-if="loginConfig.copyright.company_name">
|
||||
{{ loginConfig.copyright.company_name }}
|
||||
</span>
|
||||
<a v-if="loginConfig.copyright.icp"
|
||||
href="https://beian.miit.gov.cn/"
|
||||
target="_blank"
|
||||
class="hover:text-blue-600"
|
||||
>
|
||||
{{ loginConfig.copyright.icp }}
|
||||
</a>
|
||||
<a v-if="loginConfig.copyright.gov_record"
|
||||
:href="loginConfig.copyright.gov_url"
|
||||
target="_blank"
|
||||
class="hover:text-blue-600"
|
||||
>
|
||||
{{ loginConfig.copyright.gov_record }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 响应式样式 */
|
||||
@media (max-width: 768px) {
|
||||
.md\:w-1\/2 {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.md\:block {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,231 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { Button, Form, Input, Modal, Select, Switch } from 'ant-design-vue';
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { addUserApi, editUserApi } from '#/api';
|
||||
|
||||
// 表单数据
|
||||
const formState = ref({
|
||||
uid: undefined as number | undefined,
|
||||
username: '',
|
||||
real_name: '',
|
||||
password: '',
|
||||
role_ids: [] as number[],
|
||||
status: 1,
|
||||
head_img: '',
|
||||
mobile: '',
|
||||
email: '',
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const isEdit = computed(() => !!formState.value.uid);
|
||||
|
||||
// 角色选项(需要从后端获取)
|
||||
const roleOptions = ref([
|
||||
{ label: '管理员', value: 1 },
|
||||
{ label: '运营', value: 2 },
|
||||
{ label: '客服', value: 3 },
|
||||
]);
|
||||
|
||||
// 表单配置
|
||||
const formSchema: VbenFormSchema[] = [
|
||||
{
|
||||
fieldName: 'username',
|
||||
label: $t('sys.user.accountNumber'),
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: $t('sys.user.accountNumberPlaceholder'),
|
||||
disabled: isEdit,
|
||||
},
|
||||
rules: [{ required: true, message: $t('sys.user.accountNumberRequired') }],
|
||||
},
|
||||
{
|
||||
fieldName: 'real_name',
|
||||
label: $t('sys.user.realName'),
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: $t('sys.user.realNamePlaceholder'),
|
||||
},
|
||||
rules: [{ required: true, message: $t('sys.user.realNameRequired') }],
|
||||
},
|
||||
{
|
||||
fieldName: 'password',
|
||||
label: $t('sys.user.password'),
|
||||
component: 'InputPassword',
|
||||
componentProps: {
|
||||
placeholder: isEdit ? $t('sys.user.passwordPlaceholderEdit') : $t('sys.user.passwordPlaceholder'),
|
||||
},
|
||||
rules: isEdit.value ? [] : [{ required: true, message: $t('sys.user.passwordRequired') }],
|
||||
},
|
||||
{
|
||||
fieldName: 'role_ids',
|
||||
label: $t('sys.user.role'),
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
mode: 'multiple',
|
||||
placeholder: $t('sys.user.rolePlaceholder'),
|
||||
options: roleOptions.value,
|
||||
},
|
||||
rules: [{ required: true, message: $t('sys.user.roleRequired') }],
|
||||
},
|
||||
{
|
||||
fieldName: 'mobile',
|
||||
label: $t('sys.user.mobile'),
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: $t('sys.user.mobilePlaceholder'),
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'email',
|
||||
label: $t('sys.user.email'),
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: $t('sys.user.emailPlaceholder'),
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: $t('sys.user.status'),
|
||||
component: 'Switch',
|
||||
componentProps: {
|
||||
checkedChildren: $t('common.enable'),
|
||||
unCheckedChildren: $t('common.disable'),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 模态框API
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
draggable: true,
|
||||
title: computed(() => (isEdit.value ? $t('sys.user.editUser') : $t('sys.user.addUser'))),
|
||||
onConfirm: handleSubmit,
|
||||
onOpenChange: handleOpenChange,
|
||||
});
|
||||
|
||||
// 处理模态框打开
|
||||
function handleOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
const data = modalApi.getData();
|
||||
if (data) {
|
||||
// 编辑模式,填充表单数据
|
||||
Object.keys(formState.value).forEach((key) => {
|
||||
if (data[key] !== undefined) {
|
||||
(formState.value as any)[key] = data[key];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 新增模式,重置表单
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
function resetForm() {
|
||||
formState.value = {
|
||||
uid: undefined,
|
||||
username: '',
|
||||
real_name: '',
|
||||
password: '',
|
||||
role_ids: [],
|
||||
status: 1,
|
||||
head_img: '',
|
||||
mobile: '',
|
||||
email: '',
|
||||
};
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
const params = {
|
||||
...formState.value,
|
||||
role_ids: formState.value.role_ids.join(','),
|
||||
};
|
||||
|
||||
if (isEdit.value) {
|
||||
// 编辑用户
|
||||
await editUserApi(params);
|
||||
} else {
|
||||
// 新增用户
|
||||
await addUserApi(params);
|
||||
}
|
||||
|
||||
modalApi.close();
|
||||
|
||||
// 通知父组件刷新数据
|
||||
const callback = modalApi.getData()?.callback;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存用户失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
open: modalApi.open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal>
|
||||
<VbenForm
|
||||
:schema="formSchema"
|
||||
:model="formState"
|
||||
:loading="loading"
|
||||
label-col="{ span: 6 }"
|
||||
wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<template #default="{ form }">
|
||||
<Form.Item
|
||||
v-for="field in formSchema"
|
||||
:key="field.fieldName"
|
||||
:label="field.label"
|
||||
:name="field.fieldName"
|
||||
:rules="field.rules"
|
||||
>
|
||||
<template v-if="field.component === 'Input'">
|
||||
<Input
|
||||
v-model:value="formState[field.fieldName as keyof typeof formState]"
|
||||
v-bind="field.componentProps"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="field.component === 'InputPassword'">
|
||||
<Input.Password
|
||||
v-model:value="formState[field.fieldName as keyof typeof formState]"
|
||||
v-bind="field.componentProps"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="field.component === 'Select'">
|
||||
<Select
|
||||
v-model:value="formState[field.fieldName as keyof typeof formState]"
|
||||
v-bind="field.componentProps"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="field.component === 'Switch'">
|
||||
<Switch
|
||||
v-model:checked="formState[field.fieldName as keyof typeof formState]"
|
||||
v-bind="field.componentProps"
|
||||
/>
|
||||
</template>
|
||||
</Form.Item>
|
||||
</template>
|
||||
</VbenForm>
|
||||
</Modal>
|
||||
</template>
|
||||
320
admin-vben/apps/web-antd/src/views/system/user/index.vue
Normal file
320
admin-vben/apps/web-antd/src/views/system/user/index.vue
Normal file
@@ -0,0 +1,320 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { computed, h, onMounted, ref } from 'vue';
|
||||
|
||||
import { Avatar, Button, Card, Modal, Space, Table, Tag } from 'ant-design-vue';
|
||||
import { DeleteOutlined, EditOutlined, LockOutlined, UnlockOutlined, UserAddOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { addUserApi, deleteUserApi, editUserApi, getUserListApi, lockUserApi, unlockUserApi } from '#/api';
|
||||
|
||||
import UserEditModal from './components/user-edit-modal.vue';
|
||||
|
||||
// 用户数据
|
||||
const loading = ref(false);
|
||||
const dataSource = ref<any[]>([]);
|
||||
const total = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const searchValue = ref('');
|
||||
|
||||
// 搜索表单配置
|
||||
const searchFormSchema: VbenFormSchema[] = [
|
||||
{
|
||||
fieldName: 'search',
|
||||
label: $t('sys.user.accountNumber'),
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: $t('sys.user.accountNumberPlaceholder'),
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 表格列配置
|
||||
const columns = computed(() => [
|
||||
{
|
||||
title: $t('sys.user.headImg'),
|
||||
dataIndex: 'head_img',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
customRender: ({ record }: any) => {
|
||||
return h(Avatar, {
|
||||
src: record.head_img || '/src/assets/images/member_head.png',
|
||||
size: 40,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: $t('sys.user.accountNumber'),
|
||||
dataIndex: 'username',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: $t('sys.user.realName'),
|
||||
dataIndex: 'real_name',
|
||||
width: 120,
|
||||
customRender: ({ text }: any) => text || '--',
|
||||
},
|
||||
{
|
||||
title: $t('sys.user.roleName'),
|
||||
dataIndex: 'role_array',
|
||||
width: 150,
|
||||
customRender: ({ record }: any) => {
|
||||
if (record.is_admin) {
|
||||
return h(Tag, { color: 'red' }, () => $t('sys.user.administrator'));
|
||||
}
|
||||
if (record.role_array && record.role_array.length > 0) {
|
||||
return record.role_array.join(' | ');
|
||||
}
|
||||
return '--';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: $t('sys.user.status'),
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
customRender: ({ record }: any) => {
|
||||
return record.status === 1
|
||||
? h(Tag, { color: 'success' }, () => $t('sys.user.statusUnlock'))
|
||||
: h(Tag, { color: 'error' }, () => $t('sys.user.statusLock'));
|
||||
},
|
||||
},
|
||||
{
|
||||
title: $t('sys.user.lastLoginTime'),
|
||||
dataIndex: 'last_time',
|
||||
width: 180,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: $t('sys.user.lastLoginIP'),
|
||||
dataIndex: 'last_ip',
|
||||
width: 150,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: $t('common.operation'),
|
||||
dataIndex: 'operation',
|
||||
width: 200,
|
||||
align: 'right',
|
||||
fixed: 'right',
|
||||
customRender: ({ record }: any) => {
|
||||
if (record.is_admin) {
|
||||
return h('span', { style: { color: '#999' } }, $t('sys.user.adminDisabled'));
|
||||
}
|
||||
|
||||
return h(Space, null, () => [
|
||||
h(Button, {
|
||||
type: 'link',
|
||||
size: 'small',
|
||||
icon: h(EditOutlined),
|
||||
onClick: () => handleEdit(record),
|
||||
}, () => $t('common.edit')),
|
||||
|
||||
record.status === 1
|
||||
? h(Button, {
|
||||
type: 'link',
|
||||
size: 'small',
|
||||
icon: h(LockOutlined),
|
||||
danger: true,
|
||||
onClick: () => handleLock(record.uid),
|
||||
}, () => $t('common.lock'))
|
||||
: h(Button, {
|
||||
type: 'link',
|
||||
size: 'small',
|
||||
icon: h(UnlockOutlined),
|
||||
onClick: () => handleUnlock(record.uid),
|
||||
}, () => $t('common.unlock')),
|
||||
|
||||
h(Button, {
|
||||
type: 'link',
|
||||
size: 'small',
|
||||
icon: h(DeleteOutlined),
|
||||
danger: true,
|
||||
onClick: () => handleDelete(record.uid),
|
||||
}, () => $t('common.delete')),
|
||||
]);
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// 用户编辑模态框
|
||||
const [UserEditModalApi, userEditModalRef] = useVbenModal({
|
||||
connectedComponent: UserEditModal,
|
||||
});
|
||||
|
||||
// 加载用户列表
|
||||
const loadUserList = async (page = 1) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const params = {
|
||||
page,
|
||||
limit: pageSize.value,
|
||||
username: searchValue.value,
|
||||
};
|
||||
|
||||
const response = await getUserListApi(params);
|
||||
dataSource.value = response.data.data;
|
||||
total.value = response.data.total;
|
||||
currentPage.value = page;
|
||||
} catch (error) {
|
||||
console.error('加载用户列表失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = (values: any) => {
|
||||
searchValue.value = values?.search || '';
|
||||
loadUserList(1);
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
searchValue.value = '';
|
||||
loadUserList(1);
|
||||
};
|
||||
|
||||
// 添加用户
|
||||
const handleAdd = () => {
|
||||
UserEditModalApi.open({
|
||||
title: $t('sys.user.addUser'),
|
||||
});
|
||||
};
|
||||
|
||||
// 编辑用户
|
||||
const handleEdit = (record: any) => {
|
||||
UserEditModalApi.open({
|
||||
title: $t('sys.user.editUser'),
|
||||
data: record,
|
||||
});
|
||||
};
|
||||
|
||||
// 锁定用户
|
||||
const handleLock = async (userId: number) => {
|
||||
Modal.confirm({
|
||||
title: $t('common.warning'),
|
||||
content: $t('sys.user.lockTips'),
|
||||
onOk: async () => {
|
||||
try {
|
||||
await lockUserApi(userId);
|
||||
loadUserList(currentPage.value);
|
||||
} catch (error) {
|
||||
console.error('锁定用户失败:', error);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 解锁用户
|
||||
const handleUnlock = async (userId: number) => {
|
||||
Modal.confirm({
|
||||
title: $t('common.warning'),
|
||||
content: $t('sys.user.unlockTips'),
|
||||
onOk: async () => {
|
||||
try {
|
||||
await unlockUserApi(userId);
|
||||
loadUserList(currentPage.value);
|
||||
} catch (error) {
|
||||
console.error('解锁用户失败:', error);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 删除用户
|
||||
const handleDelete = async (userId: number) => {
|
||||
Modal.confirm({
|
||||
title: $t('common.warning'),
|
||||
content: $t('sys.user.deleteTips'),
|
||||
onOk: async () => {
|
||||
try {
|
||||
await deleteUserApi(userId);
|
||||
loadUserList(currentPage.value);
|
||||
} catch (error) {
|
||||
console.error('删除用户失败:', error);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 表格分页变化
|
||||
const handleTableChange = (pagination: any) => {
|
||||
currentPage.value = pagination.current;
|
||||
pageSize.value = pagination.pageSize;
|
||||
loadUserList(pagination.current);
|
||||
};
|
||||
|
||||
// 模态框操作完成
|
||||
const handleModalComplete = () => {
|
||||
loadUserList(currentPage.value);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadUserList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto p-4">
|
||||
<Card>
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>{{ $t('sys.user.title') }}</span>
|
||||
<Button type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<UserAddOutlined />
|
||||
</template>
|
||||
{{ $t('sys.user.addUser') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="mb-4">
|
||||
<VbenForm
|
||||
:schema="searchFormSchema"
|
||||
:show-default-actions="false"
|
||||
@submit="handleSearch"
|
||||
@reset="handleReset"
|
||||
>
|
||||
<template #actions="{ reset, submit }">
|
||||
<Space>
|
||||
<Button type="primary" @click="submit">
|
||||
{{ $t('common.search') }}
|
||||
</Button>
|
||||
<Button @click="reset">
|
||||
{{ $t('common.reset') }}
|
||||
</Button>
|
||||
</Space>
|
||||
</template>
|
||||
</VbenForm>
|
||||
</div>
|
||||
|
||||
<!-- 用户表格 -->
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
:pagination="{
|
||||
current: currentPage,
|
||||
pageSize: pageSize,
|
||||
total: total,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => $t('common.total', { total }),
|
||||
}"
|
||||
row-key="uid"
|
||||
@change="handleTableChange"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<!-- 用户编辑模态框 -->
|
||||
<user-edit-modal ref="userEditModalRef" @complete="handleModalComplete" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -9,7 +9,7 @@
|
||||
<el-button type="primary" class="w-[100px]" @click="addEvent">
|
||||
{{ t('addMenu') }}
|
||||
</el-button>
|
||||
<el-button class="w-[100px]" @click="refreshMenu">
|
||||
<el-button class="w-[100px]" :loading="refreshLoading" @click="refreshMenu">
|
||||
{{ t('initializeMenu') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -82,6 +82,7 @@ const getMenuList = () => {
|
||||
}
|
||||
getMenuList()
|
||||
// 重置菜单
|
||||
const refreshLoading = ref(false)
|
||||
const refreshMenu = () => {
|
||||
ElMessageBox.confirm(h('div', null, [
|
||||
h('p', null, t('initializeMenuTipsOne')),
|
||||
@@ -93,9 +94,12 @@ const refreshMenu = () => {
|
||||
// type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
refreshLoading.value = true
|
||||
menuRefresh({}).then(res => {
|
||||
location.reload()
|
||||
refreshLoading.value = false
|
||||
}).catch(() => {
|
||||
refreshLoading.value = false
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user