diff --git a/backend/services/authorization-service/src/api/controllers/admin-authorization.controller.ts b/backend/services/authorization-service/src/api/controllers/admin-authorization.controller.ts index 59436214..0759ccd9 100644 --- a/backend/services/authorization-service/src/api/controllers/admin-authorization.controller.ts +++ b/backend/services/authorization-service/src/api/controllers/admin-authorization.controller.ts @@ -1,5 +1,5 @@ -import { Controller, Post, Body, UseGuards, HttpCode, HttpStatus } from '@nestjs/common' -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger' +import { Controller, Post, Get, Body, Query, Param, UseGuards, HttpCode, HttpStatus } from '@nestjs/common' +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger' import { AuthorizationApplicationService } from '@/application/services' import { GrantCommunityCommand, @@ -7,6 +7,7 @@ import { GrantCityCompanyCommand, GrantAuthProvinceCompanyCommand, GrantAuthCityCompanyCommand, + RevokeAuthorizationCommand, } from '@/application/commands' import { GrantCommunityDto, @@ -14,9 +15,12 @@ import { GrantCityCompanyDto, GrantAuthProvinceCompanyDto, GrantAuthCityCompanyDto, + RevokeAuthorizationDto, + QueryAuthorizationsDto, } from '@/api/dto/request' import { CurrentUser } from '@/shared/decorators' import { JwtAuthGuard } from '@/shared/guards' +import { RoleType } from '@/domain/enums' @ApiTags('Admin Authorization') @Controller('admin/authorizations') @@ -25,6 +29,62 @@ import { JwtAuthGuard } from '@/shared/guards' export class AdminAuthorizationController { constructor(private readonly applicationService: AuthorizationApplicationService) {} + @Get() + @ApiOperation({ summary: '查询授权列表(管理员)' }) + @ApiQuery({ name: 'roleType', required: false, enum: RoleType }) + @ApiQuery({ name: 'keyword', required: false }) + @ApiQuery({ name: 'includeRevoked', required: false, type: Boolean }) + @ApiQuery({ name: 'page', required: false, type: Number }) + @ApiQuery({ name: 'limit', required: false, type: Number }) + @ApiResponse({ status: 200, description: '授权列表' }) + async queryAuthorizations( + @Query() dto: QueryAuthorizationsDto, + ): Promise<{ + items: Array<{ + id: string + accountSequence: string + nickname: string + avatar: string | null + roleType: RoleType + regionName: string + status: string + benefitActive: boolean + createdAt: Date + authorizedAt: Date | null + revokedAt: Date | null + revokeReason: string | null + }> + total: number + page: number + limit: number + }> { + return this.applicationService.queryAuthorizations({ + roleType: dto.roleType, + keyword: dto.keyword, + includeRevoked: dto.includeRevoked, + page: dto.page, + limit: dto.limit, + }) + } + + @Post(':id/revoke') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: '撤销授权(管理员)' }) + @ApiResponse({ status: 200, description: '撤销成功' }) + async revokeAuthorization( + @CurrentUser() user: { userId: string; accountSequence: string }, + @Param('id') authorizationId: string, + @Body() dto: RevokeAuthorizationDto, + ): Promise<{ message: string }> { + const command = new RevokeAuthorizationCommand( + authorizationId, + dto.reason, + user.accountSequence, + ) + await this.applicationService.revokeAuthorization(command) + return { message: '授权已撤销' } + } + @Post('community') @HttpCode(HttpStatus.CREATED) @ApiOperation({ summary: '授权社区(管理员)' }) diff --git a/backend/services/authorization-service/src/api/dto/request/index.ts b/backend/services/authorization-service/src/api/dto/request/index.ts index f52c5cb8..88be716e 100644 --- a/backend/services/authorization-service/src/api/dto/request/index.ts +++ b/backend/services/authorization-service/src/api/dto/request/index.ts @@ -9,3 +9,4 @@ export * from './grant-auth-city-company.dto' export * from './revoke-authorization.dto' export * from './grant-monthly-bypass.dto' export * from './self-apply-authorization.dto' +export * from './query-authorizations.dto' diff --git a/backend/services/authorization-service/src/api/dto/request/query-authorizations.dto.ts b/backend/services/authorization-service/src/api/dto/request/query-authorizations.dto.ts new file mode 100644 index 00000000..4c6ca1cb --- /dev/null +++ b/backend/services/authorization-service/src/api/dto/request/query-authorizations.dto.ts @@ -0,0 +1,35 @@ +import { IsOptional, IsString, IsEnum, IsInt, Min, Max } from 'class-validator' +import { ApiPropertyOptional } from '@nestjs/swagger' +import { Type } from 'class-transformer' +import { RoleType } from '@/domain/enums' + +export class QueryAuthorizationsDto { + @ApiPropertyOptional({ description: '授权类型', enum: RoleType }) + @IsOptional() + @IsEnum(RoleType) + roleType?: RoleType + + @ApiPropertyOptional({ description: '搜索关键词(匹配昵称、账户序列号、地区名称)' }) + @IsOptional() + @IsString() + keyword?: string + + @ApiPropertyOptional({ description: '是否包含已撤销的授权', default: false }) + @IsOptional() + includeRevoked?: boolean + + @ApiPropertyOptional({ description: '页码', default: 1 }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + page?: number + + @ApiPropertyOptional({ description: '每页数量', default: 20 }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + @Max(100) + limit?: number +} diff --git a/backend/services/authorization-service/src/application/services/authorization-application.service.ts b/backend/services/authorization-service/src/application/services/authorization-application.service.ts index a2a25203..64014d5b 100644 --- a/backend/services/authorization-service/src/application/services/authorization-application.service.ts +++ b/backend/services/authorization-service/src/application/services/authorization-application.service.ts @@ -3430,4 +3430,115 @@ export class AuthorizationApplicationService { if (name.length === 2) return name[0] + '*' return name[0] + '*'.repeat(name.length - 2) + name[name.length - 1] } + + // ============ 管理员查询方法 ============ + + /** + * 查询授权列表(管理员用) + */ + async queryAuthorizations(params: { + roleType?: RoleType + keyword?: string + includeRevoked?: boolean + page?: number + limit?: number + }): Promise<{ + items: Array<{ + id: string + accountSequence: string + nickname: string + avatar: string | null + roleType: RoleType + regionName: string + status: AuthorizationStatus + benefitActive: boolean + createdAt: Date + authorizedAt: Date | null + revokedAt: Date | null + revokeReason: string | null + }> + total: number + page: number + limit: number + }> { + const page = params.page ?? 1 + const limit = params.limit ?? 20 + const includeRevoked = params.includeRevoked ?? false + + // 获取所有授权(根据是否包含已撤销) + let allAuthorizations: AuthorizationRole[] + if (params.roleType) { + allAuthorizations = await this.authorizationRepository.findAllActive(params.roleType) + } else { + allAuthorizations = await this.authorizationRepository.findAllActive() + } + + // 如果包含已撤销的,需要添加已撤销的授权 + if (includeRevoked) { + const revokedAuths = await this.authorizationRepository.findByStatus(AuthorizationStatus.REVOKED) + // 过滤 roleType + const filteredRevoked = params.roleType + ? revokedAuths.filter((a) => a.roleType === params.roleType) + : revokedAuths + allAuthorizations = [...allAuthorizations, ...filteredRevoked] + } + + // 获取用户信息 + const accountSequences = [...new Set(allAuthorizations.map((a) => a.userId.accountSequence))] + let usersMap = new Map() + + if (accountSequences.length > 0) { + try { + const usersInfoMap = await this.identityServiceClient.batchGetUserInfoBySequence(accountSequences) + usersInfoMap.forEach((userInfo, accountSequence) => { + usersMap.set(accountSequence, { nickname: userInfo.nickname, avatar: userInfo.avatarUrl ?? null }) + }) + } catch (e) { + this.logger.warn(`获取用户信息失败: ${e}`) + } + } + + // 关键词过滤 + let filteredAuthorizations = allAuthorizations + if (params.keyword) { + const keyword = params.keyword.toLowerCase() + filteredAuthorizations = allAuthorizations.filter((auth) => { + const userInfo = usersMap.get(auth.userId.accountSequence) + return ( + auth.userId.accountSequence.toLowerCase().includes(keyword) || + auth.regionName.toLowerCase().includes(keyword) || + (userInfo?.nickname && userInfo.nickname.toLowerCase().includes(keyword)) + ) + }) + } + + // 排序:按创建时间降序 + filteredAuthorizations.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) + + // 分页 + const total = filteredAuthorizations.length + const startIndex = (page - 1) * limit + const pagedAuthorizations = filteredAuthorizations.slice(startIndex, startIndex + limit) + + // 构建响应 + const items = pagedAuthorizations.map((auth) => { + const userInfo = usersMap.get(auth.userId.accountSequence) + return { + id: auth.authorizationId.value, + accountSequence: auth.userId.accountSequence, + nickname: userInfo?.nickname ?? auth.userId.accountSequence, + avatar: userInfo?.avatar ?? null, + roleType: auth.roleType, + regionName: auth.regionName, + status: auth.status, + benefitActive: auth.benefitActive, + createdAt: auth.createdAt, + authorizedAt: auth.authorizedAt, + revokedAt: auth.revokedAt, + revokeReason: auth.revokeReason, + } + }) + + return { items, total, page, limit } + } } diff --git a/frontend/admin-web/src/app/(dashboard)/authorization/authorization.module.scss b/frontend/admin-web/src/app/(dashboard)/authorization/authorization.module.scss index 998d2162..937293b9 100644 --- a/frontend/admin-web/src/app/(dashboard)/authorization/authorization.module.scss +++ b/frontend/admin-web/src/app/(dashboard)/authorization/authorization.module.scss @@ -941,3 +941,100 @@ } } } + +/* 加载状态 */ +.authorization__loading { + align-self: stretch; + padding: 40px 24px; + text-align: center; + color: #6b7280; + font-size: 14px; +} + +/* 错误状态 */ +.authorization__error { + align-self: stretch; + padding: 24px; + background-color: #fef2f2; + border-radius: 6px; + color: #991b1b; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + gap: 12px; +} + +.authorization__retryBtn { + cursor: pointer; + border: 1px solid #991b1b; + border-radius: 4px; + background-color: transparent; + padding: 4px 12px; + font-size: 12px; + color: #991b1b; + font-family: inherit; + @include transition-fast; + + &:hover { + background-color: #991b1b; + color: #fff; + } +} + +/* 日期列 */ +.authorization__tableCell--date { + width: 100px; + flex-shrink: 0; + font-size: 12px; + color: #6b7280; +} + +/* 撤销原因显示 */ +.authorization__revokeReason { + font-size: 12px; + color: #6b7280; + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: help; +} + +/* 分页 */ +.authorization__pagination { + align-self: stretch; + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + padding: 16px 0; + border-top: 1px solid #e5e7eb; + margin-top: 8px; +} + +.authorization__pageBtn { + cursor: pointer; + border: 1px solid #e5e7eb; + border-radius: 6px; + background-color: #fff; + padding: 6px 12px; + font-size: 14px; + color: #1e293b; + font-family: inherit; + @include transition-fast; + + &:hover:not(:disabled) { + background-color: #f3f4f6; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +.authorization__pageInfo { + font-size: 14px; + color: #6b7280; +} diff --git a/frontend/admin-web/src/app/(dashboard)/authorization/page.tsx b/frontend/admin-web/src/app/(dashboard)/authorization/page.tsx index 9f31afc0..d85d7833 100644 --- a/frontend/admin-web/src/app/(dashboard)/authorization/page.tsx +++ b/frontend/admin-web/src/app/(dashboard)/authorization/page.tsx @@ -1,127 +1,67 @@ 'use client'; -import { useState } from 'react'; +import { useState, useMemo } from 'react'; import { PageContainer } from '@/components/layout'; +import { toast } from '@/components/common'; import { cn } from '@/utils/helpers'; +import { useAuthorizations, useRevokeAuthorization } from '@/hooks/useAuthorizations'; +import type { RoleType, Authorization } from '@/types/authorization.types'; +import { ROLE_TYPE_LABELS } from '@/types/authorization.types'; import styles from './authorization.module.scss'; -/** - * 授权角色类型 - */ -type AuthorizationType = - | 'COMMUNITY' - | 'AUTH_PROVINCE_COMPANY' - | 'PROVINCE_COMPANY' - | 'AUTH_CITY_COMPANY' - | 'CITY_COMPANY'; - -/** - * 授权角色名称映射 - */ -const authorizationTypeLabels: Record = { - COMMUNITY: '社区', - AUTH_PROVINCE_COMPANY: '省团队', - PROVINCE_COMPANY: '正式省公司', - AUTH_CITY_COMPANY: '市团队', - CITY_COMPANY: '正式市公司', -}; - -/** - * 授权记录接口 - */ -interface AuthorizationItem { - id: string; - userId: string; - accountSequence: string; - nickname: string; - avatar?: string; - type: AuthorizationType; - region?: string; // 如: "广东省" 或 "广东省深圳市" - skipAssessment: boolean; - createdAt: string; - status: 'active' | 'revoked'; -} - -// 模拟授权数据 -const mockAuthorizations: AuthorizationItem[] = [ - { - id: '1', - userId: '12345', - accountSequence: 'D25122700001', - nickname: '张三', - type: 'PROVINCE_COMPANY', - region: '广东省', - skipAssessment: false, - createdAt: '2025-01-01', - status: 'active', - }, - { - id: '2', - userId: '12346', - accountSequence: 'D25122700002', - nickname: '李四', - type: 'AUTH_CITY_COMPANY', - region: '广东省深圳市', - skipAssessment: true, - createdAt: '2025-01-02', - status: 'active', - }, - { - id: '3', - userId: '12347', - accountSequence: 'D25122700003', - nickname: '王五', - type: 'COMMUNITY', - region: '榴莲社区A', - skipAssessment: false, - createdAt: '2025-01-03', - status: 'active', - }, -]; - /** * 授权管理页面 - * 简化版 - 保留核心授权功能 + * 使用真实 API 数据 */ export default function AuthorizationPage() { // 筛选状态 - const [filterType, setFilterType] = useState(''); - const [filterStatus, setFilterStatus] = useState<'active' | 'revoked' | ''>(''); + const [filterType, setFilterType] = useState(''); + const [filterStatus, setFilterStatus] = useState<'ACTIVE' | 'REVOKED' | ''>(''); const [searchKeyword, setSearchKeyword] = useState(''); + const [page, setPage] = useState(1); + const limit = 20; // 创建授权对话框状态 const [showCreateModal, setShowCreateModal] = useState(false); const [createForm, setCreateForm] = useState({ accountSequence: '', - type: 'COMMUNITY' as AuthorizationType, + type: 'COMMUNITY' as RoleType, region: '', skipAssessment: false, }); // 取消授权对话框状态 const [showRevokeModal, setShowRevokeModal] = useState(false); - const [revokeTarget, setRevokeTarget] = useState(null); + const [revokeTarget, setRevokeTarget] = useState(null); const [revokeReason, setRevokeReason] = useState(''); - // 筛选后的数据 - const filteredData = mockAuthorizations.filter((item) => { - if (filterType && item.type !== filterType) return false; - if (filterStatus && item.status !== filterStatus) return false; - if (searchKeyword) { - const keyword = searchKeyword.toLowerCase(); - return ( - item.nickname.toLowerCase().includes(keyword) || - item.accountSequence.toLowerCase().includes(keyword) || - (item.region && item.region.toLowerCase().includes(keyword)) - ); - } - return true; - }); + // 查询参数 + const queryParams = useMemo(() => ({ + roleType: filterType || undefined, + keyword: searchKeyword || undefined, + includeRevoked: filterStatus === 'REVOKED' ? true : filterStatus === '' ? true : false, + page, + limit, + }), [filterType, searchKeyword, filterStatus, page]); + + // 获取授权列表 + const { data, isLoading, error, refetch } = useAuthorizations(queryParams); + + // 撤销授权 mutation + const revokeMutation = useRevokeAuthorization(); + + // 根据状态筛选数据 + const filteredData = useMemo(() => { + if (!data?.items) return []; + if (!filterStatus) return data.items; + return data.items.filter(item => item.status === filterStatus); + }, [data?.items, filterStatus]); // 处理创建授权 const handleCreate = () => { // TODO: 调用API创建授权 console.log('创建授权:', createForm); + toast.info('创建授权功能开发中'); setShowCreateModal(false); setCreateForm({ accountSequence: '', @@ -132,27 +72,37 @@ export default function AuthorizationPage() { }; // 处理取消授权 - const handleRevoke = () => { - // TODO: 调用API取消授权 - console.log('取消授权:', revokeTarget, '原因:', revokeReason); - setShowRevokeModal(false); - setRevokeTarget(null); - setRevokeReason(''); + const handleRevoke = async () => { + if (!revokeTarget || !revokeReason) return; + + try { + await revokeMutation.mutateAsync({ + authorizationId: revokeTarget.id, + data: { reason: revokeReason }, + }); + toast.success('授权已撤销'); + setShowRevokeModal(false); + setRevokeTarget(null); + setRevokeReason(''); + } catch (err) { + console.error('撤销授权失败:', err); + toast.error('撤销授权失败'); + } }; // 打开取消授权对话框 - const openRevokeModal = (item: AuthorizationItem) => { + const openRevokeModal = (item: Authorization) => { setRevokeTarget(item); setShowRevokeModal(true); }; // 判断是否需要地区选择 - const needsRegion = (type: AuthorizationType) => { - return type !== 'COMMUNITY' || type === 'COMMUNITY'; + const needsRegion = (_type: RoleType) => { + return true; // 所有类型都需要地区/名称 }; // 获取地区输入提示 - const getRegionPlaceholder = (type: AuthorizationType) => { + const getRegionPlaceholder = (type: RoleType) => { switch (type) { case 'COMMUNITY': return '输入社区名称'; @@ -191,6 +141,12 @@ export default function AuthorizationPage() { ); + // 格式化日期 + const formatDate = (dateStr: string | null) => { + if (!dateStr) return '-'; + return new Date(dateStr).toLocaleDateString('zh-CN'); + }; + return (
@@ -211,11 +167,14 @@ export default function AuthorizationPage() { setFilterStatus(e.target.value as 'active' | 'revoked' | '')} + onChange={(e) => { + setFilterStatus(e.target.value as 'ACTIVE' | 'REVOKED' | ''); + setPage(1); + }} aria-label="授权状态" > - - + + setSearchKeyword(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && refetch()} aria-label="关键词搜索" /> - +
- {/* 授权表格 */} -
-
-
- 头像 -
-
- 昵称 -
-
- 账户序列号 -
-
- 授权类型 -
-
- 地区/名称 -
-
- 状态 -
-
- 操作 -
+ {/* 加载状态 */} + {isLoading && ( +
加载中...
+ )} + + {/* 错误状态 */} + {error && ( +
+ 加载失败: {error instanceof Error ? error.message : '未知错误'} +
- {filteredData.map((item) => ( -
-
+ )} + + {/* 授权表格 */} + {!isLoading && !error && ( + <> +
+
-
-
- {item.nickname} -
-
- {item.accountSequence} -
-
- - {authorizationTypeLabels[item.type]} - -
-
- {item.region || '-'} -
-
- - {item.status === 'active' ? '有效' : '已撤销'} - + 头像 +
+
+ 昵称 +
+
+ 账户序列号 +
+
+ 授权类型 +
+
+ 地区/名称 +
+
+ 授权时间 +
+
+ 状态 +
+
+ 操作 +
-
- {item.status === 'active' && ( - - )} -
+
+
+
+ {item.nickname || '-'} +
+
+ {item.accountSequence} +
+
+ + {ROLE_TYPE_LABELS[item.roleType] || item.roleType} + +
+
+ {item.regionName || '-'} +
+
+ {formatDate(item.authorizedAt || item.createdAt)} +
+
+ + {item.status === 'ACTIVE' ? '有效' : '已撤销'} + +
+
+ {item.status === 'ACTIVE' && ( + + )} + {item.status === 'REVOKED' && item.revokeReason && ( + + 原因: {item.revokeReason.length > 10 ? `${item.revokeReason.slice(0, 10)}...` : item.revokeReason} + + )} +
+
+ ))} + {filteredData.length === 0 && ( +
暂无授权记录
+ )}
- ))} - {filteredData.length === 0 && ( -
暂无授权记录
- )} -
+ + {/* 分页 */} + {data && data.total > limit && ( +
+ + + 第 {page} 页 / 共 {Math.ceil(data.total / limit)} 页 (共 {data.total} 条) + + +
+ )} + + )}

