feat: P3阶段完成 - 全部模块迁移完成 🎉
## P3.1: 争议仲裁模块(disputes)✅ - API: disputes.ts - 模块导出 ## P3.2: 卖家中心模块(seller)✅ - Views: 4个页面 - Composables: usePublishForm, usePublishDraft - 模块导出 ## P3.3: 管理后台模块(admin)✅ - API: 8个文件(adminAuth, adminDashboard, adminUsers等) - Views: 15个管理页面 - Composables: useAdminTable, useAdminPaginatedTable - Components: 管理端组件 - 模块导出 --- ## 🎉 Features 架构迁移全部完成! ### 最终统计 - ✅ P0: shared(基础设施)- 22个文件 - ✅ P1: wallet, chats, orders - 24个文件 - ✅ P2: listings, auth - 35个文件 - ✅ P3: seller, disputes, admin - 47个文件 **总计:** 9个模块,128个文件完成迁移 ### 新架构 ``` frontend/src/ ├── features/ # 9个业务模块 ✅ │ ├── wallet/ ✅ │ ├── chats/ ✅ │ ├── orders/ ✅ (已重构) │ ├── listings/ ✅ │ ├── auth/ ✅ │ ├── seller/ ✅ │ ├── disputes/ ✅ │ └── admin/ ✅ └── shared/ ✅ ``` 下一步:清理旧文件、更新路由配置 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
3534cffce1
commit
c9397635e2
@@ -0,0 +1,32 @@
|
||||
import { apiClient } from './client'
|
||||
|
||||
import type { ApiResponse, PaginatedResult } from './types'
|
||||
|
||||
export interface AdminAuditLog {
|
||||
id: number
|
||||
actor_type: string
|
||||
actor_id: number
|
||||
actor_username: string
|
||||
actor_nickname: string
|
||||
action: string
|
||||
biz_type: string
|
||||
biz_id?: number
|
||||
ip: string
|
||||
user_agent: string
|
||||
detail?: Record<string, unknown> | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface AdminAuditQuery {
|
||||
actor_id?: string
|
||||
action?: string
|
||||
biz_type?: string
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
export async function fetchAdminAuditLogs(query: AdminAuditQuery = {}) {
|
||||
const params = Object.fromEntries(Object.entries(query).filter(([, value]) => value !== '' && value !== undefined))
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<AdminAuditLog>>>('/admin/audit-logs', { params })
|
||||
return data.data
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import axios from 'axios'
|
||||
|
||||
import { apiClient } from './client'
|
||||
import type { ApiResponse } from './types'
|
||||
import { getRefreshToken, setAuthTokens } from '@/utils/authStorage'
|
||||
import type { UserStatus } from '@/types/status'
|
||||
|
||||
export interface AdminRole {
|
||||
id: number
|
||||
code: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface AdminUser {
|
||||
id: number
|
||||
username: string
|
||||
nickname: string
|
||||
status: UserStatus
|
||||
roles: AdminRole[]
|
||||
permissions: string[]
|
||||
last_login_at?: string
|
||||
}
|
||||
|
||||
export interface AdminTokenPair {
|
||||
access_token: string
|
||||
refresh_token: string
|
||||
token_type: string
|
||||
expires_in: number
|
||||
}
|
||||
|
||||
export interface AdminLoginData {
|
||||
admin: AdminUser
|
||||
tokens: AdminTokenPair
|
||||
}
|
||||
|
||||
export interface AdminCaptcha {
|
||||
captcha_id: string
|
||||
image: string
|
||||
expires_in: number
|
||||
}
|
||||
|
||||
export async function fetchAdminCaptcha() {
|
||||
const { data } = await apiClient.get<ApiResponse<AdminCaptcha>>('/admin/auth/captcha')
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function loginAdmin(username: string, password: string, captchaId: string, captchaCode: string) {
|
||||
const { data } = await apiClient.post<ApiResponse<AdminLoginData>>('/admin/auth/login', {
|
||||
username,
|
||||
password,
|
||||
captcha_id: captchaId,
|
||||
captcha_code: captchaCode,
|
||||
})
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchAdminMe() {
|
||||
const { data } = await apiClient.get<ApiResponse<AdminUser>>('/admin/me')
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function logoutAdmin() {
|
||||
const { data } = await apiClient.post<ApiResponse<{ logged_out: boolean }>>('/admin/auth/logout')
|
||||
return data.data
|
||||
}
|
||||
|
||||
/** Manually refresh admin token (uses raw axios to avoid interceptor recursion) */
|
||||
export async function refreshAdminSession() {
|
||||
const refreshToken = getRefreshToken('admin')
|
||||
if (!refreshToken) throw new Error('no refresh token')
|
||||
const { data } = await axios.post<ApiResponse<AdminTokenPair>>('/api/admin/auth/refresh', { refresh_token: refreshToken }, { timeout: 10000 })
|
||||
setAuthTokens('admin', data.data)
|
||||
return data.data
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { apiClient } from './client'
|
||||
import type { ApiResponse } from './types'
|
||||
import type { DisputeStatus, OrderStatus } from '@/types/status'
|
||||
|
||||
export interface DashboardMetrics {
|
||||
total_users: number
|
||||
verified_users: number
|
||||
total_listings: number
|
||||
published_listings: number
|
||||
total_orders: number
|
||||
renting_orders: number
|
||||
today_orders: number
|
||||
today_ledger_amount: number
|
||||
}
|
||||
|
||||
export interface DashboardPending {
|
||||
listing_reviews: number
|
||||
disputes: number
|
||||
pending_handoffs: number
|
||||
pending_return_confirms: number
|
||||
}
|
||||
|
||||
export interface DashboardRecentOrder {
|
||||
id: number
|
||||
order_no: string
|
||||
title: string
|
||||
renter_id: number
|
||||
owner_id: number
|
||||
status: OrderStatus
|
||||
rent_amount: number
|
||||
deposit_amount: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface DashboardRecentDispute {
|
||||
id: number
|
||||
order_id: number
|
||||
order_no: string
|
||||
title: string
|
||||
type: string
|
||||
status: DisputeStatus
|
||||
initiator_id: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface AdminDashboard {
|
||||
metrics: DashboardMetrics
|
||||
pending: DashboardPending
|
||||
recent_orders: DashboardRecentOrder[]
|
||||
recent_disputes: DashboardRecentDispute[]
|
||||
generated_at: string
|
||||
}
|
||||
|
||||
export async function fetchAdminDashboard() {
|
||||
const { data } = await apiClient.get<ApiResponse<AdminDashboard>>('/admin/dashboard')
|
||||
return {
|
||||
...data.data,
|
||||
recent_orders: data.data.recent_orders ?? [],
|
||||
recent_disputes: data.data.recent_disputes ?? [],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { apiClient } from './client'
|
||||
|
||||
import type { ApiResponse, PaginatedResult } from './types'
|
||||
|
||||
export interface AdminRole {
|
||||
id: number
|
||||
code: string
|
||||
name: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface AdminMgrUser {
|
||||
id: number
|
||||
username: string
|
||||
nickname: string
|
||||
status: string
|
||||
roles: AdminRole[]
|
||||
last_login_at?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface CreateAdminRequest {
|
||||
username: string
|
||||
password: string
|
||||
nickname?: string
|
||||
}
|
||||
|
||||
export interface UpdateAdminRequest {
|
||||
nickname?: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
export interface ChangePasswordRequest {
|
||||
old_password: string
|
||||
new_password: string
|
||||
}
|
||||
|
||||
export async function fetchAdminMgrUsers(page = 1, pageSize = 20) {
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<AdminMgrUser>>>('/admin/admin-users', {
|
||||
params: { page, page_size: pageSize },
|
||||
})
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchAdminMgrUser(id: number) {
|
||||
const { data } = await apiClient.get<ApiResponse<AdminMgrUser>>(`/admin/admin-users/${id}`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function createAdminMgrUser(req: CreateAdminRequest) {
|
||||
const { data } = await apiClient.post<ApiResponse<AdminMgrUser>>('/admin/admin-users', req)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function updateAdminMgrUser(id: number, req: UpdateAdminRequest) {
|
||||
const { data } = await apiClient.put<ApiResponse<AdminMgrUser>>(`/admin/admin-users/${id}`, req)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function deleteAdminMgrUser(id: number) {
|
||||
const { data } = await apiClient.delete<ApiResponse<{ deleted: boolean }>>(`/admin/admin-users/${id}`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function assignAdminRoles(id: number, roleIds: number[]) {
|
||||
const { data } = await apiClient.put<ApiResponse<{ updated: boolean }>>(`/admin/admin-users/${id}/roles`, {
|
||||
role_ids: roleIds,
|
||||
})
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function changeAdminPassword(id: number, req: ChangePasswordRequest) {
|
||||
const { data } = await apiClient.put<ApiResponse<{ updated: boolean }>>(`/admin/admin-users/${id}/password`, req)
|
||||
return data.data
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { apiClient } from './client'
|
||||
|
||||
import type { ApiResponse } from './types'
|
||||
|
||||
export interface Permission {
|
||||
id: number
|
||||
code: string
|
||||
name: string
|
||||
resource: string
|
||||
action: string
|
||||
}
|
||||
|
||||
export interface Role {
|
||||
id: number
|
||||
code: string
|
||||
name: string
|
||||
description: string
|
||||
perm_count: number
|
||||
permissions?: Permission[]
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface CreateRoleRequest {
|
||||
code: string
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface UpdateRoleRequest {
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export async function fetchRoles() {
|
||||
const { data } = await apiClient.get<ApiResponse<Role[]>>('/admin/roles')
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchRole(id: number) {
|
||||
const { data } = await apiClient.get<ApiResponse<Role>>(`/admin/roles/${id}`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function createRole(req: CreateRoleRequest) {
|
||||
const { data } = await apiClient.post<ApiResponse<Role>>('/admin/roles', req)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function updateRole(id: number, req: UpdateRoleRequest) {
|
||||
const { data } = await apiClient.put<ApiResponse<Role>>(`/admin/roles/${id}`, req)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function deleteRole(id: number) {
|
||||
const { data } = await apiClient.delete<ApiResponse<{ deleted: boolean }>>(`/admin/roles/${id}`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function assignRolePermissions(roleId: number, permissionIds: number[]) {
|
||||
const { data } = await apiClient.put<ApiResponse<{ updated: boolean }>>(`/admin/roles/${roleId}/permissions`, {
|
||||
permission_ids: permissionIds,
|
||||
})
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchPermissions() {
|
||||
const { data } = await apiClient.get<ApiResponse<Permission[]>>('/admin/permissions')
|
||||
return data.data
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { apiClient } from './client'
|
||||
|
||||
import type { ApiResponse, PaginatedResult } from './types'
|
||||
import type { RealnameStatusValue, RiskStatus, UserStatus } from '@/types/status'
|
||||
|
||||
export interface AdminUserItem {
|
||||
id: number
|
||||
phone: string
|
||||
nickname: string
|
||||
realname_status: RealnameStatusValue
|
||||
risk_status: RiskStatus
|
||||
credit_score: number
|
||||
status: UserStatus
|
||||
order_count: number
|
||||
listing_count: number
|
||||
dispute_count: number
|
||||
last_login_at?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export async function fetchAdminUsers(page = 1, pageSize = 20) {
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<AdminUserItem>>>('/admin/users', {
|
||||
params: { page, page_size: pageSize },
|
||||
})
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function freezeAdminUser(id: number, reason: string) {
|
||||
const { data } = await apiClient.post<ApiResponse<AdminUserItem>>(`/admin/users/${id}/freeze`, { reason })
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function unfreezeAdminUser(id: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<AdminUserItem>>(`/admin/users/${id}/unfreeze`)
|
||||
return data.data
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { apiClient } from './client'
|
||||
|
||||
import type { ApiResponse, PaginatedResult } from './types'
|
||||
import type { BalanceType, LedgerDirection } from '@/types/status'
|
||||
|
||||
export interface AdminWalletLedger {
|
||||
id: number
|
||||
ledger_no: string
|
||||
user_id: number
|
||||
user_phone: string
|
||||
user_nickname: string
|
||||
order_id?: number
|
||||
order_no: string
|
||||
direction: LedgerDirection
|
||||
amount: number
|
||||
balance_after: number
|
||||
balance_type: BalanceType
|
||||
biz_type: string
|
||||
biz_no: string
|
||||
remark: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface AdminWalletLedgerQuery {
|
||||
user_id?: string
|
||||
order_id?: string
|
||||
biz_type?: string
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
export async function fetchAdminWalletLedger(query: AdminWalletLedgerQuery = {}) {
|
||||
const params = Object.fromEntries(Object.entries(query).filter(([, value]) => value !== '' && value !== undefined))
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<AdminWalletLedger>>>('/admin/wallet/ledger', { params })
|
||||
return data.data
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { apiClient } from './client'
|
||||
import type { ApiResponse } from './types'
|
||||
|
||||
export interface SystemConfig {
|
||||
id: number
|
||||
key: string
|
||||
value: string
|
||||
description: string
|
||||
updated_by?: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export async function fetchSystemConfigs() {
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: SystemConfig[] }>>('/admin/system-configs')
|
||||
return data.data.items
|
||||
}
|
||||
|
||||
export async function updateSystemConfig(key: string, payload: { value: string; description?: string }) {
|
||||
const { data } = await apiClient.put<ApiResponse<SystemConfig>>(`/admin/system-configs/${key}`, payload)
|
||||
return data.data
|
||||
}
|
||||
Reference in New Issue
Block a user