feat: add admin web MVP

This commit is contained in:
王鹏
2026-07-07 16:18:23 +08:00
parent fa8f243486
commit bb325151d6
30 changed files with 4029 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
import type { CurrentUser } from '../types'
import { apiPost } from './http'
export function adminLogin(username: string, password: string) {
return apiPost<CurrentUser>('/api/auth/admin-login', { username, password })
}
export function logout() {
return apiPost<void>('/api/auth/logout')
}

View File

@@ -0,0 +1,34 @@
import type { DeliveryOrder, DeliveryUser } from '../types'
import { apiGet, apiPost, apiPut } from './http'
export function listDeliveryUsers() {
return apiGet<DeliveryUser[]>('/api/admin/delivery-users')
}
export function createDeliveryUser(data: Partial<DeliveryUser>) {
return apiPost<DeliveryUser>('/api/admin/delivery-users', data)
}
export function enableDeliveryUser(id: number) {
return apiPut<DeliveryUser>(`/api/admin/delivery-users/${id}/enable`)
}
export function disableDeliveryUser(id: number) {
return apiPut<DeliveryUser>(`/api/admin/delivery-users/${id}/disable`)
}
export function listDeliveryOrders(status?: string) {
return apiGet<DeliveryOrder[]>('/api/admin/delivery-orders', status ? { status } : undefined)
}
export function assignGoodsOrder(orderId: number, riderId: number) {
return apiPost<DeliveryOrder>('/api/admin/delivery-orders/assign-goods-order', { orderId, riderId })
}
export function assignExpressOrder(orderId: number, riderId: number) {
return apiPost<DeliveryOrder>('/api/admin/delivery-orders/assign-express-order', { orderId, riderId })
}
export function assignGroupBuyOrder(orderId: number, riderId: number) {
return apiPost<DeliveryOrder>('/api/admin/delivery-orders/assign-group-buy-order', { orderId, riderId })
}

View File

@@ -0,0 +1,26 @@
import type { GroupBuy } from '../types'
import { apiGet, apiPost, apiPut } from './http'
export function listGroupBuys() {
return apiGet<GroupBuy[]>('/api/admin/group-buys')
}
export function createGroupBuy(data: Partial<GroupBuy>) {
return apiPost<GroupBuy>('/api/admin/group-buys', data)
}
export function updateGroupBuy(id: number, data: Partial<GroupBuy>) {
return apiPut<GroupBuy>(`/api/admin/group-buys/${id}`, data)
}
export function startGroupBuy(id: number) {
return apiPut<GroupBuy>(`/api/admin/group-buys/${id}/start`)
}
export function closeGroupBuy(id: number) {
return apiPut<GroupBuy>(`/api/admin/group-buys/${id}/close`)
}
export function completeGroupBuy(id: number) {
return apiPut<GroupBuy>(`/api/admin/group-buys/${id}/complete`)
}

46
admin-web/src/api/http.ts Normal file
View File

@@ -0,0 +1,46 @@
import axios, { AxiosHeaders } from 'axios'
export interface ApiEnvelope<T> {
code: number
message: string
data: T
}
export const http = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || ''
})
http.interceptors.request.use((config) => {
const token = localStorage.getItem('admin_token')
if (token) {
const headers = AxiosHeaders.from(config.headers)
headers.set('satoken', token)
headers.set('Authorization', token)
config.headers = headers
}
return config
})
async function unwrap<T>(request: Promise<{ data: ApiEnvelope<T> }>): Promise<T> {
const response = await request
if (response.data.code !== 0) {
throw new Error(response.data.message || '请求失败')
}
return response.data.data
}
export function apiGet<T>(url: string, params?: Record<string, unknown>) {
return unwrap<T>(http.get(url, { params }))
}
export function apiPost<T>(url: string, data?: unknown) {
return unwrap<T>(http.post(url, data))
}
export function apiPut<T>(url: string, data?: unknown) {
return unwrap<T>(http.put(url, data))
}
export function apiDelete<T>(url: string) {
return unwrap<T>(http.delete(url))
}

View File

@@ -0,0 +1,26 @@
import type { Notice } from '../types'
import { apiDelete, apiGet, apiPost, apiPut } from './http'
export function listNotices() {
return apiGet<Notice[]>('/api/admin/notices')
}
export function createNotice(data: Partial<Notice>) {
return apiPost<Notice>('/api/admin/notices', data)
}
export function updateNotice(id: number, data: Partial<Notice>) {
return apiPut<Notice>(`/api/admin/notices/${id}`, data)
}
export function publishNotice(id: number) {
return apiPut<Notice>(`/api/admin/notices/${id}/publish`)
}
export function offlineNotice(id: number) {
return apiPut<Notice>(`/api/admin/notices/${id}/offline`)
}
export function deleteNotice(id: number) {
return apiDelete<void>(`/api/admin/notices/${id}`)
}