帮助:在此管理所有授权用户,包括社区、省团队、市团队、正式省公司和正式市公司。 @@ -428,10 +463,10 @@ export default function AuthorizationPage() { className={styles.modal__select} value={createForm.type} onChange={(e) => - setCreateForm({ ...createForm, type: e.target.value as AuthorizationType }) + setCreateForm({ ...createForm, type: e.target.value as RoleType }) } > - {Object.entries(authorizationTypeLabels).map(([value, label]) => ( + {Object.entries(ROLE_TYPE_LABELS).map(([value, label]) => ( @@ -490,9 +525,9 @@ export default function AuthorizationPage() {

撤销授权

- 确定要撤销用户 {revokeTarget.nickname} ( + 确定要撤销用户 {revokeTarget.nickname || revokeTarget.accountSequence} ( {revokeTarget.accountSequence}) 的{' '} - {authorizationTypeLabels[revokeTarget.type]} 授权吗? + {ROLE_TYPE_LABELS[revokeTarget.roleType] || revokeTarget.roleType} 授权吗?

@@ -515,9 +550,9 @@ export default function AuthorizationPage() {
diff --git a/frontend/admin-web/src/hooks/index.ts b/frontend/admin-web/src/hooks/index.ts index 8f7ccc6d..5caae6c2 100644 --- a/frontend/admin-web/src/hooks/index.ts +++ b/frontend/admin-web/src/hooks/index.ts @@ -2,3 +2,4 @@ export * from './useDashboard'; export * from './useUsers'; +export * from './useAuthorizations'; diff --git a/frontend/admin-web/src/hooks/useAuthorizations.ts b/frontend/admin-web/src/hooks/useAuthorizations.ts new file mode 100644 index 00000000..09df65c3 --- /dev/null +++ b/frontend/admin-web/src/hooks/useAuthorizations.ts @@ -0,0 +1,121 @@ +/** + * 授权管理 Hooks + * 使用 React Query 进行数据获取和缓存管理 + */ + +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { authorizationService } from '@/services/authorizationService'; +import type { + QueryAuthorizationsParams, + RevokeAuthorizationRequest, + GrantCommunityRequest, + GrantProvinceCompanyRequest, + GrantCityCompanyRequest, + GrantAuthProvinceCompanyRequest, + GrantAuthCityCompanyRequest, +} from '@/types/authorization.types'; + +/** Query Keys */ +export const authorizationKeys = { + all: ['authorizations'] as const, + list: (params: QueryAuthorizationsParams) => + [...authorizationKeys.all, 'list', params] as const, +}; + +/** + * 获取授权列表 + */ +export function useAuthorizations(params: QueryAuthorizationsParams = {}) { + return useQuery({ + queryKey: authorizationKeys.list(params), + queryFn: () => authorizationService.getList(params), + staleTime: 30 * 1000, // 30秒后标记为过期 + gcTime: 5 * 60 * 1000, // 5分钟后垃圾回收 + }); +} + +/** + * 撤销授权 + */ +export function useRevokeAuthorization() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ authorizationId, data }: { authorizationId: string; data: RevokeAuthorizationRequest }) => + authorizationService.revoke(authorizationId, data), + onSuccess: () => { + // 撤销成功后刷新所有相关列表 + queryClient.invalidateQueries({ queryKey: authorizationKeys.all }); + }, + }); +} + +/** + * 授权社区 + */ +export function useGrantCommunity() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: GrantCommunityRequest) => authorizationService.grantCommunity(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: authorizationKeys.all }); + }, + }); +} + +/** + * 授权正式省公司 + */ +export function useGrantProvinceCompany() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: GrantProvinceCompanyRequest) => authorizationService.grantProvinceCompany(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: authorizationKeys.all }); + }, + }); +} + +/** + * 授权正式市公司 + */ +export function useGrantCityCompany() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: GrantCityCompanyRequest) => authorizationService.grantCityCompany(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: authorizationKeys.all }); + }, + }); +} + +/** + * 授权省团队 + */ +export function useGrantAuthProvinceCompany() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: GrantAuthProvinceCompanyRequest) => authorizationService.grantAuthProvinceCompany(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: authorizationKeys.all }); + }, + }); +} + +/** + * 授权市团队 + */ +export function useGrantAuthCityCompany() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: GrantAuthCityCompanyRequest) => authorizationService.grantAuthCityCompany(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: authorizationKeys.all }); + }, + }); +} diff --git a/frontend/admin-web/src/infrastructure/api/endpoints.ts b/frontend/admin-web/src/infrastructure/api/endpoints.ts index 5bc40977..e42705b2 100644 --- a/frontend/admin-web/src/infrastructure/api/endpoints.ts +++ b/frontend/admin-web/src/infrastructure/api/endpoints.ts @@ -35,6 +35,15 @@ export const API_ENDPOINTS = { // 授权管理 (authorization-service) AUTHORIZATION: { + // 管理员授权管理 + ADMIN_LIST: '/v1/admin/authorizations', + ADMIN_REVOKE: (id: string) => `/v1/admin/authorizations/${id}/revoke`, + ADMIN_GRANT_COMMUNITY: '/v1/admin/authorizations/community', + ADMIN_GRANT_PROVINCE_COMPANY: '/v1/admin/authorizations/province-company', + ADMIN_GRANT_CITY_COMPANY: '/v1/admin/authorizations/city-company', + ADMIN_GRANT_AUTH_PROVINCE_COMPANY: '/v1/admin/authorizations/auth-province-company', + ADMIN_GRANT_AUTH_CITY_COMPANY: '/v1/admin/authorizations/auth-city-company', + // 其他授权端点 PROVINCE_COMPANIES: '/v1/authorizations/province-companies', CITY_COMPANIES: '/v1/authorizations/city-companies', PROVINCE_RULES: '/v1/authorizations/province-rules', diff --git a/frontend/admin-web/src/services/authorizationService.ts b/frontend/admin-web/src/services/authorizationService.ts new file mode 100644 index 00000000..5033a522 --- /dev/null +++ b/frontend/admin-web/src/services/authorizationService.ts @@ -0,0 +1,93 @@ +/** + * 授权管理服务 + * 用于管理后台查询和管理用户授权 + */ + +import apiClient from '@/infrastructure/api/client'; +import { API_ENDPOINTS } from '@/infrastructure/api/endpoints'; +import type { + AuthorizationListResponse, + QueryAuthorizationsParams, + RevokeAuthorizationRequest, + GrantCommunityRequest, + GrantProvinceCompanyRequest, + GrantCityCompanyRequest, + GrantAuthProvinceCompanyRequest, + GrantAuthCityCompanyRequest, +} from '@/types/authorization.types'; + +/** + * 授权管理服务 + * + * API 响应结构(经过 apiClient 拦截器解包后): + * { success: true, data: { code: "OK", message: "success", data: {...} } } + * + * 需要访问 .data.data 获取实际业务数据 + */ +export const authorizationService = { + /** + * 查询授权列表 + */ + async getList(params: QueryAuthorizationsParams = {}): Promise { + const response = await apiClient.get(API_ENDPOINTS.AUTHORIZATION.ADMIN_LIST, { params }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const result = (response as any)?.data?.data; + return result ?? { items: [], total: 0, page: 1, limit: 20 }; + }, + + /** + * 撤销授权 + */ + async revoke(authorizationId: string, data: RevokeAuthorizationRequest): Promise<{ message: string }> { + const response = await apiClient.post(API_ENDPOINTS.AUTHORIZATION.ADMIN_REVOKE(authorizationId), data); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (response as any)?.data?.data ?? { message: '授权已撤销' }; + }, + + /** + * 授权社区 + */ + async grantCommunity(data: GrantCommunityRequest): Promise<{ message: string }> { + const response = await apiClient.post(API_ENDPOINTS.AUTHORIZATION.ADMIN_GRANT_COMMUNITY, data); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (response as any)?.data?.data ?? { message: '社区授权成功' }; + }, + + /** + * 授权正式省公司 + */ + async grantProvinceCompany(data: GrantProvinceCompanyRequest): Promise<{ message: string }> { + const response = await apiClient.post(API_ENDPOINTS.AUTHORIZATION.ADMIN_GRANT_PROVINCE_COMPANY, data); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (response as any)?.data?.data ?? { message: '正式省公司授权成功' }; + }, + + /** + * 授权正式市公司 + */ + async grantCityCompany(data: GrantCityCompanyRequest): Promise<{ message: string }> { + const response = await apiClient.post(API_ENDPOINTS.AUTHORIZATION.ADMIN_GRANT_CITY_COMPANY, data); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (response as any)?.data?.data ?? { message: '正式市公司授权成功' }; + }, + + /** + * 授权省团队 + */ + async grantAuthProvinceCompany(data: GrantAuthProvinceCompanyRequest): Promise<{ message: string }> { + const response = await apiClient.post(API_ENDPOINTS.AUTHORIZATION.ADMIN_GRANT_AUTH_PROVINCE_COMPANY, data); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (response as any)?.data?.data ?? { message: '省团队授权成功' }; + }, + + /** + * 授权市团队 + */ + async grantAuthCityCompany(data: GrantAuthCityCompanyRequest): Promise<{ message: string }> { + const response = await apiClient.post(API_ENDPOINTS.AUTHORIZATION.ADMIN_GRANT_AUTH_CITY_COMPANY, data); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (response as any)?.data?.data ?? { message: '市团队授权成功' }; + }, +}; + +export default authorizationService; diff --git a/frontend/admin-web/src/types/authorization.types.ts b/frontend/admin-web/src/types/authorization.types.ts new file mode 100644 index 00000000..176eca5c --- /dev/null +++ b/frontend/admin-web/src/types/authorization.types.ts @@ -0,0 +1,145 @@ +// 授权管理类型定义 + +/** + * 角色类型 + */ +export type RoleType = + | 'COMMUNITY' + | 'AUTH_PROVINCE_COMPANY' + | 'PROVINCE_COMPANY' + | 'AUTH_CITY_COMPANY' + | 'CITY_COMPANY'; + +/** + * 授权状态 + */ +export type AuthorizationStatus = 'ACTIVE' | 'REVOKED'; + +/** + * 授权信息 + */ +export interface Authorization { + id: string; + accountSequence: string; + nickname: string; + avatar: string | null; + roleType: RoleType; + regionName: string; + status: AuthorizationStatus; + benefitActive: boolean; + createdAt: string; + authorizedAt: string | null; + revokedAt: string | null; + revokeReason: string | null; +} + +/** + * 查询授权列表参数 + */ +export interface QueryAuthorizationsParams { + roleType?: RoleType; + keyword?: string; + includeRevoked?: boolean; + page?: number; + limit?: number; +} + +/** + * 授权列表响应 + */ +export interface AuthorizationListResponse { + items: Authorization[]; + total: number; + page: number; + limit: number; +} + +/** + * 撤销授权请求 + */ +export interface RevokeAuthorizationRequest { + reason: string; +} + +/** + * 授权社区请求 + */ +export interface GrantCommunityRequest { + userId: string; + accountSequence: string; + communityName: string; + skipAssessment?: boolean; +} + +/** + * 授权省公司请求 + */ +export interface GrantProvinceCompanyRequest { + userId: string; + accountSequence: string; + provinceCode: string; + provinceName: string; + skipAssessment?: boolean; +} + +/** + * 授权市公司请求 + */ +export interface GrantCityCompanyRequest { + userId: string; + accountSequence: string; + cityCode: string; + cityName: string; + skipAssessment?: boolean; +} + +/** + * 授权省团队请求 + */ +export interface GrantAuthProvinceCompanyRequest { + userId: string; + accountSequence: string; + provinceCode: string; + provinceName: string; + skipAssessment?: boolean; +} + +/** + * 授权市团队请求 + */ +export interface GrantAuthCityCompanyRequest { + userId: string; + accountSequence: string; + cityCode: string; + cityName: string; + skipAssessment?: boolean; +} + +/** + * 角色类型显示名称 + */ +export const ROLE_TYPE_LABELS: Record = { + COMMUNITY: '社区', + AUTH_PROVINCE_COMPANY: '省团队', + PROVINCE_COMPANY: '正式省公司', + AUTH_CITY_COMPANY: '市团队', + CITY_COMPANY: '正式市公司', +}; + +/** + * 获取角色类型显示名称 + */ +export function getRoleTypeLabel(roleType: RoleType): string { + return ROLE_TYPE_LABELS[roleType] || roleType; +} + +/** + * 角色类型筛选选项 + */ +export const ROLE_TYPE_OPTIONS = [ + { value: 'COMMUNITY', label: '社区' }, + { value: 'AUTH_PROVINCE_COMPANY', label: '省团队' }, + { value: 'PROVINCE_COMPANY', label: '正式省公司' }, + { value: 'AUTH_CITY_COMPANY', label: '市团队' }, + { value: 'CITY_COMPANY', label: '正式市公司' }, +] as const; diff --git a/frontend/admin-web/src/types/index.ts b/frontend/admin-web/src/types/index.ts index 3d91a0ec..b478c233 100644 --- a/frontend/admin-web/src/types/index.ts +++ b/frontend/admin-web/src/types/index.ts @@ -8,3 +8,4 @@ export * from './common.types'; export * from './dashboard.types'; export * from './pending-action.types'; export * from './withdrawal.types'; +export * from './authorization.types';