feat: P2阶段完成 - listings和auth模块迁移
## P2.1: 商品浏览模块(listings)✅ ### 完整迁移(23个文件) - API: listings.ts, listingOptions.ts, homeConfig.ts - Views: 5个页面(HomeView, ListingsView, ListingDetailView + 移动端2个) - Composables: 3个(useHomeFilters, useFilterOptions, useListingQuery) - Components: 9个(ListingCard, 各种过滤器组件) - Tests: 2个测试文件 **功能:** - 首页浏览、商品列表、详情查看 - 高级筛选(服务器、等级、哈夫币、皮肤等) - 排序和分区功能 - 横幅、公告、统计展示 **技术改进:** - 更新所有导入路径到 @/shared/ - 建立清晰的模块导出 --- ## P2.2: 用户认证模块(auth)✅ ### 完整迁移(12个文件) - API: auth.ts, realname.ts, notifications.ts - Views: 8个页面(登录、注册、个人资料、实名认证、通知 + 移动端) - 模块导出 **功能:** - 用户注册、登录、Token管理 - 实名认证、风险检查 - 个人资料编辑、头像上传 - 消息/通知中心 **技术改进:** - 统一 API 导入路径 - 类型安全增强 --- ## 里程碑 🎉 **P2 阶段完成!** **累计完成:** 6个模块 - ✅ P0: shared(基础设施)- 22个文件 - ✅ P1: wallet, chats, orders - 24个文件 - ✅ P2: listings, auth - 35个文件 **总计:** 81个文件已迁移 **剩余:** P3阶段(seller, disputes, admin)+ 清理工作 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
f415832c19
commit
405abfa4f7
@@ -0,0 +1,194 @@
|
||||
import { apiClient } from '@/shared/api/client'
|
||||
import type { ApiResponse } from '@/shared/types/types'
|
||||
import type { ListingReviewStatus, ListingStatus } from '@/shared/types/status'
|
||||
|
||||
export interface Listing {
|
||||
id: number
|
||||
account_id: number
|
||||
owner_id: number
|
||||
owner_phone?: string
|
||||
owner_nickname?: string
|
||||
title: string
|
||||
description: string
|
||||
game_name: string
|
||||
server_region: string
|
||||
login_platform: string
|
||||
rank_level: string
|
||||
haf_coin_amount: number
|
||||
asset_summary?: Record<string, unknown>
|
||||
screenshot_urls: string[]
|
||||
cover_url: string
|
||||
price: number
|
||||
deposit_amount: number
|
||||
is_accelerated_sale?: boolean
|
||||
in_transaction: boolean
|
||||
status: ListingStatus
|
||||
review_status: ListingReviewStatus
|
||||
review_reason: string
|
||||
published_at?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface ListingPayload {
|
||||
title: string
|
||||
description: string
|
||||
server_region: string
|
||||
login_platform: string
|
||||
rank_level: string
|
||||
haf_coin_amount: number
|
||||
asset_summary?: Record<string, unknown>
|
||||
screenshot_urls: string[]
|
||||
price: number
|
||||
deposit_amount: number
|
||||
}
|
||||
|
||||
export interface PublicListingQuery {
|
||||
page?: number
|
||||
page_size?: number
|
||||
keyword?: string
|
||||
sort?: string
|
||||
zone?: string
|
||||
server?: string
|
||||
region?: string
|
||||
login_method?: string
|
||||
rank?: string
|
||||
insurance?: string
|
||||
stamina?: string
|
||||
load?: string
|
||||
skin_group?: string
|
||||
skin_name?: string
|
||||
min_coin?: number
|
||||
max_coin?: number
|
||||
min_price?: number
|
||||
max_price?: number
|
||||
min_deposit?: number
|
||||
max_deposit?: number
|
||||
min_total?: number
|
||||
max_total?: number
|
||||
min_fire_level?: number
|
||||
max_fire_level?: number
|
||||
min_secret_kd?: number
|
||||
max_secret_kd?: number
|
||||
[key: `resource_${string}_min`]: number | undefined
|
||||
[key: `resource_${string}_max`]: number | undefined
|
||||
}
|
||||
|
||||
export interface PublicListingPage {
|
||||
items: Listing[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
zone_counts: Record<string, number>
|
||||
}
|
||||
|
||||
export async function fetchListings(query: PublicListingQuery = {}) {
|
||||
const page = await fetchListingsPage(query)
|
||||
return page.items
|
||||
}
|
||||
|
||||
export async function fetchListingsPage(query: PublicListingQuery = {}) {
|
||||
const params = Object.fromEntries(
|
||||
Object.entries(query).filter(([, value]) => value !== '' && value !== undefined && value !== null)
|
||||
)
|
||||
const { data } = await apiClient.get<ApiResponse<Partial<PublicListingPage>>>('/listings', { params })
|
||||
return normalizePublicListingPage(data.data, query)
|
||||
}
|
||||
|
||||
function normalizePublicListingPage(data: Partial<PublicListingPage>, query: PublicListingQuery): PublicListingPage {
|
||||
const items = Array.isArray(data.items) ? data.items : []
|
||||
return {
|
||||
items,
|
||||
total: Number.isFinite(Number(data.total)) ? Number(data.total) : items.length,
|
||||
page: Number.isFinite(Number(data.page)) ? Number(data.page) : Number(query.page || 1),
|
||||
page_size: Number.isFinite(Number(data.page_size)) ? Number(data.page_size) : Number(query.page_size || items.length || 20),
|
||||
zone_counts: data.zone_counts && typeof data.zone_counts === 'object' ? data.zone_counts : {},
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchListing(id: string | number) {
|
||||
const { data } = await apiClient.get<ApiResponse<Listing>>(`/listings/${id}`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchSellerListings() {
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: Listing[] }>>('/seller/listings')
|
||||
return data.data.items
|
||||
}
|
||||
|
||||
export async function createListing(payload: ListingPayload) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>('/listings', payload)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function submitListingReview(id: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(`/listings/${id}/submit-review`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function offlineListing(id: number) {
|
||||
const { data } = await apiClient.delete<ApiResponse<{ offline: boolean }>>(`/listings/${id}`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchPendingReviewListings() {
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: Listing[] }>>('/admin/listings/pending')
|
||||
return data.data.items
|
||||
}
|
||||
|
||||
export interface AdminListingQuery {
|
||||
owner_id?: string
|
||||
status?: ListingStatus | ''
|
||||
review_status?: ListingReviewStatus | ''
|
||||
limit?: number
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
export interface AdminListingPage {
|
||||
items: Listing[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export async function fetchAdminListings(query: AdminListingQuery = {}) {
|
||||
const params = Object.fromEntries(Object.entries(query).filter(([, value]) => value !== '' && value !== undefined))
|
||||
const { data } = await apiClient.get<ApiResponse<AdminListingPage>>('/admin/listings', { params })
|
||||
return normalizeAdminListingPage(data.data, query)
|
||||
}
|
||||
|
||||
function normalizeAdminListingPage(data: Partial<AdminListingPage>, query: AdminListingQuery): AdminListingPage {
|
||||
const items = Array.isArray(data.items) ? data.items : []
|
||||
return {
|
||||
items,
|
||||
total: Number.isFinite(Number(data.total)) ? Number(data.total) : items.length,
|
||||
page: Number.isFinite(Number(data.page)) ? Number(data.page) : Number(query.page || 1),
|
||||
page_size: Number.isFinite(Number(data.page_size)) ? Number(data.page_size) : Number(query.page_size || items.length || 10),
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAdminListing(id: string | number) {
|
||||
const { data } = await apiClient.get<ApiResponse<Listing>>(`/admin/listings/${id}`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function adminOfflineListing(id: number, reason: string) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(`/admin/listings/${id}/offline`, { reason })
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function adminMarkListingAbnormal(id: number, reason: string) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(`/admin/listings/${id}/mark-abnormal`, { reason })
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function approveListing(id: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(`/admin/listings/${id}/approve`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function rejectListing(id: number, reason: string) {
|
||||
const { data } = await apiClient.post<ApiResponse<Listing>>(`/admin/listings/${id}/reject`, { reason })
|
||||
return data.data
|
||||
}
|
||||
Reference in New Issue
Block a user