514 lines
12 KiB
Vue
514 lines
12 KiB
Vue
<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="handleCourseSearch"
|
||
v-model:value="searchCourseName"
|
||
class="custom-search"
|
||
/>
|
||
</a-form-item>
|
||
|
||
<a-form-item label="课程类别">
|
||
<a-select
|
||
style="width: 120px"
|
||
placeholder="请选择"
|
||
v-model:value="searchCourseType"
|
||
@change="handleCourseSearch"
|
||
>
|
||
<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-form-item label="上架状态">
|
||
<a-select
|
||
style="width: 120px"
|
||
placeholder="请选择"
|
||
v-model:value="searchIsShelves"
|
||
@change="handleCourseSearch"
|
||
>
|
||
<a-select-option value="">全部</a-select-option>
|
||
<a-select-option value="true">已上架</a-select-option>
|
||
<a-select-option value="false">已下架</a-select-option>
|
||
</a-select>
|
||
</a-form-item>
|
||
|
||
<a-button class="custom-button" @click="goAddCourse">新增课程</a-button>
|
||
<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: 1600, 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 === 'orderCount'">
|
||
{{ record.orderCount }}人
|
||
</template>
|
||
|
||
<template v-if="column.key === 'firstLevelRate'">
|
||
{{ record.firstLevelRate }}%
|
||
</template>
|
||
|
||
<template v-if="column.key === 'secondLevelRate'">
|
||
{{ record.secondLevelRate }}%
|
||
</template>
|
||
|
||
<template v-if="column.key === 'isShelves'">
|
||
<a-tag :color="record.isShelves ? 'green' : 'red'">
|
||
{{ record.isShelves ? '已上架' : '已下架' }}
|
||
</a-tag>
|
||
</template>
|
||
|
||
<!-- 操作列 -->
|
||
<template v-if="column.key === 'action'">
|
||
<a-space :size="8">
|
||
<a-button
|
||
size="small"
|
||
danger
|
||
@click="deleteCourse(record.id)"
|
||
>
|
||
删除
|
||
</a-button>
|
||
<a-button
|
||
size="small"
|
||
type="link"
|
||
@click="showDetails(record.id)"
|
||
>
|
||
详情
|
||
</a-button>
|
||
<a-button
|
||
size="small"
|
||
type="link"
|
||
@click="chapterDetails(record.id)"
|
||
>
|
||
章节
|
||
</a-button>
|
||
</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";
|
||
import router from "../../router";
|
||
|
||
const loading = ref(false);
|
||
const selectedRowKeys = ref<number[]>([]); // 存储选中的行ID
|
||
|
||
// 搜索参数
|
||
const searchCourseName = ref("");
|
||
const searchCourseType = ref("");
|
||
const searchIsShelves = ref("");
|
||
|
||
// 分页查询参数
|
||
const searchParams = ref({
|
||
current: 1,
|
||
pageSize: 10,
|
||
sortField: "id",
|
||
sortOrder: "ascend",
|
||
name: "",
|
||
type: "",
|
||
isShelves: ""
|
||
});
|
||
|
||
// 行选择配置
|
||
const rowSelection = ref({
|
||
selectedRowKeys: selectedRowKeys,
|
||
onChange: (selectedKeys: number[]) => {
|
||
selectedRowKeys.value = selectedKeys;
|
||
}
|
||
});
|
||
|
||
// 课程表格列定义
|
||
const columns = [
|
||
{
|
||
title: '课程ID',
|
||
dataIndex: 'id',
|
||
width: 45,
|
||
key: 'id',
|
||
fixed: 'left',
|
||
align: 'center',
|
||
sorter: true,
|
||
sortDirections: ['ascend', 'descend']
|
||
},
|
||
{
|
||
title: '课程图片',
|
||
dataIndex: 'image',
|
||
key: 'image',
|
||
width: 45,
|
||
fixed: 'left',
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '课程名称',
|
||
dataIndex: 'name',
|
||
width: 65,
|
||
key: 'name',
|
||
fixed: 'left',
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '课程类别',
|
||
dataIndex: 'type',
|
||
width: 55,
|
||
key: 'type',
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '课程原价',
|
||
dataIndex: 'originPrice',
|
||
width: 55,
|
||
key: 'originPrice',
|
||
align: 'center',
|
||
sorter: true,
|
||
sortDirections: ['ascend', 'descend']
|
||
},
|
||
{
|
||
title: '折扣价格',
|
||
dataIndex: 'discountPrice',
|
||
key: 'discountPrice',
|
||
width: 55,
|
||
align: 'center',
|
||
sorter: true,
|
||
sortDirections: ['ascend', 'descend']
|
||
},
|
||
{
|
||
title: '已下单人数',
|
||
dataIndex: 'orderCount',
|
||
key: 'orderCount',
|
||
width: 55,
|
||
align: 'center',
|
||
sorter: true,
|
||
sortDirections: ['ascend', 'descend']
|
||
},
|
||
{
|
||
title: '一级佣金比例',
|
||
dataIndex: 'firstLevelRate',
|
||
key: 'firstLevelRate',
|
||
width: 65,
|
||
align: 'center',
|
||
sorter: true,
|
||
sortDirections: ['ascend', 'descend']
|
||
},
|
||
{
|
||
title: '二级佣金比例',
|
||
dataIndex: 'secondLevelRate',
|
||
key: 'secondLevelRate',
|
||
width: 65,
|
||
align: 'center',
|
||
sorter: true,
|
||
sortDirections: ['ascend', 'descend']
|
||
},
|
||
{
|
||
title: '是否上架',
|
||
dataIndex: 'isShelves',
|
||
key: 'isShelves',
|
||
width: 65,
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
fixed: 'right',
|
||
width: 75,
|
||
align: 'center'
|
||
}
|
||
];
|
||
|
||
// 课程名称搜索方法
|
||
const handleCourseSearch = async () => {
|
||
searchParams.value.name = searchCourseName.value;
|
||
searchParams.value.type = searchCourseType.value;
|
||
searchParams.value.isShelves = searchIsShelves.value;
|
||
searchParams.value.current = 1; // 重置到第一页
|
||
await getCourseList();
|
||
};
|
||
|
||
// 课程分页查询
|
||
const getCourseList = async () => {
|
||
loading.value = true;
|
||
try {
|
||
const storedToken = localStorage.getItem('token');
|
||
if (!storedToken) throw new Error('未找到登录信息');
|
||
|
||
const res: any = await myAxios.post("/course/page", searchParams.value,
|
||
{ headers: { Authorization: storedToken } }
|
||
);
|
||
|
||
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(getCourseList);
|
||
|
||
// 分页配置
|
||
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) {
|
||
// 获取排序字段(使用列的key而不是dataIndex)
|
||
const sortField = sorter.field;
|
||
|
||
// 获取排序方向(ascend/descend)
|
||
const sortOrder = sorter.order ? sorter.order : '';
|
||
|
||
// 更新搜索参数中的排序字段和排序方向
|
||
searchParams.value.sortField = sortField;
|
||
searchParams.value.sortOrder = sortOrder;
|
||
} else {
|
||
// 如果没有排序信息,重置为默认排序
|
||
searchParams.value.sortField = "id";
|
||
searchParams.value.sortOrder = "ascend";
|
||
}
|
||
|
||
// 同步到分页组件
|
||
pagination.value.current = pag.current;
|
||
pagination.value.pageSize = pag.pageSize;
|
||
|
||
getCourseList();
|
||
};
|
||
|
||
// 课程接口定义
|
||
interface Course {
|
||
id: number;
|
||
name: string;
|
||
type: string;
|
||
detail: string;
|
||
promoCodeDesc: string;
|
||
image: string;
|
||
originPrice: number;
|
||
discountPrice: number;
|
||
orderCount: number;
|
||
firstLevelRate: number;
|
||
secondLevelRate: number;
|
||
isShelves: boolean;
|
||
}
|
||
|
||
const tableData = ref<Course[]>([]);
|
||
|
||
// 删除课程
|
||
const deleteCourse = (id: number) => {
|
||
Modal.confirm({
|
||
title: '确认删除',
|
||
content: '确定要删除该课程吗?删除后数据将无法恢复!',
|
||
okText: '确认',
|
||
cancelText: '取消',
|
||
onOk: async () => {
|
||
try {
|
||
const storedToken = localStorage.getItem('token');
|
||
const res: any = await myAxios.post(
|
||
"/course/delete",
|
||
{ id },
|
||
{ headers: { Authorization: storedToken } }
|
||
);
|
||
|
||
if (res.code === 1) {
|
||
message.success('删除成功');
|
||
await getCourseList();
|
||
} 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: deleteBatchCourses
|
||
});
|
||
};
|
||
|
||
// 批量删除方法
|
||
const deleteBatchCourses = async () => {
|
||
try {
|
||
const storedToken = localStorage.getItem('token');
|
||
const res: any = await myAxios.post(
|
||
"/course/delBatch",
|
||
{ ids: selectedRowKeys.value },
|
||
{ headers: { Authorization: storedToken } }
|
||
);
|
||
|
||
if (res.code === 1) {
|
||
message.success(`成功删除 ${selectedRowKeys.value.length} 门课程`);
|
||
selectedRowKeys.value = []; // 清空选择
|
||
await getCourseList(); // 刷新列表
|
||
} else {
|
||
message.error(res.message || '批量删除失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('批量删除失败:', error);
|
||
message.error('批量删除操作失败');
|
||
}
|
||
};
|
||
|
||
// 重置搜索条件
|
||
const reset = () => {
|
||
searchCourseName.value = "";
|
||
searchCourseType.value = "";
|
||
searchIsShelves.value = "";
|
||
selectedRowKeys.value = [];
|
||
|
||
searchParams.value = {
|
||
current: 1,
|
||
pageSize: 10,
|
||
sortField: "id",
|
||
sortOrder: "ascend",
|
||
name: "",
|
||
type: "",
|
||
isShelves: ""
|
||
};
|
||
|
||
getCourseList();
|
||
};
|
||
|
||
// 跳转到新增课程页面
|
||
const goAddCourse = () => {
|
||
router.push('/addcourse')
|
||
}
|
||
|
||
// 查看课程详情
|
||
const showDetails = (id: string) => {
|
||
router.push({
|
||
path: '/courseDetail',
|
||
query: { id: String(id) }
|
||
})
|
||
}
|
||
|
||
// 查看章节详情
|
||
const chapterDetails = (id: string) => {
|
||
router.push({
|
||
path: '/chapterDetail',
|
||
query: { id: String(id) }
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 原有样式保持不变 */
|
||
.search-box {
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.custom-button {
|
||
background-color: #ffa940;
|
||
border-color: #ffa940;
|
||
color: #fff;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.custom-button:hover,
|
||
.custom-button:focus {
|
||
background-color: #fa8c16;
|
||
border-color: #fa8c16;
|
||
color: #fff;
|
||
}
|
||
|
||
.custom-button[disabled] {
|
||
background-color: #ffa940;
|
||
border-color: #ffa940;
|
||
opacity: 0.6;
|
||
color: #fff;
|
||
}
|
||
|
||
.custom-button.ant-btn-dangerous {
|
||
background-color: #ff4d4f;
|
||
border-color: #ff4d4f;
|
||
}
|
||
|
||
.custom-button.ant-btn-dangerous:hover,
|
||
.custom-button.ant-btn-dangerous:focus {
|
||
background-color: #ff7875;
|
||
border-color: #ff7875;
|
||
}
|
||
|
||
.custom-search :deep(.ant-input-search-button) {
|
||
background-color: #ffa940;
|
||
border-color: #ffa940;
|
||
}
|
||
|
||
.danger {
|
||
background-color: #ff4d4f !important;
|
||
border-color: #ff4d4f !important;
|
||
}
|
||
</style> |