fix: 修复前端API调用和响应处理,补全i18n多语言包

1. 修复启动卡住问题
   - 创建专用 authRequestClient 连接到认证中心
   - 修复认证相关API路由错误

2. 修复API响应处理
   - 移除错误的 defaultResponseInterceptor
   - 添加自定义响应拦截器直接返回 response.data
   - 修复分页数据提取(response.items)

3. 修复类型定义
   - Tenant: contactPhone 可选,status 改为 string
   - User: 添加 phone, tenantId, tenantName, emailConfirmed
   - Role: 添加 tenantId, isSystem, userCount
   - OAuth: 修改 Id 类型为 string
   - 修正所有 DTO 的必填字段

4. 修复查询参数
   - 租户列表使用 name 而不是 keyword

5. 补全 i18n 多语言包
   - 添加 app, tenant, user, role, oauth, common 模块
   - 中文:"蜂铃管理平台"、"蜂铃控制台"
   - 英文:Fengling Management Platform、Fengling Console

6. 修复租户管理逻辑
   - 添加 editingId 跟踪编辑状态
   - 修复 handleSave 使用正确的租户ID
This commit is contained in:
Sam 2026-02-08 20:18:36 +08:00
parent fa93d71725
commit d2d70b462e
13 changed files with 460 additions and 97 deletions

View File

@ -13,7 +13,7 @@ VITE_AUTH_SERVER_URL=http://localhost:5132
VITE_AUTH_SERVICE_URL=http://localhost:5132
VITE_OAUTH_CLIENT_ID=fengling-console
VITE_OAUTH_REDIRECT_URI=http://localhost:5777/auth/callback
VITE_OAUTH_SCOPE=api offline_access openid profile email
VITE_OAUTH_SCOPE=fengling_api offline_access openid profile email
# 是否开启 Nitro Mock服务true 为开启false 为关闭
VITE_NITRO_MOCK=false

View File

