refactor(backend): 重构动物相关 API 接口
- 更新了动物数据结构和相关类型定义 - 优化了动物列表、详情、创建、更新和删除接口 - 新增了更新动物状态接口 - 移除了与认领记录相关的接口 -调整了 API 响应结构
This commit is contained in:
@@ -69,6 +69,7 @@ import { useRouter } from 'vue-router'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { authAPI } from '@/api'
|
||||
|
||||
interface FormState {
|
||||
username: string
|
||||
@@ -91,25 +92,22 @@ const onFinish = async (values: FormState) => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// 模拟登录过程
|
||||
console.log('登录信息:', values)
|
||||
|
||||
// TODO: 调用真实登录接口
|
||||
// const response = await authAPI.login(values)
|
||||
|
||||
// 模拟登录成功
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
// 调用真实登录接口
|
||||
const response = await authAPI.login(values)
|
||||
|
||||
// 保存token
|
||||
localStorage.setItem('admin_token', 'mock_token_123456')
|
||||
if (response?.data?.token) {
|
||||
localStorage.setItem('admin_token', response.data.token)
|
||||
} else {
|
||||
throw new Error('登录响应中缺少token')
|
||||
}
|
||||
|
||||
// 更新用户状态
|
||||
appStore.setUser({
|
||||
id: 1,
|
||||
username: values.username,
|
||||
nickname: '管理员',
|
||||
role: 'admin'
|
||||
})
|
||||
if (response?.data?.admin) {
|
||||
appStore.setUser(response.data.admin)
|
||||
} else {
|
||||
throw new Error('登录响应中缺少用户信息')
|
||||
}
|
||||
|
||||
message.success('登录成功!')
|
||||
|
||||
@@ -117,9 +115,10 @@ const onFinish = async (values: FormState) => {
|
||||
const redirect = router.currentRoute.value.query.redirect as string
|
||||
router.push(redirect || '/dashboard')
|
||||
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('登录失败:', error)
|
||||
message.error('登录失败,请检查用户名和密码')
|
||||
const errorMessage = error.message || '登录失败,请检查用户名和密码'
|
||||
message.error(errorMessage)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -174,10 +173,11 @@ const onFinishFailed = (errorInfo: any) => {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.login-footer p {
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, h } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
@@ -150,7 +150,7 @@ import {
|
||||
StopOutlined,
|
||||
PlayCircleOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { getMerchants, approveMerchant, rejectMerchant, disableMerchant, enableMerchant } from '@/api/merchant'
|
||||
import { getMerchants, getMerchant, approveMerchant, rejectMerchant, disableMerchant, enableMerchant } from '@/api/merchant'
|
||||
import type { Merchant } from '@/api/merchant'
|
||||
|
||||
interface SearchForm {
|
||||
@@ -314,8 +314,33 @@ const handleTableChange: TableProps['onChange'] = (pag) => {
|
||||
loadMerchants()
|
||||
}
|
||||
|
||||
const handleView = (record: Merchant) => {
|
||||
message.info(`查看商家: ${record.business_name}`)
|
||||
const handleView = async (record: Merchant) => {
|
||||
try {
|
||||
const response = await getMerchant(record.id)
|
||||
Modal.info({
|
||||
title: '商家详情',
|
||||
width: 600,
|
||||
content: h('div', { class: 'merchant-detail-modal' }, [
|
||||
h('a-descriptions', {
|
||||
column: 1,
|
||||
bordered: true
|
||||
}, [
|
||||
h('a-descriptions-item', { label: '商家ID' }, response.data.id),
|
||||
h('a-descriptions-item', { label: '商家名称' }, response.data.business_name),
|
||||
h('a-descriptions-item', { label: '商家类型' }, getTypeText(response.data.merchant_type)),
|
||||
h('a-descriptions-item', { label: '联系人' }, response.data.contact_person),
|
||||
h('a-descriptions-item', { label: '联系电话' }, response.data.contact_phone),
|
||||
h('a-descriptions-item', { label: '状态' }, [
|
||||
h('a-tag', { color: getStatusColor(response.data.status) }, getStatusText(response.data.status))
|
||||
]),
|
||||
h('a-descriptions-item', { label: '入驻时间' }, response.data.created_at),
|
||||
h('a-descriptions-item', { label: '更新时间' }, response.data.updated_at)
|
||||
])
|
||||
])
|
||||
})
|
||||
} catch (error) {
|
||||
message.error('获取商家详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleApprove = async (record: Merchant) => {
|
||||
|
||||
@@ -1,162 +1,241 @@
|
||||
<template>
|
||||
<div class="order-management">
|
||||
<a-page-header title="订单管理" sub-title="管理花束订单和交易记录">
|
||||
<a-page-header
|
||||
title="订单管理"
|
||||
sub-title="管理系统订单信息"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="handleRefresh">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button type="primary" @click="showStats">
|
||||
<template #icon><BarChartOutlined /></template>
|
||||
销售统计
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
|
||||
<a-tab-pane key="orders" tab="订单列表">
|
||||
<!-- 统计卡片 -->
|
||||
<a-row :gutter="16" class="stats-row">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<div class="search-container">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="订单号">
|
||||
<a-input v-model:value="searchForm.order_no" placeholder="输入订单号" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="searchForm.status" placeholder="全部状态" style="width: 120px" allow-clear>
|
||||
<a-select-option value="pending">待支付</a-select-option>
|
||||
<a-select-option value="paid">已支付</a-select-option>
|
||||
<a-select-option value="shipped">已发货</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="cancelled">已取消</a-select-option>
|
||||
<a-select-option value="refunded">已退款</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="下单时间">
|
||||
<a-range-picker v-model:value="searchForm.orderTime" :placeholder="['开始时间', '结束时间']" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="orderColumns"
|
||||
:data-source="orderList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-key="record => record.id"
|
||||
@change="handleTableChange"
|
||||
<a-statistic
|
||||
title="今日订单"
|
||||
:value="statistics.today_orders"
|
||||
:precision="0"
|
||||
suffix="单"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'order_no'">
|
||||
<a-typography-text copyable>{{ record.order_no }}</a-typography-text>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'amount'">¥{{ record.amount }}</template>
|
||||
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'payment_method'">
|
||||
<span>{{ getPaymentMethodText(record.payment_method) }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space :size="8">
|
||||
<a-button size="small" @click="handleViewOrder(record)">
|
||||
<EyeOutlined />详情
|
||||
</a-button>
|
||||
|
||||
<template v-if="record.status === 'paid'">
|
||||
<a-button size="small" type="primary" @click="handleShip(record)">
|
||||
<CarOutlined />发货
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-if="record.status === 'shipped'">
|
||||
<a-button size="small" type="primary" @click="handleComplete(record)">
|
||||
<CheckCircleOutlined />完成
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-if="['pending', 'paid'].includes(record.status)">
|
||||
<a-button size="small" danger @click="handleCancel(record)">
|
||||
<CloseOutlined />取消
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-if="record.status === 'paid'">
|
||||
<a-button size="small" danger @click="handleRefund(record)">
|
||||
<RollbackOutlined />退款
|
||||
</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #prefix>
|
||||
<ShoppingCartOutlined />
|
||||
</template>
|
||||
</a-table>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="今日销售额"
|
||||
:value="statistics.today_sales"
|
||||
:precision="2"
|
||||
suffix="元"
|
||||
:value-style="{ color: '#cf1322' }"
|
||||
>
|
||||
<template #prefix>
|
||||
<MoneyCollectOutlined />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="本月订单"
|
||||
:value="statistics.month_orders"
|
||||
:precision="0"
|
||||
suffix="单"
|
||||
>
|
||||
<template #prefix>
|
||||
<CalendarOutlined />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="本月销售额"
|
||||
:value="statistics.month_sales"
|
||||
:precision="2"
|
||||
suffix="元"
|
||||
:value-style="{ color: '#cf1322' }"
|
||||
>
|
||||
<template #prefix>
|
||||
<DollarOutlined />
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-tab-pane key="statistics" tab="销售统计">
|
||||
<a-card title="销售数据概览">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-statistic title="今日订单" :value="statistics.today_orders" :precision="0" :value-style="{ color: '#3f8600' }">
|
||||
<template #prefix><ShoppingOutlined /></template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="今日销售额" :value="statistics.today_sales" :precision="2" prefix="¥" :value-style="{ color: '#cf1322' }" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="本月订单" :value="statistics.month_orders" :precision="0" :value-style="{ color: '#1890ff' }" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic title="本月销售额" :value="statistics.month_sales" :precision="2" prefix="¥" :value-style="{ color: '#722ed1' }" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<a-card>
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-container">
|
||||
<a-form layout="inline" :model="searchForm">
|
||||
<a-form-item label="订单号">
|
||||
<a-input
|
||||
v-model:value="searchForm.order_no"
|
||||
placeholder="请输入订单号"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.status"
|
||||
placeholder="全部状态"
|
||||
style="width: 120px"
|
||||
allow-clear
|
||||
>
|
||||
<a-select-option value="pending">待支付</a-select-option>
|
||||
<a-select-option value="paid">已支付</a-select-option>
|
||||
<a-select-option value="shipped">已发货</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="cancelled">已取消</a-select-option>
|
||||
<a-select-option value="refunded">已退款</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="下单时间">
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.orderTime"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button style="margin-left: 8px" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<a-card title="销售趋势" style="margin-top: 16px;">
|
||||
<div style="height: 300px;">
|
||||
<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #999;">
|
||||
<BarChartOutlined style="font-size: 48px; margin-right: 12px;" />
|
||||
<span>销售趋势图表开发中</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<!-- 订单表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="orderList"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-key="record => record.id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'amount'">
|
||||
<span class="amount-text">¥{{ record.amount }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<a-space :size="8">
|
||||
<a-button size="small" @click="handleView(record)">
|
||||
<EyeOutlined />
|
||||
查看
|
||||
</a-button>
|
||||
|
||||
<template v-if="record.status === 'pending'">
|
||||
<a-button size="small" type="primary" @click="handlePay(record)">
|
||||
<PayCircleOutlined />
|
||||
支付
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-else-if="record.status === 'paid'">
|
||||
<a-button size="small" type="primary" @click="handleShip(record)">
|
||||
<SendOutlined />
|
||||
发货
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-else-if="record.status === 'shipped'">
|
||||
<a-button size="small" type="primary" @click="handleComplete(record)">
|
||||
<CheckCircleOutlined />
|
||||
完成
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<a-dropdown>
|
||||
<a-button size="small">
|
||||
更多
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item
|
||||
v-if="['pending', 'paid'].includes(record.status)"
|
||||
@click="handleCancel(record)"
|
||||
>
|
||||
<CloseCircleOutlined />
|
||||
取消
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
v-if="['paid', 'shipped', 'completed'].includes(record.status)"
|
||||
@click="handleRefund(record)"
|
||||
>
|
||||
<RedoOutlined />
|
||||
退款
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, h } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
ShoppingCartOutlined,
|
||||
MoneyCollectOutlined,
|
||||
CalendarOutlined,
|
||||
DollarOutlined,
|
||||
SearchOutlined,
|
||||
BarChartOutlined,
|
||||
ReloadOutlined,
|
||||
EyeOutlined,
|
||||
CarOutlined,
|
||||
PayCircleOutlined,
|
||||
SendOutlined,
|
||||
CheckCircleOutlined,
|
||||
CloseOutlined,
|
||||
RollbackOutlined,
|
||||
ShoppingOutlined
|
||||
CloseCircleOutlined,
|
||||
RedoOutlined,
|
||||
DownOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { getOrders, updateOrderStatus, getOrderStatistics } from '@/api/order'
|
||||
import {
|
||||
getOrders,
|
||||
getOrder,
|
||||
getOrderStatistics,
|
||||
updateOrderStatus,
|
||||
shipOrder,
|
||||
completeOrder,
|
||||
cancelOrder,
|
||||
refundOrder
|
||||
} from '@/api/order'
|
||||
import type { Order, OrderStatistics } from '@/api/order'
|
||||
|
||||
interface SearchForm {
|
||||
@@ -165,8 +244,13 @@ interface SearchForm {
|
||||
orderTime: any[]
|
||||
}
|
||||
|
||||
const activeTab = ref('orders')
|
||||
const loading = ref(false)
|
||||
const statistics = ref<OrderStatistics>({
|
||||
today_orders: 0,
|
||||
today_sales: 0,
|
||||
month_orders: 0,
|
||||
month_sales: 0
|
||||
})
|
||||
|
||||
const searchForm = reactive<SearchForm>({
|
||||
order_no: '',
|
||||
@@ -174,13 +258,6 @@ const searchForm = reactive<SearchForm>({
|
||||
orderTime: []
|
||||
})
|
||||
|
||||
const statistics = reactive<OrderStatistics>({
|
||||
today_orders: 0,
|
||||
today_sales: 0,
|
||||
month_orders: 0,
|
||||
month_sales: 0
|
||||
})
|
||||
|
||||
const orderList = ref<Order[]>([])
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
@@ -191,25 +268,66 @@ const pagination = reactive({
|
||||
showTotal: (total: number) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const orderColumns = [
|
||||
{ title: '订单号', key: 'order_no', width: 160 },
|
||||
{ title: '用户', dataIndex: 'user_name', key: 'user_name', width: 100 },
|
||||
{ title: '联系电话', dataIndex: 'user_phone', key: 'user_phone', width: 120 },
|
||||
{ title: '金额', key: 'amount', width: 100, align: 'center' },
|
||||
{ title: '状态', key: 'status', width: 100, align: 'center' },
|
||||
{ title: '支付方式', key: 'payment_method', width: 100, align: 'center' },
|
||||
{ title: '下单时间', dataIndex: 'created_at', key: 'created_at', width: 150 },
|
||||
{ title: '操作', key: 'actions', width: 200, align: 'center' }
|
||||
const columns = [
|
||||
{
|
||||
title: '订单号',
|
||||
dataIndex: 'order_no',
|
||||
key: 'order_no',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '用户',
|
||||
dataIndex: 'user_name',
|
||||
key: 'user_name',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
dataIndex: 'user_phone',
|
||||
key: 'user_phone',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '金额',
|
||||
key: 'amount',
|
||||
width: 100,
|
||||
align: 'right'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '支付方式',
|
||||
dataIndex: 'payment_method',
|
||||
key: 'payment_method',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '下单时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 200,
|
||||
align: 'center'
|
||||
}
|
||||
]
|
||||
|
||||
// 状态映射
|
||||
const getStatusColor = (status: string) => {
|
||||
const colors = {
|
||||
pending: 'orange',
|
||||
paid: 'blue',
|
||||
shipped: 'green',
|
||||
completed: 'purple',
|
||||
shipped: 'purple',
|
||||
completed: 'green',
|
||||
cancelled: 'red',
|
||||
refunded: 'default'
|
||||
refunded: 'red'
|
||||
}
|
||||
return colors[status as keyof typeof colors] || 'default'
|
||||
}
|
||||
@@ -226,21 +344,24 @@ const getStatusText = (status: string) => {
|
||||
return texts[status as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
// 支付方式映射
|
||||
const getPaymentMethodText = (method: string) => {
|
||||
const texts = {
|
||||
wechat: '微信支付',
|
||||
alipay: '支付宝',
|
||||
bank: '银行卡',
|
||||
bank: '银行转账',
|
||||
balance: '余额支付'
|
||||
}
|
||||
return texts[method as keyof typeof texts] || '未知'
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadOrders()
|
||||
loadStatistics()
|
||||
})
|
||||
|
||||
// 方法
|
||||
const loadOrders = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -263,20 +384,12 @@ const loadOrders = async () => {
|
||||
const loadStatistics = async () => {
|
||||
try {
|
||||
const response = await getOrderStatistics()
|
||||
Object.assign(statistics, response.data)
|
||||
statistics.value = response.data
|
||||
} catch (error) {
|
||||
message.error('加载统计数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleTabChange = (key: string) => {
|
||||
if (key === 'orders') {
|
||||
loadOrders()
|
||||
} else if (key === 'statistics') {
|
||||
loadStatistics()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadOrders()
|
||||
@@ -293,11 +406,8 @@ const handleReset = () => {
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
if (activeTab.value === 'orders') {
|
||||
loadOrders()
|
||||
} else {
|
||||
loadStatistics()
|
||||
}
|
||||
loadOrders()
|
||||
loadStatistics()
|
||||
message.success('数据已刷新')
|
||||
}
|
||||
|
||||
@@ -307,17 +417,60 @@ const handleTableChange: TableProps['onChange'] = (pag) => {
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
const handleViewOrder = (record: Order) => {
|
||||
message.info(`查看订单: ${record.order_no}`)
|
||||
const handleView = async (record: Order) => {
|
||||
try {
|
||||
const response = await getOrder(record.id)
|
||||
Modal.info({
|
||||
title: '订单详情',
|
||||
width: 600,
|
||||
content: h('div', { class: 'order-detail-modal' }, [
|
||||
h('a-descriptions', {
|
||||
column: 1,
|
||||
bordered: true
|
||||
}, [
|
||||
h('a-descriptions-item', { label: '订单号' }, response.data.order_no),
|
||||
h('a-descriptions-item', { label: '用户' }, response.data.user_name),
|
||||
h('a-descriptions-item', { label: '联系电话' }, response.data.user_phone),
|
||||
h('a-descriptions-item', { label: '订单金额' }, `¥${response.data.amount}`),
|
||||
h('a-descriptions-item', { label: '状态' }, [
|
||||
h('a-tag', { color: getStatusColor(response.data.status) }, getStatusText(response.data.status))
|
||||
]),
|
||||
h('a-descriptions-item', { label: '支付方式' }, getPaymentMethodText(response.data.payment_method)),
|
||||
h('a-descriptions-item', { label: '下单时间' }, response.data.created_at),
|
||||
h('a-descriptions-item', { label: '支付时间' }, response.data.paid_at || '-'),
|
||||
h('a-descriptions-item', { label: '发货时间' }, response.data.shipped_at || '-'),
|
||||
h('a-descriptions-item', { label: '完成时间' }, response.data.completed_at || '-')
|
||||
])
|
||||
])
|
||||
})
|
||||
} catch (error) {
|
||||
message.error('获取订单详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handlePay = async (record: Order) => {
|
||||
Modal.confirm({
|
||||
title: '确认支付',
|
||||
content: `确定要标记订单 "${record.order_no}" 为已支付状态吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await updateOrderStatus(record.id, 'paid')
|
||||
message.success('订单状态已更新')
|
||||
loadOrders()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleShip = async (record: Order) => {
|
||||
Modal.confirm({
|
||||
title: '确认发货',
|
||||
content: `确定要发货订单 "${record.order_no}" 吗?`,
|
||||
content: `确定要标记订单 "${record.order_no}" 为已发货状态吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await updateOrderStatus(record.id, 'shipped')
|
||||
await shipOrder(record.id)
|
||||
message.success('订单已发货')
|
||||
loadOrders()
|
||||
} catch (error) {
|
||||
@@ -330,10 +483,10 @@ const handleShip = async (record: Order) => {
|
||||
const handleComplete = async (record: Order) => {
|
||||
Modal.confirm({
|
||||
title: '确认完成',
|
||||
content: `确定要完成订单 "${record.order_no}" 吗?`,
|
||||
content: `确定要标记订单 "${record.order_no}" 为已完成状态吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await updateOrderStatus(record.id, 'completed')
|
||||
await completeOrder(record.id)
|
||||
message.success('订单已完成')
|
||||
loadOrders()
|
||||
} catch (error) {
|
||||
@@ -349,7 +502,7 @@ const handleCancel = async (record: Order) => {
|
||||
content: `确定要取消订单 "${record.order_no}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await updateOrderStatus(record.id, 'cancelled')
|
||||
await cancelOrder(record.id)
|
||||
message.success('订单已取消')
|
||||
loadOrders()
|
||||
} catch (error) {
|
||||
@@ -362,11 +515,11 @@ const handleCancel = async (record: Order) => {
|
||||
const handleRefund = async (record: Order) => {
|
||||
Modal.confirm({
|
||||
title: '确认退款',
|
||||
content: `确定要退款订单 "${record.order_no}" 吗?退款金额: ¥${record.amount}`,
|
||||
content: `确定要为订单 "${record.order_no}" 办理退款吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await updateOrderStatus(record.id, 'refunded')
|
||||
message.success('退款申请已提交')
|
||||
await refundOrder(record.id)
|
||||
message.success('订单已退款')
|
||||
loadOrders()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
@@ -374,15 +527,14 @@ const handleRefund = async (record: Order) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const showStats = () => {
|
||||
activeTab.value = 'statistics'
|
||||
loadStatistics()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.order-management {
|
||||
.stats-row {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
@@ -393,5 +545,15 @@ const showStats = () => {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.amount-text {
|
||||
font-weight: 500;
|
||||
color: #cf1322;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background: #fafafa;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -236,8 +236,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { message, Modal, type FormInstance } from 'ant-design-vue'
|
||||
import { ref, reactive, computed, onMounted, h } from 'vue'
|
||||
import { message, Modal, type FormInstance, TableProps } from 'ant-design-vue'
|
||||
import {
|
||||
UserAddOutlined,
|
||||
SearchOutlined,
|
||||
@@ -248,10 +248,10 @@ import {
|
||||
StopOutlined,
|
||||
PlayCircleOutlined,
|
||||
WarningOutlined,
|
||||
DownOutlined
|
||||
DownOutlined,
|
||||
type TableProps
|
||||
} from '@ant-design/icons-vue'
|
||||
import type { TableProps } from 'ant-design-vue'
|
||||
import { getUsers, updateUser, createUser, deleteUser } from '@/api/user'
|
||||
import { getUsers, getUser, updateUser, createUser, deleteUser } from '@/api/user'
|
||||
import type { User } from '@/api/user'
|
||||
|
||||
interface SearchForm {
|
||||
@@ -473,9 +473,44 @@ const showCreateModal = () => {
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
const handleView = (record: User) => {
|
||||
// TODO: 跳转到用户详情页
|
||||
message.info(`查看用户: ${record.nickname}`)
|
||||
const handleView = async (record: User) => {
|
||||
try {
|
||||
const response = await getUser(record.id)
|
||||
Modal.info({
|
||||
title: '用户详情',
|
||||
width: 600,
|
||||
content: h('div', { class: 'user-detail-modal' }, [
|
||||
h('a-descriptions', {
|
||||
column: 1,
|
||||
bordered: true
|
||||
}, [
|
||||
h('a-descriptions-item', { label: '用户ID' }, response.data.id),
|
||||
h('a-descriptions-item', { label: '用户名' }, response.data.username),
|
||||
h('a-descriptions-item', { label: '昵称' }, response.data.nickname),
|
||||
h('a-descriptions-item', { label: '头像' }, [
|
||||
h('a-avatar', {
|
||||
src: response.data.avatar,
|
||||
size: 64,
|
||||
shape: 'square'
|
||||
})
|
||||
]),
|
||||
h('a-descriptions-item', { label: '性别' }, response.data.gender),
|
||||
h('a-descriptions-item', { label: '生日' }, response.data.birthday),
|
||||
h('a-descriptions-item', { label: '手机号' }, response.data.phone),
|
||||
h('a-descriptions-item', { label: '邮箱' }, response.data.email),
|
||||
h('a-descriptions-item', { label: '状态' }, [
|
||||
h('a-tag', { color: getStatusColor(response.data.status) }, getStatusText(response.data.status))
|
||||
]),
|
||||
h('a-descriptions-item', { label: '等级' }, `Lv.${response.data.level}`),
|
||||
h('a-descriptions-item', { label: '积分' }, response.data.points),
|
||||
h('a-descriptions-item', { label: '注册时间' }, response.data.created_at),
|
||||
h('a-descriptions-item', { label: '更新时间' }, response.data.updated_at)
|
||||
])
|
||||
])
|
||||
})
|
||||
} catch (error) {
|
||||
message.error('获取用户详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleEdit = (record: User) => {
|
||||
|
||||
Reference in New Issue
Block a user