2025-06-25 13:16:06 +08:00
|
|
|
<template>
|
|
|
|
<!-- 搜索框 -->
|
|
|
|
<div class="search-box">
|
|
|
|
<a-form layout="inline">
|
|
|
|
<a-space>
|
|
|
|
<a-form-item label="订单号">
|
|
|
|
<a-input-search
|
|
|
|
style="width: 200px"
|
|
|
|
placeholder="请输入订单号"
|
|
|
|
enter-button
|
|
|
|
@search="handleSearch"
|
|
|
|
v-model:value="searchOrderNumber"
|
|
|
|
class="custom-search"
|
|
|
|
/>
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
<a-form-item label="订单状态">
|
|
|
|
<a-select
|
|
|
|
style="width: 120px"
|
|
|
|
placeholder="请选择"
|
|
|
|
v-model:value="searchOrderStatus"
|
|
|
|
@change="handleSearch"
|
|
|
|
>
|
|
|
|
<a-select-option value="">全部</a-select-option>
|
|
|
|
<a-select-option value="交易关闭">交易关闭</a-select-option>
|
|
|
|
<a-select-option value="交易成功">交易成功</a-select-option>
|
|
|
|
<a-select-option value="待支付">待支付</a-select-option>
|
|
|
|
<a-select-option value="已退款">已退款</a-select-option>
|
|
|
|
</a-select>
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
<a-button class="custom-button" @click="reset">重置搜索</a-button>
|
|
|
|
<a-button
|
|
|
|
class="custom-button danger"
|
|
|
|
:disabled="selectedRowKeys.length === 0"
|
|
|
|
@click="showDeleteConfirm"
|
|
|
|
>
|
|
|
|
批量删除
|
|
|
|
</a-button>
|
|
|
|
</a-space>
|
|
|
|
</a-form>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 数据-->
|
|
|
|
<a-table
|
|
|
|
:columns="columns"
|
|
|
|
:data-source="tableData"
|
|
|
|
:scroll="{ x: 2000, y: 550 }"
|
|
|
|
:loading="loading"
|
|
|
|
:pagination="pagination"
|
|
|
|
:row-selection="rowSelection"
|
|
|
|
bordered
|
|
|
|
rowKey="id"
|
|
|
|
@change="handleTableChange"
|
|
|
|
>
|
|
|
|
<template #bodyCell="{ column, record }">
|
|
|
|
<!-- 课程图片 -->
|
|
|
|
<template v-if="column.key === 'image'">
|
|
|
|
<a-avatar :src="downLoadImage+record.image" shape="square" :size="64"/>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<!-- 金额字段 -->
|
|
|
|
<template v-if="column.key === 'originPrice'">
|
|
|
|
{{ record.originPrice }}¥
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<template v-if="column.key === 'discountPrice'">
|
|
|
|
{{ record.discountPrice }}¥
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<template v-if="column.key === 'totalAmount'">
|
|
|
|
{{ record.totalAmount }}¥
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<!-- 订单状态 -->
|
|
|
|
<template v-if="column.key === 'orderStatus'">
|
|
|
|
<a-tag
|
|
|
|
:color="getStatusColor(record.orderStatus)"
|
|
|
|
>
|
|
|
|
{{ record.orderStatus }}
|
|
|
|
</a-tag>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<!-- 操作列 -->
|
|
|
|
<template v-if="column.key === 'action'">
|
|
|
|
<a-space :size="8">
|
|
|
|
<a-button
|
|
|
|
size="small"
|
|
|
|
danger
|
|
|
|
@click="deleteOrder(record.id)"
|
|
|
|
>
|
|
|
|
删除
|
|
|
|
</a-button>
|
2025-07-06 22:25:17 +08:00
|
|
|
<a-button
|
|
|
|
size="small"
|
|
|
|
primary
|
|
|
|
|
|
|
|
>
|
|
|
|
退款
|
|
|
|
</a-button>
|
2025-06-25 13:16:06 +08:00
|
|
|
</a-space>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</a-table>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
import { onMounted, ref } from "vue";
|
|
|
|
import myAxios from "../../api/myAxios.ts";
|
|
|
|
import { message, Modal } from "ant-design-vue";
|
|
|
|
import {downLoadImage} from "../../api/ImageUrl.ts";
|
|
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
const selectedRowKeys = ref<number[]>([]); // 存储选中的行ID
|
|
|
|
|
|
|
|
// 搜索参数
|
|
|
|
const searchOrderNumber = ref("");
|
|
|
|
const searchOrderStatus = ref("");
|
|
|
|
|
|
|
|
// 分页查询参数
|
|
|
|
const searchParams = ref({
|
|
|
|
current: 1,
|
|
|
|
pageSize: 10,
|
|
|
|
sortField: "id",
|
|
|
|
sortOrder: "ascend",
|
|
|
|
orderNumber: "",
|
|
|
|
orderStatus: ""
|
|
|
|
});
|
|
|
|
|
|
|
|
// 行选择配置
|
|
|
|
const rowSelection = ref({
|
|
|
|
selectedRowKeys: selectedRowKeys,
|
|
|
|
onChange: (selectedKeys: number[]) => {
|
|
|
|
selectedRowKeys.value = selectedKeys;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// 订单表格列定义
|
|
|
|
const columns = [
|
|
|
|
{
|
|
|
|
title: '订单ID',
|
|
|
|
dataIndex: 'id',
|
|
|
|
width: 85,
|
|
|
|
key: 'id',
|
|
|
|
fixed: 'left',
|
|
|
|
align: 'center',
|
|
|
|
sorter: true,
|
|
|
|
sortDirections: ['ascend', 'descend']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '订单号',
|
|
|
|
dataIndex: 'orderNumber',
|
|
|
|
width: 200,
|
|
|
|
key: 'orderNumber',
|
|
|
|
fixed: 'left',
|
|
|
|
align: 'center'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '课程图片',
|
|
|
|
dataIndex: 'image',
|
|
|
|
key: 'image',
|
|
|
|
width: 75,
|
|
|
|
align: 'center'
|
|
|
|
},
|
2025-07-06 22:25:17 +08:00
|
|
|
{
|
|
|
|
title: '用户ID',
|
|
|
|
dataIndex: 'userId',
|
|
|
|
width: 90,
|
|
|
|
key: 'userId',
|
|
|
|
align: 'center'
|
|
|
|
},{
|
|
|
|
title: '课程ID',
|
|
|
|
dataIndex: 'courseId',
|
|
|
|
width: 90,
|
|
|
|
key: 'courseId',
|
|
|
|
align: 'center'
|
|
|
|
},
|
2025-06-25 13:16:06 +08:00
|
|
|
{
|
|
|
|
title: '课程名称',
|
|
|
|
dataIndex: 'name',
|
|
|
|
width: 140,
|
|
|
|
key: 'name',
|
|
|
|
align: 'center'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '课程类别',
|
|
|
|
dataIndex: 'type',
|
|
|
|
width: 75,
|
|
|
|
key: 'type',
|
|
|
|
align: 'center'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '课程原价',
|
|
|
|
dataIndex: 'originPrice',
|
|
|
|
width: 95,
|
|
|
|
key: 'originPrice',
|
|
|
|
align: 'center',
|
|
|
|
sorter: true,
|
|
|
|
sortDirections: ['ascend', 'descend']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '折扣价格',
|
|
|
|
dataIndex: 'discountPrice',
|
|
|
|
key: 'discountPrice',
|
|
|
|
width: 95,
|
|
|
|
align: 'center',
|
|
|
|
sorter: true,
|
|
|
|
sortDirections: ['ascend', 'descend']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '订单金额',
|
|
|
|
dataIndex: 'totalAmount',
|
|
|
|
key: 'totalAmount',
|
|
|
|
width: 95,
|
|
|
|
align: 'center',
|
|
|
|
sorter: true,
|
|
|
|
sortDirections: ['ascend', 'descend']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '交易号',
|
|
|
|
dataIndex: 'transactionNumber',
|
|
|
|
key: 'transactionNumber',
|
|
|
|
width: 150,
|
|
|
|
align: 'center'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '订单状态',
|
|
|
|
dataIndex: 'orderStatus',
|
|
|
|
key: 'orderStatus',
|
|
|
|
width: 75,
|
|
|
|
align: 'center'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '创建时间',
|
|
|
|
dataIndex: 'createTime',
|
|
|
|
key: 'createTime',
|
|
|
|
width: 150,
|
|
|
|
align: 'center',
|
|
|
|
sorter: true,
|
|
|
|
sortDirections: ['ascend', 'descend']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '更新时间',
|
|
|
|
dataIndex: 'updateTime',
|
|
|
|
key: 'updateTime',
|
|
|
|
width: 150,
|
|
|
|
align: 'center',
|
|
|
|
sorter: true,
|
|
|
|
sortDirections: ['ascend', 'descend']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '操作',
|
|
|
|
key: 'action',
|
|
|
|
fixed: 'right',
|
2025-07-06 22:25:17 +08:00
|
|
|
width: 130,
|
2025-06-25 13:16:06 +08:00
|
|
|
align: 'center'
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
// 订单搜索方法
|
|
|
|
const handleSearch = async () => {
|
|
|
|
searchParams.value.orderNumber = searchOrderNumber.value;
|
|
|
|
searchParams.value.orderStatus = searchOrderStatus.value;
|
|
|
|
searchParams.value.current = 1; // 重置到第一页
|
|
|
|
await getOrderList();
|
|
|
|
};
|
|
|
|
|
|
|
|
// 订单分页查询
|
|
|
|
const getOrderList = async () => {
|
|
|
|
loading.value = true;
|
|
|
|
try {
|
|
|
|
const storedToken = localStorage.getItem('token');
|
|
|
|
if (!storedToken) throw new Error('未找到登录信息');
|
|
|
|
|
|
|
|
const res: any = await myAxios.post("/courseOrder/page", searchParams.value,
|
|
|
|
{ headers: { Authorization: storedToken } }
|
|
|
|
);
|
|
|
|
console.log(res)
|
|
|
|
if (res.code === 1 && res.data && Array.isArray(res.data.records)) {
|
|
|
|
tableData.value = res.data.records;
|
|
|
|
// 同步总条数到分页组件
|
|
|
|
pagination.value.total = res.data.total;
|
|
|
|
} else {
|
|
|
|
message.error(res.message || '请求失败');
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error("请求失败:", error);
|
|
|
|
message.error('获取数据失败');
|
|
|
|
} finally {
|
|
|
|
loading.value = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
onMounted(getOrderList);
|
|
|
|
|
|
|
|
// 分页配置
|
|
|
|
const pagination = ref({
|
|
|
|
current: 1,
|
|
|
|
pageSize: 10,
|
|
|
|
total: 0,
|
|
|
|
showSizeChanger: true,
|
|
|
|
showQuickJumper: true,
|
|
|
|
showTotal: (total: number) => `共 ${total} 条`,
|
|
|
|
pageSizeOptions: ['10', '20', '50', '100']
|
|
|
|
});
|
|
|
|
|
|
|
|
// 处理分页和排序变化
|
|
|
|
const handleTableChange = (pag: any, _filters: any, sorter: any) => {
|
|
|
|
searchParams.value.current = pag.current;
|
|
|
|
searchParams.value.pageSize = pag.pageSize;
|
|
|
|
|
|
|
|
// 处理排序
|
|
|
|
if (sorter && sorter.field) {
|
|
|
|
const sortField = sorter.field;
|
|
|
|
|
|
|
|
const sortOrder = sorter.order ? sorter.order : '';
|
|
|
|
|
|
|
|
searchParams.value.sortField = sortField;
|
|
|
|
searchParams.value.sortOrder = sortOrder;
|
|
|
|
} else {
|
2025-07-02 09:43:17 +08:00
|
|
|
|
2025-06-25 13:16:06 +08:00
|
|
|
searchParams.value.sortField = "id";
|
|
|
|
searchParams.value.sortOrder = "ascend";
|
|
|
|
}
|
|
|
|
|
|
|
|
// 同步到分页组件
|
|
|
|
pagination.value.current = pag.current;
|
|
|
|
pagination.value.pageSize = pag.pageSize;
|
|
|
|
|
|
|
|
getOrderList();
|
|
|
|
};
|
|
|
|
|
|
|
|
// 订单接口定义
|
|
|
|
interface Order {
|
|
|
|
id: number;
|
|
|
|
orderNumber: string;
|
|
|
|
name: string;
|
|
|
|
type: string;
|
|
|
|
image: string;
|
|
|
|
originprice: number;
|
|
|
|
discountprice: number;
|
|
|
|
totalAmount: number;
|
|
|
|
transactionNumber: string | null;
|
|
|
|
orderStatus: string;
|
|
|
|
createtime: string;
|
|
|
|
updateTime: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
const tableData = ref<Order[]>([]);
|
|
|
|
|
|
|
|
// 获取状态颜色
|
|
|
|
const getStatusColor = (status: string) => {
|
|
|
|
switch (status) {
|
|
|
|
case '交易成功':
|
|
|
|
return 'green';
|
|
|
|
case '待支付':
|
|
|
|
return 'orange';
|
|
|
|
case '交易关闭':
|
|
|
|
return 'red';
|
|
|
|
case '已退款':
|
|
|
|
return 'blue';
|
|
|
|
default:
|
|
|
|
return 'gray';
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 删除订单
|
|
|
|
const deleteOrder = (id: number) => {
|
|
|
|
Modal.confirm({
|
|
|
|
title: '确认删除',
|
|
|
|
content: '确定要删除该订单吗?删除后数据将无法恢复!',
|
|
|
|
okText: '确认',
|
|
|
|
cancelText: '取消',
|
|
|
|
onOk: async () => {
|
|
|
|
try {
|
|
|
|
const storedToken = localStorage.getItem('token');
|
|
|
|
const res: any = await myAxios.post(
|
|
|
|
"/courseOrder/delete",
|
|
|
|
{ id },
|
|
|
|
{ headers: { Authorization: storedToken } }
|
|
|
|
);
|
|
|
|
|
|
|
|
if (res.code === 1) {
|
|
|
|
message.success('删除成功');
|
|
|
|
await getOrderList();
|
|
|
|
} else {
|
|
|
|
message.error(res.message || '删除失败');
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('删除失败:', error);
|
|
|
|
message.error('删除操作失败');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// 批量删除确认弹窗
|
|
|
|
const showDeleteConfirm = () => {
|
|
|
|
if (selectedRowKeys.value.length === 0) {
|
|
|
|
message.warning('请至少选择一个订单');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Modal.confirm({
|
|
|
|
title: '确认批量删除',
|
|
|
|
content: `确定要删除选中的 ${selectedRowKeys.value.length} 个订单吗?删除后数据将无法恢复!`,
|
|
|
|
okText: '确认',
|
|
|
|
cancelText: '取消',
|
|
|
|
onOk: deleteBatchOrders
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// 批量删除方法
|
|
|
|
const deleteBatchOrders = async () => {
|
|
|
|
try {
|
|
|
|
const storedToken = localStorage.getItem('token');
|
|
|
|
const res: any = await myAxios.post(
|
|
|
|
"/courseOrder/delBatch",
|
|
|
|
{ ids: selectedRowKeys.value },
|
|
|
|
{ headers: { Authorization: storedToken } }
|
|
|
|
);
|
|
|
|
|
|
|
|
if (res.code === 1) {
|
|
|
|
message.success(`成功删除 ${selectedRowKeys.value.length} 个订单`);
|
|
|
|
selectedRowKeys.value = []; // 清空选择
|
|
|
|
await getOrderList(); // 刷新列表
|
|
|
|
} else {
|
|
|
|
message.error(res.message || '批量删除失败');
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('批量删除失败:', error);
|
|
|
|
message.error('批量删除操作失败');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 重置搜索条件
|
|
|
|
const reset = () => {
|
|
|
|
searchOrderNumber.value = "";
|
|
|
|
searchOrderStatus.value = "";
|
|
|
|
selectedRowKeys.value = [];
|
|
|
|
|
|
|
|
searchParams.value = {
|
|
|
|
current: 1,
|
|
|
|
pageSize: 10,
|
|
|
|
sortField: "id",
|
|
|
|
sortOrder: "ascend",
|
|
|
|
orderNumber: "",
|
|
|
|
orderStatus: ""
|
|
|
|
};
|
|
|
|
|
|
|
|
getOrderList();
|
|
|
|
};
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
.search-box {
|
|
|
|
margin-bottom: 10px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.danger {
|
|
|
|
background-color: #ff4d4f !important;
|
|
|
|
border-color: #ff4d4f !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
.custom-button {
|
|
|
|
background-color: #ffa940;
|
|
|
|
border-color: #ffa940;
|
|
|
|
color: #fff;
|
|
|
|
}
|
|
|
|
|
|
|
|
.custom-search :deep(.ant-input-search-button) {
|
|
|
|
background-color: #ffa940;
|
|
|
|
border-color: #ffa940;
|
|
|
|
}
|
|
|
|
</style>
|