@ -1,5 +1,5 @@
import type { User } from 'oidc-client-ts';
import { baseRequestClient } from '#/api/request';
import { authRequestClient, baseRequestClient } from '#/api/request';
import { ElNotification } from 'element-plus';
export namespace AuthApi {
@ -51,7 +51,7 @@ export function getUserInfoFromToken(user: AuthApi.OidcUser): AuthApi.UserInfo {
*/
export async function getUserInfoApi(): Promise<AuthApi.UserInfo> {
try {
return await baseRequestClient.get<AuthApi.UserInfo>('/connect/userinfo', {
return await authRequestClient.get<AuthApi.UserInfo>('/connect/userinfo', {
withCredentials: true,
});
} catch (error: any) {
@ -73,7 +73,7 @@ export async function getUserInfoApi(): Promise<AuthApi.UserInfo> {
*/
export async function logoutApi() {
try {
await baseRequestClient.post('/connect/logout', {
await authRequestClient.post('/connect/logout', {
withCredentials: true,
});
} catch (error) {

View File

@ -3,6 +3,8 @@ import type { FenglingApi } from './typings';
import { requestClient } from '#/api/request';
export namespace LogApi {
const apiPrefix = '/console'
export async function getAuditLogList(params: {
page?: number
pageSize?: number
@ -13,7 +15,7 @@ export namespace LogApi {
endDate?: string
}) {
return requestClient.get<FenglingApi.PaginatedResponse<FenglingApi.Log.AuditLog>>(
'/logs/audit',
`${apiPrefix}/logs/audit`,
{ params }
);
}
@ -28,7 +30,7 @@ export namespace LogApi {
endDate?: string
}) {
return requestClient.get<FenglingApi.PaginatedResponse<FenglingApi.Log.AccessLog>>(
'/logs/access',
`${apiPrefix}/logs/access`,
{ params }
);
}

View File

@ -2,6 +2,8 @@ import type { FenglingApi } from './typings';
import { baseRequestClient } from '#/api/request';
export namespace OAuthApi {
const apiPrefix = '/console';
/**
* OAuth客户端列表
*/
@ -16,7 +18,7 @@ export namespace OAuthApi {
...(params.keyword ? { keyword: params.keyword } : {}),
});
return await baseRequestClient.get<FenglingApi.PaginatedResponse<FenglingApi.OAuth.OAuthClient>>(
`/oauth/clients?${queryParams.toString()}`,
`${apiPrefix}/oauth/clients?${queryParams.toString()}`,
);
}
@ -24,7 +26,7 @@ export namespace OAuthApi {
* OAuth客户端详情
*/
export async function getClient(id: number): Promise<FenglingApi.OAuth.OAuthClient> {
return await baseRequestClient.get<FenglingApi.OAuth.OAuthClient>(`/oauth/clients/${id}`);
return await baseRequestClient.get<FenglingApi.OAuth.OAuthClient>(`${apiPrefix}/oauth/clients/${id}`);
}
/**
@ -33,7 +35,7 @@ export namespace OAuthApi {
export async function createClient(
data: FenglingApi.OAuth.CreateOAuthClientDto,
): Promise<FenglingApi.OAuth.OAuthClient> {
return await baseRequestClient.post<FenglingApi.OAuth.OAuthClient>('/oauth/clients', data);
return await baseRequestClient.post<FenglingApi.OAuth.OAuthClient, FenglingApi.OAuth.CreateOAuthClientDto>(`${apiPrefix}/oauth/clients`, data);
}
/**
@ -43,27 +45,27 @@ export namespace OAuthApi {
id: number,
data: FenglingApi.OAuth.UpdateOAuthClientDto,
): Promise<FenglingApi.OAuth.OAuthClient> {
return await baseRequestClient.put<FenglingApi.OAuth.OAuthClient>(`/oauth/clients/${id}`, data);
return await baseRequestClient.put<FenglingApi.OAuth.OAuthClient, FenglingApi.OAuth.UpdateOAuthClientDto>(`${apiPrefix}/oauth/clients/${id}`, data);
}
/**
* OAuth客户端
*/
export async function deleteClient(id: number): Promise<void> {
await baseRequestClient.delete(`/oauth/clients/${id}`);
await baseRequestClient.delete<void>(`${apiPrefix}/oauth/clients/${id}`);
}
/**
* OAuth客户端密钥
*/
export async function resetClientSecret(id: number): Promise<{ clientSecret: string }> {
return await baseRequestClient.post<{ clientSecret: string }>(`/oauth/clients/${id}/reset-secret`);
return await baseRequestClient.post<{ clientSecret: string }>(`${apiPrefix}/oauth/clients/${id}/reset-secret`);
}
/**
* /OAuth客户端
*/
export async function toggleClientStatus(id: number, status: 'active' | 'inactive'): Promise<void> {
await baseRequestClient.put(`/oauth/clients/${id}/status`, { status });
await baseRequestClient.put<void, { status: string }>(`${apiPrefix}/oauth/clients/${id}/status`, { status });
}
}

View File

@ -3,34 +3,35 @@ import type { FenglingApi } from './typings';
import { requestClient } from '#/api/request';
export namespace RoleApi {
const apiPrefix = '/console'
export async function getRoleList(params: {
page?: number
pageSize?: number
keyword?: string
}) {
return requestClient.get<FenglingApi.PaginatedResponse<FenglingApi.Role.Role>>(
'/roles',
`${apiPrefix}/roles`,
{ params }
);
}
export async function getRoleById(id: number) {
return requestClient.get<FenglingApi.Role.Role>(`/roles/${id}`);
return requestClient.get<FenglingApi.Role.Role>(`${apiPrefix}/roles/${id}`);
}
export async function createRole(data: FenglingApi.Role.CreateRoleDto) {
return requestClient.post<FenglingApi.Role.Role>('/roles', data);
return requestClient.post<FenglingApi.Role.Role, FenglingApi.Role.CreateRoleDto>(`${apiPrefix}/roles`, data);
}
export async function updateRole(id: number, data: FenglingApi.Role.UpdateRoleDto) {
return requestClient.put<FenglingApi.Role.Role>(`/roles/${id}`, data);
return requestClient.put<void, FenglingApi.Role.UpdateRoleDto>(`${apiPrefix}/roles/${id}`, data);
}
export async function deleteRole(id: number) {
return requestClient.delete(`/roles/${id}`);
return requestClient.delete<void>(`${apiPrefix}/roles/${id}`);
}
export async function getAllRoles() {
return requestClient.get<FenglingApi.Role.Role[]>('/roles/all');
return requestClient.get<FenglingApi.Role.Role[]>(`${apiPrefix}/roles/all`);
}
}

View File

@ -3,53 +3,72 @@ import type { FenglingApi } from './typings';
import { requestClient } from '#/api/request';
export namespace TenantApi {
const apiPrefix = '/console';
export async function getTenantList(params: {
page?: number
pageSize?: number
keyword?: string
name?: string
tenantId?: string
status?: string
}) {
return requestClient.get<FenglingApi.PaginatedResponse<FenglingApi.Tenant.Tenant>>(
'/tenants',
`${apiPrefix}/tenants`,
{ params }
);
}
export async function getTenantById(id: number) {
return requestClient.get<FenglingApi.Tenant.Tenant>(`/tenants/${id}`);
return requestClient.get<FenglingApi.Tenant.Tenant>(`${apiPrefix}/tenants/${id}`);
}
export async function createTenant(data: FenglingApi.Tenant.CreateTenantDto) {
return requestClient.post<FenglingApi.Tenant.Tenant>('/tenants', data);
return requestClient.post<FenglingApi.Tenant.Tenant, FenglingApi.Tenant.CreateTenantDto>(`${apiPrefix}/tenants`, data);
}
export async function updateTenant(id: number, data: FenglingApi.Tenant.UpdateTenantDto) {
return requestClient.put<FenglingApi.Tenant.Tenant>(`/tenants/${id}`, data);
return requestClient.put<void, FenglingApi.Tenant.UpdateTenantDto>(`${apiPrefix}/tenants/${id}`, data);
}
export async function deleteTenant(id: number) {
return requestClient.delete(`/tenants/${id}`);
return requestClient.delete<void>(`${apiPrefix}/tenants/${id}`);
}
export async function activateTenant(id: number) {
return requestClient.post(`/tenants/${id}/activate`);
return requestClient.post<void>(`${apiPrefix}/tenants/${id}/activate`);
}
export async function deactivateTenant(id: number) {
return requestClient.post(`/tenants/${id}/deactivate`);
return requestClient.post<void>(`${apiPrefix}/tenants/${id}/deactivate`);
}
export async function extendTenantExpiry(id: number, expiresAt: string) {
return requestClient.post(`/tenants/${id}/extend`, { expiresAt });
return requestClient.post<void, { expiresAt: string }>(`${apiPrefix}/tenants/${id}/extend`, { expiresAt });
}
export async function getTenantUsers(id: number) {
return requestClient.get<FenglingApi.PaginatedResponse<FenglingApi.User.User>>(
`/tenants/${id}/users`
`${apiPrefix}/tenants/${id}/users`
);
}
export async function getTenantRoles(id: number) {
return requestClient.get<FenglingApi.PaginatedResponse<FenglingApi.Role.Role>>(
`${apiPrefix}/tenants/${id}/roles`
);
}
export async function getTenantStatistics(id: number) {
return requestClient.get(`/tenants/${id}/statistics`);
return requestClient.get<Record<string, any>>(`${apiPrefix}/tenants/${id}/statistics`);
}
export async function getTenantSettings(id: number) {
return requestClient.get<FenglingApi.Tenant.TenantSettings>(
`${apiPrefix}/tenants/${id}/settings`
);
}
export async function updateTenantSettings(id: number, data: FenglingApi.Tenant.TenantSettings) {
return requestClient.put<void, FenglingApi.Tenant.TenantSettings>(`${apiPrefix}/tenants/${id}/settings`, data);
}
}

View File

@ -6,10 +6,10 @@ export namespace FenglingApi {
name: string
contactName: string
contactEmail: string
contactPhone: string
contactPhone?: string
maxUsers?: number
userCount: number
status: 'active' | 'inactive' | 'expired'
status: string
expiresAt?: string
description?: string
createdAt: string
@ -20,33 +20,45 @@ export namespace FenglingApi {
name: string
contactName: string
contactEmail: string
contactPhone: string
contactPhone?: string
maxUsers?: number
expiresAt?: string
status: 'active' | 'inactive'
status?: string
description?: string
}
export interface UpdateTenantDto {
name?: string
contactName?: string
contactEmail?: string
name: string
contactName: string
contactEmail: string
contactPhone?: string
maxUsers?: number
expiresAt?: string
status?: 'active' | 'inactive'
status?: string
description?: string
}
export interface TenantSettings {
allowRegistration: boolean
allowedEmailDomains: string
defaultRoleId?: number
passwordPolicy: string[]
minPasswordLength: number
sessionTimeout: number
}
}
export namespace User {
export interface User {
id: number
userName: string
email: string
userName?: string
email?: string
realName?: string
tenantId?: string
phone?: string
tenantId: number
tenantName: string
roles: string[]
emailConfirmed: boolean
isActive: boolean
createdAt: string
}
@ -54,17 +66,21 @@ export namespace FenglingApi {
export interface CreateUserDto {
userName: string
email: string
realName?: string
realName: string
password: string
tenantId?: string
phone?: string
tenantId?: number
roleIds: number[]
emailConfirmed?: boolean
isActive?: boolean
}
export interface UpdateUserDto {
email?: string
realName?: string
email: string
realName: string
phone?: string
emailConfirmed?: boolean
isActive?: boolean
roleIds?: number[]
}
export interface ResetPasswordDto {
@ -75,10 +91,13 @@ export namespace FenglingApi {
export namespace Role {
export interface Role {
id: number
name: string
displayName: string
name?: string
displayName?: string
description?: string
permissions: string[]
tenantId?: number
isSystem: boolean
permissions?: string[]
userCount: number
createdAt: string
}
@ -86,56 +105,55 @@ export namespace FenglingApi {
name: string
displayName: string
description?: string
tenantId?: number
permissions: string[]
}
export interface UpdateRoleDto {
displayName?: string
displayName: string
description?: string
permissions?: string[]
permissions: string[]
}
}
export namespace OAuth {
export interface OAuthClient {
id: number
id: string
clientId: string
displayName: string
redirectUris: string[]
postLogoutRedirectUris: string[]
scopes: string[]
grantTypes: string[]
clientType: 'confidential' | 'public'
consentType: 'explicit' | 'implicit' | 'external'
status: 'active' | 'inactive'
clientType?: string
consentType?: string
status: string
description?: string
createdAt: string
updatedAt: string
}
export interface CreateOAuthClientDto {
clientId: string
displayName: string
clientSecret?: string
redirectUris: string[]
postLogoutRedirectUris: string[]
scopes: string[]
grantTypes: string[]
clientType?: 'confidential' | 'public'
consentType?: 'explicit' | 'implicit' | 'external'
status?: 'active' | 'inactive'
displayName: string
redirectUris?: string[]
postLogoutRedirectUris?: string[]
scopes?: string[]
grantTypes?: string[]
clientType?: string
consentType?: string
status?: string
description?: string
}
export interface UpdateOAuthClientDto {
displayName: string
redirectUris: string[]
postLogoutRedirectUris: string[]
scopes: string[]
grantTypes: string[]
clientType: 'confidential' | 'public'
consentType: 'explicit' | 'implicit' | 'external'
status: 'active' | 'inactive'
displayName?: string
redirectUris?: string[]
postLogoutRedirectUris?: string[]
scopes?: string[]
grantTypes?: string[]
clientType?: string
consentType?: string
status?: string
description?: string
}
}

View File

@ -3,6 +3,9 @@ import type { FenglingApi } from './typings';
import { requestClient } from '#/api/request';
export namespace UserApi {
const apiPrefix = '/console';
export async function getUserList(params: {
page?: number
pageSize?: number
@ -11,28 +14,28 @@ export namespace UserApi {
isActive?: boolean
}) {
return requestClient.get<FenglingApi.PaginatedResponse<FenglingApi.User.User>>(
'/users',
`${apiPrefix}/users`,
{ params }
);
}
export async function getUserById(id: number) {
return requestClient.get<FenglingApi.User.User>(`/users/${id}`);
return requestClient.get<FenglingApi.User.User>(`${apiPrefix}/users/${id}`);
}
export async function createUser(data: FenglingApi.User.CreateUserDto) {
return requestClient.post<FenglingApi.User.User>('/users', data);
return requestClient.post<FenglingApi.User.User, FenglingApi.User.CreateUserDto>(`${apiPrefix}/users`, data);
}
export async function updateUser(id: number, data: FenglingApi.User.UpdateUserDto) {
return requestClient.put<FenglingApi.User.User>(`/users/${id}`, data);
return requestClient.put<void, FenglingApi.User.UpdateUserDto>(`${apiPrefix}/users/${id}`, data);
}
export async function deleteUser(id: number) {
return requestClient.delete(`/users/${id}`);
return requestClient.delete<void>(`${apiPrefix}/users/${id}`);
}
export async function resetUserPassword(id: number, data: FenglingApi.User.ResetPasswordDto) {
return requestClient.post(`/users/${id}/reset-password`, data);
return requestClient.post<void, FenglingApi.User.ResetPasswordDto>(`${apiPrefix}/users/${id}/reset-password`, data);
}
}

View File

@ -78,13 +78,19 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
});
// 处理返回的响应数据格式
client.addResponseInterceptor(
defaultResponseInterceptor({
codeField: 'code',
dataField: 'data',
successCode: 0,
}),
);
// NOTE: 后端直接返回数据,如 {items: [...], totalCount: 1}
// 不使用 {code, data} 包装格式,所以禁用 defaultResponseInterceptor
// 添加自定义响应拦截器,直接返回 response.data
client.addResponseInterceptor({
fulfilled: (response) => {
console.log('[Response] Success:', response.config?.method?.toUpperCase(), response.config?.url, response.data);
return response.data;
},
rejected: (error) => {
console.error('[Response] Error:', error.config?.method?.toUpperCase(), error.config?.url, error.response?.data || error.message);
return Promise.reject(error);
},
});
// token过期的处理
client.addResponseInterceptor(
@ -113,7 +119,11 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
}
export const requestClient = createRequestClient(apiURL, {
responseReturn: 'data',
// responseReturn: 'data', // 后端直接返回数据,不需要从 data 字段提取
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
// 认证服务客户端 - 直接连接到OIDC服务器
const authServerURL = import.meta.env.VITE_AUTH_SERVER_URL || 'http://localhost:5132';
export const authRequestClient = new RequestClient({ baseURL: authServerURL });

View File

@ -1,4 +1,8 @@
{
"app": {
"name": "Fengling Management Platform",
"title": "Fengling Console"
},
"auth": {
"login": "Login",
"register": "Register",
@ -11,5 +15,148 @@
"title": "Dashboard",
"analytics": "Analytics",
"workspace": "Workspace"
},
"tenant": {
"title": "Tenant Management",
"id": "ID",
"tenantId": "Tenant ID",
"name": "Tenant Name",
"contactName": "Contact Name",
"contactEmail": "Contact Email",
"contactPhone": "Contact Phone",
"maxUsers": "Max Users",
"userCount": "User Count",
"status": "Status",
"expiresAt": "Expires At",
"createdAt": "Created At",
"description": "Description",
"actions": "Actions",
"search": "Search Tenants",
"searchPlaceholder": "Search by name",
"create": "Create Tenant",
"edit": "Edit",
"delete": "Delete",
"save": "Save",
"cancel": "Cancel",
"confirmDelete": "Are you sure to delete this tenant?",
"statusActive": "Active",
"statusInactive": "Inactive",
"statusExpired": "Expired",
"created": "Tenant created successfully",
"updated": "Tenant updated successfully",
"deleted": "Tenant deleted successfully",
"loadFailed": "Failed to load tenants",
"saveFailed": "Failed to save tenant",
"deleteFailed": "Failed to delete tenant"
},
"user": {
"title": "User Management",
"id": "ID",
"userName": "Username",
"email": "Email",
"realName": "Real Name",
"phone": "Phone",
"tenant": "Tenant",
"roles": "Roles",
"isActive": "Status",
"emailConfirmed": "Email Confirmed",
"createdAt": "Created At",
"actions": "Actions",
"search": "Search Users",
"searchPlaceholder": "Search by username or email",
"create": "Create User",
"edit": "Edit",
"delete": "Delete",
"resetPassword": "Reset Password",
"save": "Save",
"cancel": "Cancel",
"confirmDelete": "Are you sure to delete this user?",
"active": "Active",
"inactive": "Inactive",
"created": "User created successfully",
"updated": "User updated successfully",
"deleted": "User deleted successfully",
"passwordReset": "Password reset successfully",
"loadFailed": "Failed to load users",
"saveFailed": "Failed to save user",
"deleteFailed": "Failed to delete user"
},
"role": {
"title": "Role Management",
"id": "ID",
"name": "Role Name",
"displayName": "Display Name",
"description": "Description",
"tenant": "Tenant",
"isSystem": "System Role",
"permissions": "Permissions",
"userCount": "User Count",
"createdAt": "Created At",
"actions": "Actions",
"search": "Search Roles",
"searchPlaceholder": "Search by role name",
"create": "Create Role",
"edit": "Edit",
"delete": "Delete",
"save": "Save",
"cancel": "Cancel",
"confirmDelete": "Are you sure to delete this role?",
"systemRole": "System Role",
"created": "Role created successfully",
"updated": "Role updated successfully",
"deleted": "Role deleted successfully",
"loadFailed": "Failed to load roles",
"saveFailed": "Failed to save role",
"deleteFailed": "Failed to delete role"
},
"oauth": {
"title": "OAuth Client Management",
"id": "ID",
"clientId": "Client ID",
"displayName": "Display Name",
"description": "Description",
"redirectUris": "Redirect URIs",
"postLogoutRedirectUris": "Post Logout Redirect URIs",
"scopes": "Scopes",
"grantTypes": "Grant Types",
"clientType": "Client Type",
"consentType": "Consent Type",
"status": "Status",
"actions": "Actions",
"search": "Search Clients",
"searchPlaceholder": "Search by client ID or name",
"create": "Create Client",
"edit": "Edit",
"delete": "Delete",
"resetSecret": "Reset Secret",
"save": "Save",
"cancel": "Cancel",
"confirmDelete": "Are you sure to delete this client?",
"confirmResetSecret": "Are you sure to reset client secret?",
"secretReset": "Client secret reset successfully",
"active": "Active",
"inactive": "Inactive",
"created": "Client created successfully",
"updated": "Client updated successfully",
"deleted": "Client deleted successfully",
"loadFailed": "Failed to load clients",
"saveFailed": "Failed to save client",
"deleteFailed": "Failed to delete client"
},
"common": {
"total": "Total {count} items",
"page": "Page {page}",
"loading": "Loading...",
"noData": "No data",
"search": "Search",
"reset": "Reset",
"confirm": "Confirm",
"pleaseInput": "Please input",
"pleaseSelect": "Please select",
"required": "This field is required",
"success": "Operation successful",
"error": "Operation failed",
"warning": "Warning",
"info": "Information"
}
}

View File

@ -1,4 +1,8 @@
{
"app": {
"name": "蜂铃管理平台",
"title": "蜂铃控制台"
},
"auth": {
"login": "登录",
"register": "注册",
@ -11,5 +15,148 @@
"title": "概览",
"analytics": "分析页",
"workspace": "工作台"
},
"tenant": {
"title": "租户管理",
"id": "ID",
"tenantId": "租户ID",
"name": "租户名称",
"contactName": "联系人",
"contactEmail": "联系邮箱",
"contactPhone": "联系电话",
"maxUsers": "最大用户数",
"userCount": "用户数",
"status": "状态",
"expiresAt": "过期时间",
"createdAt": "创建时间",
"description": "描述",
"actions": "操作",
"search": "搜索租户",
"searchPlaceholder": "按名称搜索",
"create": "新建租户",
"edit": "编辑",
"delete": "删除",
"save": "保存",
"cancel": "取消",
"confirmDelete": "确定要删除这个租户吗?",
"statusActive": "激活",
"statusInactive": "未激活",
"statusExpired": "已过期",
"created": "租户创建成功",
"updated": "租户更新成功",
"deleted": "租户删除成功",
"loadFailed": "加载租户失败",
"saveFailed": "保存租户失败",
"deleteFailed": "删除租户失败"
},
"user": {
"title": "用户管理",
"id": "ID",
"userName": "用户名",
"email": "邮箱",
"realName": "真实姓名",
"phone": "手机号",
"tenant": "租户",
"roles": "角色",
"isActive": "状态",
"emailConfirmed": "邮箱已验证",
"createdAt": "创建时间",
"actions": "操作",
"search": "搜索用户",
"searchPlaceholder": "按用户名或邮箱搜索",
"create": "新建用户",
"edit": "编辑",
"delete": "删除",
"resetPassword": "重置密码",
"save": "保存",
"cancel": "取消",
"confirmDelete": "确定要删除这个用户吗?",
"active": "激活",
"inactive": "未激活",
"created": "用户创建成功",
"updated": "用户更新成功",
"deleted": "用户删除成功",
"passwordReset": "密码重置成功",
"loadFailed": "加载用户失败",
"saveFailed": "保存用户失败",
"deleteFailed": "删除用户失败"
},
"role": {
"title": "角色管理",
"id": "ID",
"name": "角色名称",
"displayName": "显示名称",
"description": "描述",
"tenant": "租户",
"isSystem": "系统角色",
"permissions": "权限",
"userCount": "用户数",
"createdAt": "创建时间",
"actions": "操作",
"search": "搜索角色",
"searchPlaceholder": "按角色名称搜索",
"create": "新建角色",
"edit": "编辑",
"delete": "删除",
"save": "保存",
"cancel": "取消",
"confirmDelete": "确定要删除这个角色吗?",
"systemRole": "系统角色",
"created": "角色创建成功",
"updated": "角色更新成功",
"deleted": "角色删除成功",
"loadFailed": "加载角色失败",
"saveFailed": "保存角色失败",
"deleteFailed": "删除角色失败"
},
"oauth": {
"title": "OAuth客户端管理",
"id": "ID",
"clientId": "客户端ID",
"displayName": "显示名称",
"description": "描述",
"redirectUris": "回调地址",
"postLogoutRedirectUris": "登出回调地址",
"scopes": "授权范围",
"grantTypes": "授权类型",
"clientType": "客户端类型",
"consentType": "同意类型",
"status": "状态",
"actions": "操作",
"search": "搜索客户端",
"searchPlaceholder": "按客户端ID或名称搜索",
"create": "新建客户端",
"edit": "编辑",
"delete": "删除",
"resetSecret": "重置密钥",
"save": "保存",
"cancel": "取消",
"confirmDelete": "确定要删除这个客户端吗?",
"confirmResetSecret": "确定要重置客户端密钥吗?",
"secretReset": "密钥重置成功",
"active": "激活",
"inactive": "未激活",
"created": "客户端创建成功",
"updated": "客户端更新成功",
"deleted": "客户端删除成功",
"loadFailed": "加载客户端失败",
"saveFailed": "保存客户端失败",
"deleteFailed": "删除客户端失败"
},
"common": {
"total": "共 {count} 条",
"page": "第 {page} 页",
"loading": "加载中...",
"noData": "暂无数据",
"search": "搜索",
"reset": "重置",
"confirm": "确认",
"pleaseInput": "请输入",
"pleaseSelect": "请选择",
"required": "此项必填",
"success": "操作成功",
"error": "操作失败",
"warning": "警告",
"info": "信息"
}
}

View File

@ -16,6 +16,7 @@ import {
ElTag,
ElMessage,
ElPopconfirm,
ElPagination,
} from 'element-plus';
import { TenantApi, type FenglingApi } from '#/api/fengling';
@ -24,9 +25,10 @@ const loading = ref(false);
const tableData = ref<FenglingApi.Tenant.Tenant[]>([]);
const dialogVisible = ref(false);
const dialogTitle = ref('Create Tenant');
const formData = ref<Partial<FenglingApi.Tenant.CreateTenantDto>>({});
const formData = ref<Partial<FenglingApi.Tenant.CreateTenantDto | FenglingApi.Tenant.UpdateTenantDto>>({});
const searchKeyword = ref('');
const searchStatus = ref('');
const editingId = ref<number | null>(null);
const pagination = ref({
page: 1,
@ -40,13 +42,16 @@ const loadTenants = async () => {
const response = await TenantApi.getTenantList({
page: pagination.value.page,
pageSize: pagination.value.pageSize,
keyword: searchKeyword.value || undefined,
name: searchKeyword.value || undefined,
status: searchStatus.value || undefined,
});
tableData.value = response.items;
pagination.value.total = response.totalCount;
} catch (error) {
ElMessage.error('Failed to load tenants');
console.log('[Tenant] Response:', response);
console.log('[Tenant] Items:', response.items);
tableData.value = response.items || [];
pagination.value.total = response.totalCount || 0;
} catch (error: any) {
console.error('[Tenant] Error loading tenants:', error);
ElMessage.error(error?.response?.data?.message || 'Failed to load tenants');
} finally {
loading.value = false;
}
@ -54,6 +59,7 @@ const loadTenants = async () => {
const handleCreate = () => {
dialogTitle.value = 'Create Tenant';
editingId.value = null;
formData.value = {
status: 'active',
};
@ -62,6 +68,7 @@ const handleCreate = () => {
const handleEdit = (row: FenglingApi.Tenant.Tenant) => {
dialogTitle.value = 'Edit Tenant';
editingId.value = row.id;
formData.value = {
name: row.name,
contactName: row.contactName,
@ -69,7 +76,7 @@ const handleEdit = (row: FenglingApi.Tenant.Tenant) => {
contactPhone: row.contactPhone,
maxUsers: row.maxUsers,
expiresAt: row.expiresAt,
status: row.status === 'expired' ? 'inactive' : row.status,
status: row.status,
description: row.description,
};
dialogVisible.value = true;
@ -91,13 +98,19 @@ const handleSave = async () => {
await TenantApi.createTenant(formData.value as FenglingApi.Tenant.CreateTenantDto);
ElMessage.success('Tenant created successfully');
} else {
await TenantApi.updateTenant(tableData.value[0].id, formData.value as FenglingApi.Tenant.UpdateTenantDto);
if (!editingId.value) {
ElMessage.error('Tenant ID is missing');
return;
}
await TenantApi.updateTenant(editingId.value, formData.value as FenglingApi.Tenant.UpdateTenantDto);
ElMessage.success('Tenant updated successfully');
}
dialogVisible.value = false;
editingId.value = null;
loadTenants();
} catch (error) {
ElMessage.error('Failed to save tenant');
} catch (error: any) {
console.error('[Tenant] Save error:', error);
ElMessage.error(error?.message || 'Failed to save tenant');
}
};

View File

@ -13,6 +13,7 @@ import {
ElMessage,
ElPopconfirm,
ElSwitch,
ElPagination,
} from 'element-plus';
import { UserApi, type FenglingApi } from '#/api/fengling';