diff --git a/frontend/src/app/AppRouter.tsx b/frontend/src/app/AppRouter.tsx
index 2a1a147..81a4478 100644
--- a/frontend/src/app/AppRouter.tsx
+++ b/frontend/src/app/AppRouter.tsx
@@ -149,6 +149,21 @@ const MobileAddHomeGuidePage = lazy(() =>
default: module.MobileAddHomeGuidePage,
}))
)
+const MobileCrashListPage = lazy(() =>
+ import('@/features/mobile/MobileCrashListPage').then(module => ({
+ default: module.MobileCrashListPage,
+ }))
+)
+const MobileCrashDetailPage = lazy(() =>
+ import('@/features/mobile/MobileCrashDetailPage').then(module => ({
+ default: module.MobileCrashDetailPage,
+ }))
+)
+const MobileCrashOrdersPage = lazy(() =>
+ import('@/features/mobile/MobileCrashOrdersPage').then(module => ({
+ default: module.MobileCrashOrdersPage,
+ }))
+)
const MobileOrdersPage = lazy(() =>
import('@/features/mobile/MobileOrdersPage').then(module => ({
default: module.MobileOrdersPage,
@@ -503,6 +518,9 @@ export function AppRouter() {
} />
} />
} />
+ } />
+ } />
+ } />
} />
diff --git a/frontend/src/features/mobile/MobileCrashDetailPage.tsx b/frontend/src/features/mobile/MobileCrashDetailPage.tsx
new file mode 100644
index 0000000..591fba1
--- /dev/null
+++ b/frontend/src/features/mobile/MobileCrashDetailPage.tsx
@@ -0,0 +1,84 @@
+import { useState } from 'react'
+import { Button, Card, Dialog, Image, NavBar, Stepper, Toast } from 'antd-mobile'
+import { useNavigate, useParams } from 'react-router-dom'
+
+import { createCrashOrder, startCrashPayment, useCrashProduct } from '@/features/crash/api'
+import { formatMoney } from '@/shared/utils/format'
+
+function errorText(error: unknown) {
+ const response = (error as { response?: { data?: { message?: string } } })?.response
+ return response?.data?.message || (error instanceof Error ? error.message : '下单失败')
+}
+
+export function MobileCrashDetailPage() {
+ const { id } = useParams()
+ const navigate = useNavigate()
+ const [quantity, setQuantity] = useState(1)
+ const [submitting, setSubmitting] = useState(false)
+ const productQuery = useCrashProduct(id)
+ const product = productQuery.data
+
+ const submit = async () => {
+ if (!product) return
+ setSubmitting(true)
+ try {
+ const order = await createCrashOrder({ product_id: product.id, quantity })
+ try {
+ const payment = await startCrashPayment(order.id)
+ if (payment?.pay_url) {
+ window.location.href = payment.pay_url
+ return
+ }
+ } catch {
+ // 支付未配置时稍后在订单页继续
+ }
+ Toast.show({ icon: 'success', content: '下单成功' })
+ navigate('/m/crash/orders')
+ } catch (error) {
+ await Dialog.alert({ content: errorText(error), confirmText: '知道了' })
+ } finally {
+ setSubmitting(false)
+ }
+ }
+
+ return (
+
+
navigate('/m/crash')}
+ right={
+
+ }
+ >
+ 商品详情
+
+
+ {product ? (
+ <>
+ {product.cover_url ? (
+
+ ) : null}
+
+
+
{product.title}
+
+ {formatMoney(product.price_cent)} · {product.category_name || '未分类'}
+
+
{product.description}
+
+ 数量
+ setQuantity(value)} />
+ 合计 {formatMoney(product.price_cent * quantity)}
+
+
+
+
+
下单需登录并完成实名;支付成功后由客服接单处理。
+ >
+ ) : null}
+
+ )
+}
diff --git a/frontend/src/features/mobile/MobileCrashListPage.tsx b/frontend/src/features/mobile/MobileCrashListPage.tsx
new file mode 100644
index 0000000..11f3dd3
--- /dev/null
+++ b/frontend/src/features/mobile/MobileCrashListPage.tsx
@@ -0,0 +1,76 @@
+import { useState } from 'react'
+import { Button, Card, ErrorBlock, Image, NavBar, Skeleton, Tag } from 'antd-mobile'
+import { useNavigate } from 'react-router-dom'
+
+import { useCrashCategories, useCrashProducts } from '@/features/crash/api'
+import { formatMoney } from '@/shared/utils/format'
+
+export function MobileCrashListPage() {
+ const navigate = useNavigate()
+ const [categoryId, setCategoryId] = useState()
+ const categoriesQuery = useCrashCategories()
+ const productsQuery = useCrashProducts({ category_id: categoryId, page: 1, page_size: 30 })
+ const items = productsQuery.data?.items ?? []
+
+ return (
+
+
navigate('/m')}
+ right={
+
+ }
+ >
+ 摸红商城
+
+
+
+ setCategoryId(undefined)}
+ >
+ 全部
+
+ {(categoriesQuery.data ?? []).map(item => (
+ setCategoryId(item.id)}
+ >
+ {item.name}
+
+ ))}
+
+
+ {productsQuery.isPending ? (
+
+
+
+ ) : items.length === 0 ? (
+
+ ) : (
+
+ {items.map(item => (
+
navigate(`/m/crash/${item.id}`)}>
+
+ {item.cover_url ? (
+
+ ) : null}
+
+
{item.title}
+
+ {formatMoney(item.price_cent)} · {item.category_name || '未分类'}
+
+
+ {item.stock < 0 ? '不限量' : `库存 ${item.stock}`}
+
+
+
+
+ ))}
+
+ )}
+
+ )
+}
diff --git a/frontend/src/features/mobile/MobileCrashOrdersPage.tsx b/frontend/src/features/mobile/MobileCrashOrdersPage.tsx
new file mode 100644
index 0000000..1a41ab9
--- /dev/null
+++ b/frontend/src/features/mobile/MobileCrashOrdersPage.tsx
@@ -0,0 +1,120 @@
+import { Button, Card, Dialog, ErrorBlock, Image, NavBar, Skeleton, Tag, Toast } from 'antd-mobile'
+import { useNavigate } from 'react-router-dom'
+import { useQueryClient } from '@tanstack/react-query'
+
+import { cancelCrashOrder, startCrashPayment, useMyCrashOrders } from '@/features/crash/api'
+import { useSessionStore } from '@/stores/session'
+import { formatDateTime, formatMoney } from '@/shared/utils/format'
+
+const statusMeta: Record = {
+ pending_payment: { label: '待支付', color: 'warning' },
+ paid: { label: '已支付', color: 'primary' },
+ received: { label: '已接单', color: 'primary' },
+ completed: { label: '已完成', color: 'success' },
+ cancelled: { label: '已取消', color: 'default' },
+ refunded: { label: '已退款', color: 'danger' },
+}
+
+function errorText(error: unknown) {
+ const response = (error as { response?: { data?: { message?: string } } })?.response
+ return response?.data?.message || (error instanceof Error ? error.message : '操作失败')
+}
+
+export function MobileCrashOrdersPage() {
+ const navigate = useNavigate()
+ const queryClient = useQueryClient()
+ const user = useSessionStore(state => state.user)
+ const ordersQuery = useMyCrashOrders(1, 30)
+ const items = ordersQuery.data?.items ?? []
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: ['crash-orders'] })
+
+ return (
+
+
navigate('/m/crash')}>我的撞车订单
+
+ {ordersQuery.isPending ? (
+
+
+
+ ) : items.length === 0 ? (
+
+ ) : (
+
+ {items.map(order => {
+ const meta = statusMeta[order.status] ?? { label: order.status, color: 'default' as const }
+ return (
+
+
+ {order.product_cover_url ? (
+
+ ) : null}
+
+
+ {order.product_title || order.order_no}
+ {meta.label}
+
+
{order.order_no}
+
+ {formatMoney(order.amount_cent)} · 数量 {order.quantity}
+
+
{formatDateTime(order.created_at)}
+ {order.status === 'pending_payment' ? (
+
+
+
+
+ ) : null}
+
+
+
+ )
+ })}
+
+ )}
+
+ {!user ?
登录后可查看自己的撞车订单。
: null}
+
+ )
+}