View File

@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest'
import { buildGoodsOrderQuery } from './orderApi'
describe('buildGoodsOrderQuery', () => {
it('keeps status and date range filters stable', () => {
expect(buildGoodsOrderQuery({
status: 'PENDING_DELIVERY',
startDate: '2026-07-01',
endDate: '2026-07-07'
})).toEqual({
status: 'PENDING_DELIVERY',
startDate: '2026-07-01',
endDate: '2026-07-07'
})
})
})

View File

@@ -0,0 +1,56 @@
import type { ExpressOrder, GoodsOrder } from '../types'
import { apiGet, apiPut } from './http'
export interface GoodsOrderQuery {
status?: string
startDate?: string
endDate?: string
}
export function buildGoodsOrderQuery(query: GoodsOrderQuery) {
const params: Record<string, string> = {}
if (query.status) params.status = query.status
if (query.startDate) params.startDate = query.startDate
if (query.endDate) params.endDate = query.endDate
return params
}
export function listGoodsOrders(query: GoodsOrderQuery = {}) {
return apiGet<GoodsOrder[]>('/api/admin/goods-orders', buildGoodsOrderQuery(query))
}
export function confirmGoodsOrder(id: number) {
return apiPut<GoodsOrder>(`/api/admin/goods-orders/${id}/confirm`)
}
export function markGoodsOrderPrepared(id: number) {
return apiPut<GoodsOrder>(`/api/admin/goods-orders/${id}/prepared`)
}
export function cancelGoodsOrder(id: number) {
return apiPut<GoodsOrder>(`/api/mini/goods-orders/${id}/cancel`)
}
export function markGoodsOrderPaid(id: number) {
return apiPut<GoodsOrder>(`/api/admin/goods-orders/${id}/paid`)
}
export function listExpressOrders(status?: string) {
return apiGet<ExpressOrder[]>('/api/admin/express-orders', status ? { status } : undefined)
}
export function confirmExpressOrder(id: number) {
return apiPut<ExpressOrder>(`/api/admin/express-orders/${id}/confirm`)
}
export function adjustExpressFee(id: number, feeCent: number, reason: string) {
return apiPut<ExpressOrder>(`/api/admin/express-orders/${id}/adjust-fee`, { feeCent, reason })
}
export function cancelExpressOrder(id: number, reason?: string) {
return apiPut<ExpressOrder>(`/api/admin/express-orders/${id}/cancel`, { reason })
}
export function updateExpressPayStatus(id: number, status: string) {
return apiPut<ExpressOrder>(`/api/admin/express-orders/${id}/offline-pay-status`, { status })
}

View File

@@ -0,0 +1,38 @@
import type { Product, ProductCategory, ProductSku } from '../types'
import { apiGet, apiPost, apiPut } from './http'
export function listProducts() {
return apiGet<Product[]>('/api/admin/products')
}
export function createProduct(data: Partial<Product>) {
return apiPost<Product>('/api/admin/products', data)
}
export function updateProduct(id: number, data: Partial<Product>) {
return apiPut<Product>(`/api/admin/products/${id}`, data)
}
export function onShelfProduct(id: number) {
return apiPut<Product>(`/api/admin/products/${id}/on-shelf`)
}
export function offShelfProduct(id: number) {
return apiPut<Product>(`/api/admin/products/${id}/off-shelf`)
}
export function listProductCategories() {
return apiGet<ProductCategory[]>('/api/admin/product-categories')
}
export function createProductCategory(data: Partial<ProductCategory>) {
return apiPost<ProductCategory>('/api/admin/product-categories', data)
}
export function addSku(productId: number, data: Partial<ProductSku>) {
return apiPost<ProductSku>(`/api/admin/products/${productId}/skus`, data)
}
export function updateSku(productId: number, skuId: number, data: Partial<ProductSku>) {
return apiPut<ProductSku>(`/api/admin/products/${productId}/skus/${skuId}`, data)
}

View File

@@ -0,0 +1,18 @@
import type { SecondGoods } from '../types'
import { apiGet, apiPut } from './http'
export function listSecondGoods(status?: string) {
return apiGet<SecondGoods[]>('/api/admin/second-goods', status ? { status } : undefined)
}
export function approveSecondGoods(id: number) {
return apiPut<SecondGoods>(`/api/admin/second-goods/${id}/approve`)
}
export function rejectSecondGoods(id: number, reason: string) {
return apiPut<SecondGoods>(`/api/admin/second-goods/${id}/reject`, { reason })
}
export function offShelfSecondGoods(id: number) {
return apiPut<SecondGoods>(`/api/admin/second-goods/${id}/off-shelf`)
}