提交 1710568d authored 作者: 王鹏飞's avatar 王鹏飞

feat: 添加订单导出和删除功能,更新订单查询条件,优化订单列表视图

上级 73818823
......@@ -16,6 +16,10 @@ export interface OrderRow {
status_name?: string
pay_type?: number | string
pay_type_name?: string
transaction_id?: string | number
trade_no?: string | number
original_transaction_id?: string | number
transaction_no?: string
rating?: number
}
......@@ -69,6 +73,14 @@ export async function getOrders(query: OrdersQuery): Promise<OrdersListResult> {
return { rows: data.list ?? [], total: data.total ?? 0 }
}
export async function exportOrders(query: OrdersQuery) {
const data = await getPayload<{ name?: string; download_url?: string }>(
'/api/admin/order/export',
query,
)
return data
}
export function getSalesStatistics() {
return getPayload<SalesStatisticsResult>('/api/admin/order/salesStatistics')
}
......@@ -78,3 +90,9 @@ export async function refundOrder(id: number, refund_reason?: string) {
notifySuccess(response.message)
return true
}
export async function deleteOrder(id: number) {
const response = await axios.post('/api/admin/order/del', { id })
notifySuccess(response.message)
return true
}
......@@ -2,6 +2,7 @@ import type { Dayjs } from 'dayjs'
export interface OrdersFilters {
book_name?: string
members_name?: string
status?: string | null
pay_type?: string | null
pay_time?: [Dayjs | null, Dayjs | null] | null
......@@ -11,6 +12,7 @@ export interface OrdersQuery {
page: number
page_size: number
book_name?: string
members_name?: string
status?: string
pay_type?: string
start_pay?: number
......@@ -26,6 +28,7 @@ interface BuildOrdersQueryInput {
export function buildOrdersQuery({ page, pageSize, filters }: BuildOrdersQueryInput): OrdersQuery {
const query: OrdersQuery = { page, page_size: pageSize }
if (filters.book_name) query.book_name = filters.book_name
if (filters.members_name) query.members_name = filters.members_name
if (filters.status) query.status = filters.status
if (filters.pay_type) query.pay_type = filters.pay_type
if (filters.pay_time?.[0] && filters.pay_time[1]) {
......
import { Button, DatePicker, Input, Rate, Select, Tooltip } from 'antd'
import { Button, DatePicker, Input, Popconfirm, Rate, Select, Tooltip } from 'antd'
import { DownloadOutlined } from '@ant-design/icons'
import type { ColumnsType } from 'antd/es/table'
import { saveAs } from 'file-saver'
import { useState } from 'react'
import wechatIcon from '@/assets/images/vx.png'
......@@ -8,17 +10,23 @@ import coinIcon from '@/assets/images/money.png'
import appleIcon from '@/assets/images/apple.svg'
import { AppList, type AppListFilterItem } from '@/components/AppList'
import { usePermission } from '@/hooks/usePermission'
import { useUserStore } from '@/stores/userStore'
import { refundOrder, type OrderRow } from '../api'
import { deleteOrder, exportOrders, refundOrder, type OrderRow } from '../api'
import RefundDrawer from '../components/RefundDrawer'
import type { OrdersFilters } from '../query'
import { buildOrdersQuery, type OrdersFilters } from '../query'
import { useOrdersList } from '../useOrdersList'
const { RangePicker } = DatePicker
export default function OrdersListView() {
const list = useOrdersList()
const checkPermission = usePermission()
const userInfo = useUserStore((state) => state.userInfo)
const [refundId, setRefundId] = useState<number | null>(null)
const [exporting, setExporting] = useState(false)
const [deletingId, setDeletingId] = useState<number | null>(null)
const canDelete = userInfo.tel === '13811534871'
const submitRefund = async ({ refund_reason }: { refund_reason?: string }) => {
if (refundId === null) return
......@@ -28,6 +36,29 @@ export default function OrdersListView() {
setRefundId(null)
}
const handleExport = async () => {
setExporting(true)
try {
const result = await exportOrders(
buildOrdersQuery({ page: 1, pageSize: list.pageSize, filters: list.filters }),
)
if (!result.name || !result.download_url) return
saveAs(`/api/admin${result.download_url}`, result.name)
} finally {
setExporting(false)
}
}
const handleDelete = async (id: number) => {
setDeletingId(id)
try {
await deleteOrder(id)
list.refresh()
} finally {
setDeletingId(null)
}
}
const columns: ColumnsType<OrderRow> = [
{ title: '订单编号', key: 'id', dataIndex: 'id', align: 'center' },
{ title: '书籍名称', key: 'book_name', dataIndex: 'book_name', align: 'center', width: 150 },
......@@ -74,6 +105,13 @@ export default function OrdersListView() {
)
},
},
{
title: '交易流水号',
key: 'transaction_no',
dataIndex: 'transaction_no',
align: 'center',
width: 180,
},
{
title: '评价',
key: 'rating',
......@@ -88,9 +126,21 @@ export default function OrdersListView() {
dataIndex: 'handle',
align: 'center',
render: (_, record) =>
checkPermission('/books/order-management/refund') && Number(record.status) === 3 ? (
<Button onClick={() => setRefundId(record.id)}>退款</Button>
) : null,
<>
{checkPermission('/books/order-management/refund') && Number(record.status) === 3 ? (
<Button onClick={() => setRefundId(record.id)}>退款</Button>
) : null}
{canDelete && checkPermission('/books/order-management/delete') ? (
<Popconfirm
title='确定删除这个订单吗?'
okText='删除'
cancelText='取消'
onConfirm={() => handleDelete(record.id)}
>
<Button danger style={{ marginLeft: 8 }} loading={deletingId === record.id}>删除</Button>
</Popconfirm>
) : null}
</>,
},
]
......@@ -109,6 +159,19 @@ export default function OrdersListView() {
/>
),
},
{
name: 'members_name',
label: '购买人',
render: (field) => (
<Input
allowClear
autoComplete="off"
placeholder="请输入购买人名称"
value={field.value ?? ''}
onChange={(event) => field.onChange(event.target.value)}
/>
),
},
{
name: 'status',
label: '支付状态',
......@@ -167,7 +230,7 @@ export default function OrdersListView() {
<AppList<OrderRow, OrdersFilters>
rowKey="id"
columns={columns}
data={{ rows: list.rows, total: list.total, loading: list.loading, error: list.error }}
data={{ rows: list.rows, total: list.total, loading: list.loading || exporting, error: list.error }}
filters={{
value: list.filters,
items: filterItems,
......@@ -183,6 +246,18 @@ export default function OrdersListView() {
onChange: list.changePage,
onPageSizeChange: list.changePageSize,
}}
toolbar={
checkPermission('/books/order-management/export') ? (
<Button
type='primary'
icon={<DownloadOutlined />}
loading={exporting}
onClick={handleExport}
>
导出
</Button>
) : null
}
onRetry={list.retry}
/>
<RefundDrawer open={refundId !== null} onClose={() => setRefundId(null)} onSubmit={submitRefund} />
......
......@@ -10,6 +10,7 @@ export interface TreeChapter {
export interface UserInfo {
id?: number
type?: number
tel?: string
[key: string]: unknown
}
......
......@@ -7,21 +7,21 @@ import mkcert from 'vite-plugin-mkcert'
// https://vitejs.dev/config/
export default defineConfig(() => {
return {
base: '/',
base: '/',
plugins: [react(), mkcert()],
server: {
open: '/',
open: '/',
host: 'dev.ezijing.com',
port: 5174,
strictPort: true,
proxy: {
'/api/admin': {
target: 'http://127.0.0.1:7419',
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/api\/admin/, ''),
},
'/api': 'https://zijingebook.ezijing.com',
// '/api/admin': {
// target: 'http://127.0.0.1:7419',
// changeOrigin: true,
// secure: false,
// rewrite: (path) => path.replace(/^\/api\/admin/, ''),
// },
},
},
build: {
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论