feat(authorization): add admin authorization management API and real data integration
Backend (authorization-service): - Add QueryAuthorizationsDto for query parameters (roleType, keyword, includeRevoked, page, limit) - Add queryAuthorizations method to fetch all authorizations with user info - Add GET /admin/authorizations endpoint for listing authorizations - Add POST /admin/authorizations/:id/revoke endpoint for revoking authorization Frontend (admin-web): - Add authorization.types.ts with RoleType, Authorization, and request types - Add authorizationService.ts for API calls (list, revoke, grant operations) - Add useAuthorizations.ts React Query hooks - Update authorization page to use real API data instead of mock data - Add loading/error states, pagination, and revoke reason display - Add new styles for loading, error, pagination, and date columns The authorization management page now displays all authorized users from the database with support for filtering by role type, status, and keyword search. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e08959263a
commit
35a812c058
|
|
@ -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: '授权社区(管理员)' })
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<string, { nickname: string; avatar: string | null }>()
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<AuthorizationType, string> = {
|
||||
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<AuthorizationType | ''>('');
|
||||
const [filterStatus, setFilterStatus] = useState<'active' | 'revoked' | ''>('');
|
||||
const [filterType, setFilterType] = useState<RoleType | ''>('');
|
||||
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<AuthorizationItem | null>(null);
|
||||
const [revokeTarget, setRevokeTarget] = useState<Authorization | null>(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() {
|
|||
</div>
|
||||
);
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string | null) => {
|
||||
if (!dateStr) return '-';
|
||||
return new Date(dateStr).toLocaleDateString('zh-CN');
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer title="授权管理">
|
||||
<div className={styles.authorization}>
|
||||
|
|
@ -211,11 +167,14 @@ export default function AuthorizationPage() {
|
|||
<select
|
||||
className={styles.authorization__select}
|
||||
value={filterType}
|
||||
onChange={(e) => setFilterType(e.target.value as AuthorizationType | '')}
|
||||
onChange={(e) => {
|
||||
setFilterType(e.target.value as RoleType | '');
|
||||
setPage(1);
|
||||
}}
|
||||
aria-label="授权类型"
|
||||
>
|
||||
<option value="">全部类型</option>
|
||||
{Object.entries(authorizationTypeLabels).map(([value, label]) => (
|
||||
{Object.entries(ROLE_TYPE_LABELS).map(([value, label]) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
|
|
@ -224,12 +183,15 @@ export default function AuthorizationPage() {
|
|||
<select
|
||||
className={styles.authorization__select}
|
||||
value={filterStatus}
|
||||
onChange={(e) => setFilterStatus(e.target.value as 'active' | 'revoked' | '')}
|
||||
onChange={(e) => {
|
||||
setFilterStatus(e.target.value as 'ACTIVE' | 'REVOKED' | '');
|
||||
setPage(1);
|
||||
}}
|
||||
aria-label="授权状态"
|
||||
>
|
||||
<option value="">全部状态</option>
|
||||
<option value="active">有效</option>
|
||||
<option value="revoked">已撤销</option>
|
||||
<option value="ACTIVE">有效</option>
|
||||
<option value="REVOKED">已撤销</option>
|
||||
</select>
|
||||
<input
|
||||
className={styles.authorization__input}
|
||||
|
|
@ -237,166 +199,239 @@ export default function AuthorizationPage() {
|
|||
type="text"
|
||||
value={searchKeyword}
|
||||
onChange={(e) => setSearchKeyword(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && refetch()}
|
||||
aria-label="关键词搜索"
|
||||
/>
|
||||
<button className={styles.authorization__searchBtn}>查询</button>
|
||||
<button
|
||||
className={styles.authorization__searchBtn}
|
||||
onClick={() => {
|
||||
setPage(1);
|
||||
refetch();
|
||||
}}
|
||||
>
|
||||
查询
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 授权表格 */}
|
||||
<div className={styles.authorization__table}>
|
||||
<div className={styles.authorization__tableHeader}>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--avatar']
|
||||
)}
|
||||
>
|
||||
头像
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--nickname']
|
||||
)}
|
||||
>
|
||||
昵称
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--accountId']
|
||||
)}
|
||||
>
|
||||
账户序列号
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--type']
|
||||
)}
|
||||
>
|
||||
授权类型
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--region']
|
||||
)}
|
||||
>
|
||||
地区/名称
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--status']
|
||||
)}
|
||||
>
|
||||
状态
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--actions']
|
||||
)}
|
||||
>
|
||||
操作
|
||||
</div>
|
||||
{/* 加载状态 */}
|
||||
{isLoading && (
|
||||
<div className={styles.authorization__loading}>加载中...</div>
|
||||
)}
|
||||
|
||||
{/* 错误状态 */}
|
||||
{error && (
|
||||
<div className={styles.authorization__error}>
|
||||
加载失败: {error instanceof Error ? error.message : '未知错误'}
|
||||
<button onClick={() => refetch()} className={styles.authorization__retryBtn}>
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
{filteredData.map((item) => (
|
||||
<div key={item.id} className={styles.authorization__tableRow}>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--avatar']
|
||||
)}
|
||||
>
|
||||
)}
|
||||
|
||||
{/* 授权表格 */}
|
||||
{!isLoading && !error && (
|
||||
<>
|
||||
<div className={styles.authorization__table}>
|
||||
<div className={styles.authorization__tableHeader}>
|
||||
<div
|
||||
className={styles.authorization__avatar}
|
||||
style={item.avatar ? { backgroundImage: `url(${item.avatar})` } : undefined}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--nickname']
|
||||
)}
|
||||
>
|
||||
{item.nickname}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--accountId']
|
||||
)}
|
||||
>
|
||||
{item.accountSequence}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--type']
|
||||
)}
|
||||
>
|
||||
<span className={styles.authorization__typeBadge}>
|
||||
{authorizationTypeLabels[item.type]}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--region']
|
||||
)}
|
||||
>
|
||||
{item.region || '-'}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--status']
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
styles.authorization__badge,
|
||||
item.status === 'active'
|
||||
? styles['authorization__badge--authorized']
|
||||
: styles['authorization__badge--revoked']
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--avatar']
|
||||
)}
|
||||
>
|
||||
{item.status === 'active' ? '有效' : '已撤销'}
|
||||
</span>
|
||||
头像
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--nickname']
|
||||
)}
|
||||
>
|
||||
昵称
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--accountId']
|
||||
)}
|
||||
>
|
||||
账户序列号
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--type']
|
||||
)}
|
||||
>
|
||||
授权类型
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--region']
|
||||
)}
|
||||
>
|
||||
地区/名称
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--date']
|
||||
)}
|
||||
>
|
||||
授权时间
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--status']
|
||||
)}
|
||||
>
|
||||
状态
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--header'],
|
||||
styles['authorization__tableCell--actions']
|
||||
)}
|
||||
>
|
||||
操作
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--actions']
|
||||
)}
|
||||
>
|
||||
{item.status === 'active' && (
|
||||
<button
|
||||
{filteredData.map((item) => (
|
||||
<div key={item.id} className={styles.authorization__tableRow}>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__actionBtn,
|
||||
styles['authorization__actionBtn--revoke']
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--avatar']
|
||||
)}
|
||||
onClick={() => openRevokeModal(item)}
|
||||
>
|
||||
撤销授权
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={styles.authorization__avatar}
|
||||
style={item.avatar ? { backgroundImage: `url(${item.avatar})` } : undefined}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--nickname']
|
||||
)}
|
||||
>
|
||||
{item.nickname || '-'}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--accountId']
|
||||
)}
|
||||
>
|
||||
{item.accountSequence}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--type']
|
||||
)}
|
||||
>
|
||||
<span className={styles.authorization__typeBadge}>
|
||||
{ROLE_TYPE_LABELS[item.roleType] || item.roleType}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--region']
|
||||
)}
|
||||
>
|
||||
{item.regionName || '-'}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--date']
|
||||
)}
|
||||
>
|
||||
{formatDate(item.authorizedAt || item.createdAt)}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--status']
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
styles.authorization__badge,
|
||||
item.status === 'ACTIVE'
|
||||
? styles['authorization__badge--authorized']
|
||||
: styles['authorization__badge--revoked']
|
||||
)}
|
||||
>
|
||||
{item.status === 'ACTIVE' ? '有效' : '已撤销'}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
styles.authorization__tableCell,
|
||||
styles['authorization__tableCell--actions']
|
||||
)}
|
||||
>
|
||||
{item.status === 'ACTIVE' && (
|
||||
<button
|
||||
className={cn(
|
||||
styles.authorization__actionBtn,
|
||||
styles['authorization__actionBtn--revoke']
|
||||
)}
|
||||
onClick={() => openRevokeModal(item)}
|
||||
>
|
||||
撤销授权
|
||||
</button>
|
||||
)}
|
||||
{item.status === 'REVOKED' && item.revokeReason && (
|
||||
<span className={styles.authorization__revokeReason} title={item.revokeReason}>
|
||||
原因: {item.revokeReason.length > 10 ? `${item.revokeReason.slice(0, 10)}...` : item.revokeReason}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{filteredData.length === 0 && (
|
||||
<div className={styles.authorization__emptyRow}>暂无授权记录</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{filteredData.length === 0 && (
|
||||
<div className={styles.authorization__emptyRow}>暂无授权记录</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 分页 */}
|
||||
{data && data.total > limit && (
|
||||
<div className={styles.authorization__pagination}>
|
||||
<button
|
||||
className={styles.authorization__pageBtn}
|
||||
onClick={() => setPage(p => Math.max(1, p - 1))}
|
||||
disabled={page <= 1}
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
<span className={styles.authorization__pageInfo}>
|
||||
第 {page} 页 / 共 {Math.ceil(data.total / limit)} 页 (共 {data.total} 条)
|
||||
</span>
|
||||
<button
|
||||
className={styles.authorization__pageBtn}
|
||||
onClick={() => setPage(p => p + 1)}
|
||||
disabled={page >= Math.ceil(data.total / limit)}
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<p className={styles.authorization__help}>
|
||||
帮助:在此管理所有授权用户,包括社区、省团队、市团队、正式省公司和正式市公司。
|
||||
|
|
@ -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]) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
|
|
@ -490,9 +525,9 @@ export default function AuthorizationPage() {
|
|||
<h3 className={styles.modal__title}>撤销授权</h3>
|
||||
<div className={styles.modal__form}>
|
||||
<p className={styles.modal__warning}>
|
||||
确定要撤销用户 <strong>{revokeTarget.nickname}</strong> (
|
||||
确定要撤销用户 <strong>{revokeTarget.nickname || revokeTarget.accountSequence}</strong> (
|
||||
{revokeTarget.accountSequence}) 的{' '}
|
||||
<strong>{authorizationTypeLabels[revokeTarget.type]}</strong> 授权吗?
|
||||
<strong>{ROLE_TYPE_LABELS[revokeTarget.roleType] || revokeTarget.roleType}</strong> 授权吗?
|
||||
</p>
|
||||
<div className={styles.modal__formGroup}>
|
||||
<label className={styles.modal__label}>撤销原因</label>
|
||||
|
|
@ -515,9 +550,9 @@ export default function AuthorizationPage() {
|
|||
<button
|
||||
className={cn(styles.modal__confirmBtn, styles['modal__confirmBtn--danger'])}
|
||||
onClick={handleRevoke}
|
||||
disabled={!revokeReason}
|
||||
disabled={!revokeReason || revokeMutation.isPending}
|
||||
>
|
||||
确认撤销
|
||||
{revokeMutation.isPending ? '处理中...' : '确认撤销'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
|
||||
export * from './useDashboard';
|
||||
export * from './useUsers';
|
||||
export * from './useAuthorizations';
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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<AuthorizationListResponse> {
|
||||
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;
|
||||
|
|
@ -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<RoleType, string> = {
|
||||
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;
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
Loading…
Reference in New Issue