- 做了什么: - 新增客服手动录单数据库迁移 000084 及对应模型,支持编号唯一校验、客服快照绑定与 CRUD 操作; - 后端提供 csmanualentry 业务模块及相关 API,并在仪表盘摘要中聚合今日各客服录单数量; - 前端增加客服手动录单视图页面、路由与侧边栏导航,支持录单二次确认与细粒度权限控制; - 管理后台仪表盘增加今日客服手动录单卡片展示。 - 为什么: - 满足客服在后台手动登记外部业务编号需求,并便于运营按客服统计今日录单业绩和追溯对账。 - 验证方式: - 执行后端全量测试 go test ./... 与静态检查 go vet ./... 全部通过; - 执行前端代码门禁 npm run check 与 vite build 全部通过; - 执行 goose down/up 迁移回滚与应用测试通过,角色权限分配正确; - 本地 dev 联调 POST /api/admin/manual-entries 录单成功返回 201。
119 lines
3.0 KiB
TypeScript
119 lines
3.0 KiB
TypeScript
import { apiClient } from '@/shared/api/client'
|
|
import type { ApiResponse } from '@/shared/types/types'
|
|
import type { DisputeStatus, OrderStatus } from '@/shared/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_cent: number
|
|
}
|
|
|
|
export interface DashboardPending {
|
|
listing_reviews: number
|
|
disputes: number
|
|
pending_handoffs: number
|
|
pending_return_confirms: number
|
|
}
|
|
|
|
export interface ListingDayStats {
|
|
date: string
|
|
published_count: number
|
|
active_offline_count: number
|
|
trade_leave_count: number
|
|
}
|
|
|
|
export interface ListingChannelDayStats extends ListingDayStats {
|
|
source_channel: string
|
|
}
|
|
|
|
export interface ListingDailyOverview {
|
|
today: ListingDayStats
|
|
trend: ListingDayStats[]
|
|
today_channels: ListingChannelDayStats[]
|
|
channel_trend: ListingChannelDayStats[]
|
|
today_uploaders: ListingUploaderDayStats[]
|
|
uploader_trend: ListingUploaderDayStats[]
|
|
days: number
|
|
timezone: string
|
|
}
|
|
|
|
export interface ListingUploaderDayStats {
|
|
date: string
|
|
uploader_id: number
|
|
uploader_name: string
|
|
upload_count: number
|
|
}
|
|
|
|
export interface DashboardRecentOrder {
|
|
id: number
|
|
order_no: string
|
|
title: string
|
|
renter_id: number
|
|
owner_id: number
|
|
status: OrderStatus
|
|
rent_amount_cent: number
|
|
deposit_amount_cent: 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 ManualEntryTodayStats {
|
|
admin_id: number
|
|
admin_name: string
|
|
count: number
|
|
}
|
|
|
|
export interface AdminDashboard {
|
|
metrics: DashboardMetrics
|
|
pending: DashboardPending
|
|
listing_daily: ListingDailyOverview
|
|
manual_entry_today: ManualEntryTodayStats[]
|
|
recent_orders: DashboardRecentOrder[]
|
|
recent_disputes: DashboardRecentDispute[]
|
|
generated_at: string
|
|
}
|
|
|
|
const emptyDayStats = (): ListingDayStats => ({
|
|
date: '',
|
|
published_count: 0,
|
|
active_offline_count: 0,
|
|
trade_leave_count: 0,
|
|
})
|
|
|
|
export async function fetchAdminDashboard() {
|
|
const { data } = await apiClient.get<ApiResponse<AdminDashboard>>('/admin/dashboard')
|
|
const payload = data.data
|
|
const listingDaily = payload.listing_daily
|
|
return {
|
|
...payload,
|
|
listing_daily: {
|
|
today: listingDaily?.today ?? emptyDayStats(),
|
|
trend: listingDaily?.trend ?? [],
|
|
today_channels: listingDaily?.today_channels ?? [],
|
|
channel_trend: listingDaily?.channel_trend ?? [],
|
|
today_uploaders: listingDaily?.today_uploaders ?? [],
|
|
uploader_trend: listingDaily?.uploader_trend ?? [],
|
|
days: listingDaily?.days ?? 7,
|
|
timezone: listingDaily?.timezone ?? 'Asia/Shanghai',
|
|
},
|
|
manual_entry_today: payload.manual_entry_today ?? [],
|
|
recent_orders: payload.recent_orders ?? [],
|
|
recent_disputes: payload.recent_disputes ?? [],
|
|
}
|
|
}
|