章节模块(增删改查 批量删除)+课程订单(删查询单查询批量删)
This commit is contained in:
@ -41,6 +41,7 @@
|
||||
</span>
|
||||
</template>
|
||||
<a-menu-item key="/courseManagement">课程管理</a-menu-item>
|
||||
<a-menu-item key="/courseOrder">课程订单</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-sub-menu>
|
||||
<template #title>
|
||||
|
@ -117,6 +117,11 @@ export const routes = [
|
||||
name:'章节详情',
|
||||
component: ()=> import("../view/course/chapterDetail.vue")
|
||||
},
|
||||
{
|
||||
path:'/courseOrder',
|
||||
name:'课程订单',
|
||||
component: ()=> import("../view/course/courseOrder.vue")
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
@ -1,11 +1,907 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>章节详情</div>
|
||||
<div class="chapter-management">
|
||||
<!-- 顶部课程信息 -->
|
||||
<div class="course-header">
|
||||
<a-card :bordered="false">
|
||||
<div class="course-info">
|
||||
<a-avatar :src="courseImageUrl" shape="square" :size="80" />
|
||||
<div class="course-details">
|
||||
<h2>{{ courseName }}</h2>
|
||||
<p>课程ID: {{ courseId }} | 类别: {{ courseType }}</p>
|
||||
<p>
|
||||
创建时间: {{ courseCreateTime }} | 状态:
|
||||
<a-tag :color="courseStatus ? 'green' : 'red'">
|
||||
{{ courseStatus ? '已上架' : '已下架' }}
|
||||
</a-tag>
|
||||
</p>
|
||||
<p>
|
||||
原价: {{ courseOriginPrice }}¥ | 折扣价: {{ courseDiscountPrice }}¥ |
|
||||
佣金: {{ courseFirstLevelRate }}% / {{ courseSecondLevelRate }}%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-box">
|
||||
<a-form layout="inline">
|
||||
<a-space>
|
||||
<a-form-item label="章节名称">
|
||||
<a-input-search
|
||||
style="width: 220px"
|
||||
placeholder="请输入章节名称"
|
||||
enter-button
|
||||
@search="handleChapterSearch"
|
||||
v-model:value="searchChapterName"
|
||||
class="custom-search"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="试看权限">
|
||||
<a-select
|
||||
style="width: 140px"
|
||||
placeholder="请选择"
|
||||
v-model:value="searchPermissions"
|
||||
@change="handleChapterSearch"
|
||||
>
|
||||
<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="showAddModal">新增章节</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-button class="custom-button" @click="goBack">返回</a-button>
|
||||
</a-space>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 章节列表 -->
|
||||
<div class="chapter-list">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:scroll="{ x: 1200, y: 500 }"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-selection="rowSelection"
|
||||
bordered
|
||||
rowKey="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<!-- 章节时长格式化 -->
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'curation'">
|
||||
{{ formatDuration(record.curation) }}
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<template v-if="column.key === 'permissions'">
|
||||
<a-tag :color="getPermissionColor(record.permissions)">
|
||||
{{ record.permissions }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space :size="8">
|
||||
<a-button
|
||||
size="small"
|
||||
danger
|
||||
@click="deleteChapter(record.id)"
|
||||
>
|
||||
删除
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="showEditModal(record)"
|
||||
>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="previewVideo(record.videoView)"
|
||||
>
|
||||
预览
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
<!-- 视频预览模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="videoPreviewVisible"
|
||||
title="视频预览"
|
||||
width="800px"
|
||||
:footer="null"
|
||||
@cancel="videoPreviewVisible = false"
|
||||
>
|
||||
<div class="video-preview">
|
||||
<video :src="previewVideoUrl" controls style="width: 100%; max-height: 500px;"></video>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- 新增章节模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="addModalVisible"
|
||||
title="新增章节"
|
||||
width="600px"
|
||||
:confirm-loading="addLoading"
|
||||
@ok="handleAddChapter"
|
||||
@cancel="resetAddForm"
|
||||
>
|
||||
<a-form :model="addForm" layout="vertical">
|
||||
<a-form-item label="章节名称" required>
|
||||
<a-input
|
||||
v-model:value="addForm.name"
|
||||
placeholder="请输入章节名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="章节时长 (秒)" required>
|
||||
<a-input-number
|
||||
v-model:value="addForm.duration"
|
||||
placeholder="请输入时长(秒)"
|
||||
:min="0"
|
||||
:max="36000"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="试看权限" required>
|
||||
<a-select
|
||||
v-model:value="addForm.permissions"
|
||||
placeholder="请选择试看权限"
|
||||
>
|
||||
<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="视频文件 view 值" required>
|
||||
<a-input
|
||||
v-model:value="addForm.videoView"
|
||||
placeholder="请输入视频文件 view 值"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="所属课程ID" required>
|
||||
<a-input
|
||||
v-model:value="addForm.courseId"
|
||||
disabled
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 编辑章节模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="editModalVisible"
|
||||
title="编辑章节"
|
||||
width="600px"
|
||||
:confirm-loading="editLoading"
|
||||
@ok="handleEditChapter"
|
||||
@cancel="resetEditForm"
|
||||
>
|
||||
<a-form :model="editForm" layout="vertical">
|
||||
<a-form-item label="章节ID">
|
||||
<a-input
|
||||
v-model:value="editForm.id"
|
||||
disabled
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="章节名称" required>
|
||||
<a-input
|
||||
v-model:value="editForm.name"
|
||||
placeholder="请输入章节名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="章节时长 (秒)" required>
|
||||
<a-input-number
|
||||
v-model:value="editForm.duration"
|
||||
placeholder="请输入时长(秒)"
|
||||
:min="0"
|
||||
:max="36000"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="试看权限" required>
|
||||
<a-select
|
||||
v-model:value="editForm.permissions"
|
||||
placeholder="请选择试看权限"
|
||||
>
|
||||
<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="视频文件 view 值" required>
|
||||
<a-input
|
||||
v-model:value="editForm.videoView"
|
||||
placeholder="请输入视频文件 view 值"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="所属课程ID" required>
|
||||
<a-input
|
||||
v-model:value="editForm.courseId"
|
||||
disabled
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import myAxios from "../../api/myAxios.ts";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { downLoadImage } from "../../api/ImageUrl.ts";
|
||||
import router from "../../router";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const route = useRoute();
|
||||
const loading = ref(false);
|
||||
const selectedRowKeys = ref<number[]>([]);
|
||||
const courseLoading = ref(false); // 课程信息加载状态
|
||||
|
||||
// 课程信息
|
||||
const courseId = ref("");
|
||||
const courseName = ref("加载中...");
|
||||
const courseType = ref("加载中...");
|
||||
const courseImageUrl = ref("");
|
||||
const courseStatus = ref(false);
|
||||
const courseCreateTime = ref("加载中...");
|
||||
const courseOriginPrice = ref(0);
|
||||
const courseDiscountPrice = ref(0);
|
||||
const courseFirstLevelRate = ref(0);
|
||||
const courseSecondLevelRate = ref(0);
|
||||
|
||||
// 搜索参数
|
||||
const searchChapterName = ref("");
|
||||
const searchPermissions = ref("");
|
||||
|
||||
// 分页查询参数
|
||||
const searchParams = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
sortField: "id",
|
||||
sortOrder: "ascend",
|
||||
name: "",
|
||||
permissions: "",
|
||||
courseId: ""
|
||||
});
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '章节ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
sortDirections: ['ascend', 'descend']
|
||||
},
|
||||
{
|
||||
title: '章节名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 200,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '章节时长',
|
||||
dataIndex: 'duration',
|
||||
key: 'duration',
|
||||
width: 120,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '试看权限',
|
||||
dataIndex: 'permissions',
|
||||
key: 'permissions',
|
||||
width: 120,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '视频文件',
|
||||
dataIndex: 'videoView',
|
||||
key: 'videoView',
|
||||
width: 180,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
align: 'center',
|
||||
fixed: 'right'
|
||||
}
|
||||
];
|
||||
|
||||
// 视频预览相关
|
||||
const videoPreviewVisible = ref(false);
|
||||
const previewVideoUrl = ref("");
|
||||
|
||||
// 新增章节相关
|
||||
const addModalVisible = ref(false);
|
||||
const addLoading = ref(false);
|
||||
const addForm = ref({
|
||||
name: "",
|
||||
duration: 0,
|
||||
permissions: "全集试看",
|
||||
videoView: "",
|
||||
courseId: ""
|
||||
});
|
||||
|
||||
// 编辑章节相关
|
||||
const editModalVisible = ref(false);
|
||||
const editLoading = ref(false);
|
||||
const editForm = ref({
|
||||
id: 0,
|
||||
name: "",
|
||||
duration: 0,
|
||||
permissions: "全集试看",
|
||||
videoView: "",
|
||||
courseId: ""
|
||||
});
|
||||
|
||||
// 行选择配置
|
||||
const rowSelection = ref({
|
||||
selectedRowKeys: selectedRowKeys,
|
||||
onChange: (selectedKeys: number[]) => {
|
||||
selectedRowKeys.value = selectedKeys;
|
||||
}
|
||||
});
|
||||
|
||||
// 获取课程详细信息
|
||||
const getCourseDetail = async () => {
|
||||
courseLoading.value = true;
|
||||
try {
|
||||
const storedToken = localStorage.getItem('token');
|
||||
if (!storedToken) throw new Error('未找到登录信息');
|
||||
|
||||
const res: any = await myAxios.post(
|
||||
"/course/queryById",
|
||||
{ id: courseId.value },
|
||||
{ headers: { Authorization: storedToken } }
|
||||
);
|
||||
console.log(res)
|
||||
if (res.code === 1 && res.data) {
|
||||
const courseData = res.data;
|
||||
courseName.value = courseData.name || "未命名课程";
|
||||
courseType.value = courseData.type || "未知类别";
|
||||
courseImageUrl.value = courseData.image ? downLoadImage + courseData.image : "";
|
||||
courseStatus.value = courseData.isShelves === true;
|
||||
courseOriginPrice.value = courseData.originPrice || 0;
|
||||
courseDiscountPrice.value = courseData.discountPrice || 0;
|
||||
courseFirstLevelRate.value = courseData.firstLevelRate || 0;
|
||||
courseSecondLevelRate.value = courseData.secondLevelRate || 0;
|
||||
|
||||
// 如果没有创建时间,使用当前时间
|
||||
courseCreateTime.value = courseData.createTime
|
||||
? dayjs(courseData.createTime).format("YYYY-MM-DD HH:mm:ss")
|
||||
: dayjs().format("YYYY-MM-DD HH:mm:ss");
|
||||
} else {
|
||||
message.error(res.message || '获取课程信息失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取课程信息失败:", error);
|
||||
message.error('获取课程信息失败');
|
||||
} finally {
|
||||
courseLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 章节搜索方法
|
||||
const handleChapterSearch = async () => {
|
||||
searchParams.value.name = searchChapterName.value;
|
||||
searchParams.value.permissions = searchPermissions.value;
|
||||
searchParams.value.current = 1;
|
||||
await getChapterList();
|
||||
};
|
||||
|
||||
// 获取章节列表
|
||||
const getChapterList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const storedToken = localStorage.getItem('token');
|
||||
if (!storedToken) throw new Error('未找到登录信息');
|
||||
|
||||
const res: any = await myAxios.post("/courseChapter/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;
|
||||
}
|
||||
};
|
||||
|
||||
// 分页配置
|
||||
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, sorter: any) => {
|
||||
searchParams.value.current = pag.current;
|
||||
searchParams.value.pageSize = pag.pageSize;
|
||||
|
||||
// 处理排序
|
||||
if (sorter.field) {
|
||||
searchParams.value.sortField = sorter.field;
|
||||
searchParams.value.sortOrder = sorter.order;
|
||||
}
|
||||
|
||||
// 同步到分页组件
|
||||
pagination.value.current = pag.current;
|
||||
pagination.value.pageSize = pag.pageSize;
|
||||
|
||||
getChapterList();
|
||||
};
|
||||
|
||||
// 章节接口定义
|
||||
interface Chapter {
|
||||
id: number;
|
||||
name: string;
|
||||
duration: number;
|
||||
permissions: string;
|
||||
videoView: string;
|
||||
courseId: string;
|
||||
}
|
||||
|
||||
const tableData = ref<Chapter[]>([]);
|
||||
|
||||
// 格式化时长(秒转分钟)
|
||||
const formatDuration = (seconds: number) => {
|
||||
if (!seconds) return "0分钟";
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
return `${minutes}分钟`;
|
||||
};
|
||||
|
||||
// 根据权限获取标签颜色
|
||||
const getPermissionColor = (permissions: string) => {
|
||||
switch (permissions) {
|
||||
case "全集试看": return "blue";
|
||||
case "部分试看": return "cyan";
|
||||
case "开启": return "green";
|
||||
case "关闭": return "red";
|
||||
default: return "gray";
|
||||
}
|
||||
};
|
||||
|
||||
// 删除章节
|
||||
const deleteChapter = (id: number) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除该章节吗?删除后数据将无法恢复!',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
const storedToken = localStorage.getItem('token');
|
||||
const res: any = await myAxios.post(
|
||||
"/courseChapter/delete",
|
||||
{ id },
|
||||
{ headers: { Authorization: storedToken } }
|
||||
);
|
||||
|
||||
if (res.code === 1) {
|
||||
message.success('删除成功');
|
||||
await getChapterList();
|
||||
} 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: deleteBatchChapters
|
||||
});
|
||||
};
|
||||
|
||||
// 批量删除方法
|
||||
const deleteBatchChapters = async () => {
|
||||
try {
|
||||
const storedToken = localStorage.getItem('token');
|
||||
const res: any = await myAxios.post(
|
||||
"/courseChapter/delBatch",
|
||||
{ ids: selectedRowKeys.value },
|
||||
{ headers: { Authorization: storedToken } }
|
||||
);
|
||||
|
||||
if (res.code === 1) {
|
||||
message.success(`成功删除 ${selectedRowKeys.value.length} 个章节`);
|
||||
selectedRowKeys.value = [];
|
||||
await getChapterList();
|
||||
} else {
|
||||
message.error(res.message || '批量删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量删除失败:', error);
|
||||
message.error('批量删除操作失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 重置搜索条件
|
||||
const reset = () => {
|
||||
searchChapterName.value = "";
|
||||
searchPermissions.value = "";
|
||||
selectedRowKeys.value = [];
|
||||
|
||||
searchParams.value = {
|
||||
...searchParams.value,
|
||||
current: 1,
|
||||
name: "",
|
||||
permissions: ""
|
||||
};
|
||||
|
||||
getChapterList();
|
||||
};
|
||||
|
||||
// 显示新增章节弹窗
|
||||
const showAddModal = () => {
|
||||
addForm.value = {
|
||||
name: "",
|
||||
duration: 600, // 默认10分钟
|
||||
permissions: "全集试看",
|
||||
videoView: "",
|
||||
courseId: courseId.value
|
||||
};
|
||||
addModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 重置新增表单
|
||||
const resetAddForm = () => {
|
||||
addForm.value = {
|
||||
name: "",
|
||||
duration: 600,
|
||||
permissions: "全集试看",
|
||||
videoView: "",
|
||||
courseId: courseId.value
|
||||
};
|
||||
addModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 处理新增章节
|
||||
const handleAddChapter = async () => {
|
||||
if (!addForm.value.name) {
|
||||
message.error("请填写章节名称");
|
||||
return;
|
||||
}
|
||||
if (!addForm.value.duration || addForm.value.duration <= 0) {
|
||||
message.error("请填写有效的章节时长");
|
||||
return;
|
||||
}
|
||||
if (!addForm.value.permissions) {
|
||||
message.error("请选择试看权限");
|
||||
return;
|
||||
}
|
||||
if (!addForm.value.videoView) {
|
||||
message.error("请填写视频文件 view 值");
|
||||
return;
|
||||
}
|
||||
|
||||
addLoading.value = true;
|
||||
|
||||
try {
|
||||
const storedToken = localStorage.getItem('token');
|
||||
if (!storedToken) throw new Error('未找到登录信息');
|
||||
|
||||
const res: any = await myAxios.post(
|
||||
"/courseChapter/add",
|
||||
addForm.value,
|
||||
{ headers: { Authorization: storedToken } }
|
||||
);
|
||||
|
||||
if (res.code === 1) {
|
||||
message.success("章节添加成功");
|
||||
addModalVisible.value = false;
|
||||
await getChapterList();
|
||||
} else {
|
||||
message.error(res.message || '添加章节失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("添加章节失败:", error);
|
||||
message.error('添加章节失败');
|
||||
} finally {
|
||||
addLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 显示编辑章节弹窗
|
||||
const showEditModal = (chapter: Chapter) => {
|
||||
editForm.value = {
|
||||
id: chapter.id,
|
||||
name: chapter.name,
|
||||
duration: chapter.duration, // 注意:后端接口使用duration字段
|
||||
permissions: chapter.permissions,
|
||||
videoView: chapter.videoView,
|
||||
courseId: chapter.courseId
|
||||
};
|
||||
editModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 重置编辑表单
|
||||
const resetEditForm = () => {
|
||||
editForm.value = {
|
||||
id: 0,
|
||||
name: "",
|
||||
duration: 0,
|
||||
permissions: "全集试看",
|
||||
videoView: "",
|
||||
courseId: ""
|
||||
};
|
||||
editModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 处理编辑章节
|
||||
const handleEditChapter = async () => {
|
||||
if (!editForm.value.name) {
|
||||
message.error("请填写章节名称");
|
||||
return;
|
||||
}
|
||||
if (!editForm.value.duration || editForm.value.duration <= 0) {
|
||||
message.error("请填写有效的章节时长");
|
||||
return;
|
||||
}
|
||||
if (!editForm.value.permissions) {
|
||||
message.error("请选择试看权限");
|
||||
return;
|
||||
}
|
||||
if (!editForm.value.videoView) {
|
||||
message.error("请填写视频文件 view 值");
|
||||
return;
|
||||
}
|
||||
|
||||
editLoading.value = true;
|
||||
|
||||
try {
|
||||
const storedToken = localStorage.getItem('token');
|
||||
if (!storedToken) throw new Error('未找到登录信息');
|
||||
|
||||
// 准备更新数据,修正字段映射问题
|
||||
const updateData = {
|
||||
id: editForm.value.id,
|
||||
name: editForm.value.name,
|
||||
duration: editForm.value.duration,
|
||||
permissions: editForm.value.permissions,
|
||||
videoView: editForm.value.videoView,
|
||||
coursefd: editForm.value.courseId
|
||||
};
|
||||
|
||||
console.log("提交的更新数据:", updateData); // 添加调试日志
|
||||
|
||||
const res: any = await myAxios.post(
|
||||
"/courseChapter/update",
|
||||
updateData,
|
||||
{ headers: { Authorization: storedToken } }
|
||||
);
|
||||
|
||||
if (res.code === 1) {
|
||||
message.success("章节更新成功");
|
||||
editModalVisible.value = false;
|
||||
await getChapterList();
|
||||
} else {
|
||||
message.error(res.message || '更新章节失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("更新章节失败:", error);
|
||||
message.error('更新章节失败');
|
||||
} finally {
|
||||
editLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 预览视频
|
||||
const previewVideo = (videoView: string) => {
|
||||
previewVideoUrl.value = downLoadImage + videoView;
|
||||
videoPreviewVisible.value = true;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 从路由参数获取课程ID
|
||||
courseId.value = route.query.id ? String(route.query.id) : "";
|
||||
|
||||
if (courseId.value) {
|
||||
// 设置章节查询参数中的课程ID
|
||||
searchParams.value.courseId = courseId.value;
|
||||
|
||||
// 获取课程详细信息
|
||||
getCourseDetail();
|
||||
|
||||
// 获取章节列表
|
||||
getChapterList();
|
||||
} else {
|
||||
message.error("未获取到课程ID");
|
||||
router.back();
|
||||
}
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
router.push('/courseManagement')
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chapter-management {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.course-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.course-header :deep(.ant-card) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e4efe9 100%);
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.course-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.course-info .ant-avatar {
|
||||
margin-right: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.course-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.course-details h2 {
|
||||
margin-bottom: 8px;
|
||||
font-size: 22px;
|
||||
color: #1d1d1d;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.course-details p {
|
||||
margin-bottom: 6px;
|
||||
color: #444;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||
}
|
||||
|
||||
.chapter-list {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: #ff4d4f !important;
|
||||
border-color: #ff4d4f !important;
|
||||
}
|
||||
|
||||
.custom-button {
|
||||
background-color: #1890ff;
|
||||
border-color: #1890ff;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.custom-button:hover {
|
||||
background-color: #40a9ff;
|
||||
border-color: #40a9ff;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
|
||||
.video-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #000;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.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;
|
||||
}
|
||||
</style>
|
@ -5,7 +5,7 @@
|
||||
<a-space>
|
||||
<a-form-item label="课程名称">
|
||||
<a-input-search
|
||||
style="width: 300px"
|
||||
style="width: 200px"
|
||||
placeholder="请输入课程名称"
|
||||
enter-button
|
||||
@search="handleCourseSearch"
|
||||
@ -13,6 +13,34 @@
|
||||
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
|
||||
@ -30,7 +58,7 @@
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:scroll="{ x: 1500, y: 550 }"
|
||||
:scroll="{ x: 1600, y: 550 }"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:row-selection="rowSelection"
|
||||
@ -43,22 +71,33 @@
|
||||
<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">
|
||||
@ -97,18 +136,22 @@ import {downLoadImage} from "../../api/ImageUrl.ts";
|
||||
import router from "../../router";
|
||||
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const selectedRowKeys = ref<number[]>([]); // 存储选中的行ID
|
||||
|
||||
const searchCourseName = ref(""); // 改为项目名称搜索参数
|
||||
// 搜索参数
|
||||
const searchCourseName = ref("");
|
||||
const searchCourseType = ref("");
|
||||
const searchIsShelves = ref("");
|
||||
|
||||
// 分页查询参数
|
||||
const searchParams = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
sortField: "id",
|
||||
sortOrder: "ascend",
|
||||
userRole: null,
|
||||
name: "",
|
||||
type:""
|
||||
type: "",
|
||||
isShelves: ""
|
||||
});
|
||||
|
||||
// 行选择配置
|
||||
@ -119,9 +162,8 @@ const rowSelection = ref({
|
||||
}
|
||||
});
|
||||
|
||||
//用户表
|
||||
// 课程表格列定义
|
||||
const columns = [
|
||||
|
||||
{
|
||||
title: '课程ID',
|
||||
dataIndex: 'id',
|
||||
@ -129,10 +171,9 @@ const columns = [
|
||||
key: 'id',
|
||||
fixed: 'left',
|
||||
align: 'center',
|
||||
sorter: true, // 添加排序功能
|
||||
sortDirections: ['ascend', 'descend'] // 允许升序降序
|
||||
sorter: true,
|
||||
sortDirections: ['ascend', 'descend']
|
||||
},
|
||||
|
||||
{
|
||||
title: '课程图片',
|
||||
dataIndex: 'image',
|
||||
@ -162,8 +203,8 @@ const columns = [
|
||||
width: 55,
|
||||
key: 'originPrice',
|
||||
align: 'center',
|
||||
sorter: true, // 添加排序功能
|
||||
sortDirections: ['ascend', 'descend'] // 允许升序降序
|
||||
sorter: true,
|
||||
sortDirections: ['ascend', 'descend']
|
||||
},
|
||||
{
|
||||
title: '折扣价格',
|
||||
@ -171,8 +212,8 @@ const columns = [
|
||||
key: 'discountPrice',
|
||||
width: 55,
|
||||
align: 'center',
|
||||
sorter: true, // 添加排序功能
|
||||
sortDirections: ['ascend', 'descend'] // 允许升序降序
|
||||
sorter: true,
|
||||
sortDirections: ['ascend', 'descend']
|
||||
},
|
||||
{
|
||||
title: '已下单人数',
|
||||
@ -180,8 +221,8 @@ const columns = [
|
||||
key: 'orderCount',
|
||||
width: 55,
|
||||
align: 'center',
|
||||
sorter: true, // 添加排序功能
|
||||
sortDirections: ['ascend', 'descend'] // 允许升序降序
|
||||
sorter: true,
|
||||
sortDirections: ['ascend', 'descend']
|
||||
},
|
||||
{
|
||||
title: '一级佣金比例',
|
||||
@ -189,8 +230,8 @@ const columns = [
|
||||
key: 'firstLevelRate',
|
||||
width: 65,
|
||||
align: 'center',
|
||||
sorter: true, // 添加排序功能
|
||||
sortDirections: ['ascend', 'descend'] // 允许升序降序
|
||||
sorter: true,
|
||||
sortDirections: ['ascend', 'descend']
|
||||
},
|
||||
{
|
||||
title: '二级佣金比例',
|
||||
@ -198,8 +239,15 @@ const columns = [
|
||||
key: 'secondLevelRate',
|
||||
width: 65,
|
||||
align: 'center',
|
||||
sorter: true, // 添加排序功能
|
||||
sortDirections: ['ascend', 'descend'] // 允许升序降序
|
||||
sorter: true,
|
||||
sortDirections: ['ascend', 'descend']
|
||||
},
|
||||
{
|
||||
title: '是否上架',
|
||||
dataIndex: 'isShelves',
|
||||
key: 'isShelves',
|
||||
width: 65,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@ -210,42 +258,30 @@ const columns = [
|
||||
}
|
||||
];
|
||||
|
||||
interface ProjectRecord {
|
||||
// 这里根据实际数据结构定义属性
|
||||
superHostList?: string[];
|
||||
// 其他属性...
|
||||
}
|
||||
// 项目名称搜索方法
|
||||
// 课程名称搜索方法
|
||||
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,
|
||||
projectName: searchParams.value.projectName
|
||||
},
|
||||
const res: any = await myAxios.post("/course/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.map((item: ProjectRecord) => ({
|
||||
...item,
|
||||
superUserList: item.superHostList? item.superHostList.join(', ') : '无'
|
||||
}));
|
||||
tableData.value = res.data.records;
|
||||
// 同步总条数到分页组件
|
||||
total.value = res.data.total;
|
||||
pagination.value.total = res.data.total; // 新增此行
|
||||
pagination.value.current = searchParams.value.current; // 同步当前页
|
||||
pagination.value.total = res.data.total;
|
||||
} else {
|
||||
message.error(res.message || '请求失败');
|
||||
}
|
||||
@ -258,7 +294,6 @@ const getCourseList = async () => {
|
||||
};
|
||||
|
||||
onMounted(getCourseList);
|
||||
//分页
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
@ -270,49 +305,55 @@ const pagination = ref({
|
||||
showTotal: (total: number) => `共 ${total} 条`,
|
||||
pageSizeOptions: ['10', '20', '50', '100']
|
||||
});
|
||||
const handleTableChange = (pag: any, _: any, sorter: any) => {
|
||||
// 处理排序参数
|
||||
let sortField = "id"; // 默认排序字段
|
||||
let sortOrder = "ascend"; // 默认排序方式
|
||||
if (sorter.field) {
|
||||
sortField = sorter.field;
|
||||
sortOrder = sorter.order;
|
||||
|
||||
// 处理分页和排序变化
|
||||
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";
|
||||
}
|
||||
searchParams.value = {
|
||||
...searchParams.value,
|
||||
current: pag.current,
|
||||
pageSize: pag.pageSize,
|
||||
sortField: sortField, // 设置排序字段
|
||||
sortOrder: sortOrder
|
||||
};
|
||||
|
||||
// 同步到分页组件
|
||||
pagination.value = {
|
||||
...pagination.value,
|
||||
current: pag.current,
|
||||
pageSize: pag.pageSize
|
||||
};
|
||||
pagination.value.current = pag.current;
|
||||
pagination.value.pageSize = pag.pageSize;
|
||||
|
||||
getCourseList();
|
||||
};
|
||||
|
||||
// ID查询方法
|
||||
interface Project {
|
||||
// 课程接口定义
|
||||
interface Course {
|
||||
id: number;
|
||||
projectName: string;
|
||||
projectImage: string;
|
||||
projectSettlementCycle: number;
|
||||
maxPromoterCount: number;
|
||||
projectStatus: string;
|
||||
projectDescription: string;
|
||||
settlementDesc: string;
|
||||
// 其他可能存在的属性根据实际情况补充
|
||||
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<Project[]>([]);
|
||||
const tableData = ref<Course[]>([]);
|
||||
|
||||
|
||||
// 删除项目 - 添加确认弹窗
|
||||
// 删除课程
|
||||
const deleteCourse = (id: number) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
@ -325,12 +366,7 @@ const deleteCourse = (id: number) => {
|
||||
const res: any = await myAxios.post(
|
||||
"/course/delete",
|
||||
{ id },
|
||||
{
|
||||
headers: {
|
||||
Authorization: storedToken,
|
||||
'AfterScript': 'required-script'
|
||||
}
|
||||
}
|
||||
{ headers: { Authorization: storedToken } }
|
||||
);
|
||||
|
||||
if (res.code === 1) {
|
||||
@ -343,10 +379,7 @@ const deleteCourse = (id: number) => {
|
||||
console.error('删除失败:', error);
|
||||
message.error('删除操作失败');
|
||||
}
|
||||
},
|
||||
onCancel() {
|
||||
// 用户点击取消,不做操作
|
||||
},
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -362,12 +395,7 @@ const showDeleteConfirm = () => {
|
||||
content: `确定要删除选中的 ${selectedRowKeys.value.length} 门课程吗?删除后数据将无法恢复!`,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
await deleteBatchCourses();
|
||||
},
|
||||
onCancel() {
|
||||
// 用户点击取消,不做操作
|
||||
},
|
||||
onOk: deleteBatchCourses
|
||||
});
|
||||
};
|
||||
|
||||
@ -378,12 +406,7 @@ const deleteBatchCourses = async () => {
|
||||
const res: any = await myAxios.post(
|
||||
"/course/delBatch",
|
||||
{ ids: selectedRowKeys.value },
|
||||
{
|
||||
headers: {
|
||||
Authorization: storedToken,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
{ headers: { Authorization: storedToken } }
|
||||
);
|
||||
|
||||
if (res.code === 1) {
|
||||
@ -399,97 +422,65 @@ const deleteBatchCourses = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 重置按钮
|
||||
// 重置搜索条件
|
||||
const reset = () => {
|
||||
searchCourseName.value = "";
|
||||
searchCourseType.value = "";
|
||||
searchIsShelves.value = "";
|
||||
selectedRowKeys.value = [];
|
||||
|
||||
searchParams.value = {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
sortField: "id",
|
||||
sortOrder: "ascend",
|
||||
userRole: null,
|
||||
name: "",
|
||||
type:""
|
||||
type: "",
|
||||
isShelves: ""
|
||||
};
|
||||
|
||||
getCourseList();
|
||||
};
|
||||
|
||||
//去新增项目
|
||||
// 跳转到新增课程页面
|
||||
const goAddCourse = () => {
|
||||
router.push('/addcourse')
|
||||
}
|
||||
|
||||
// 查看课程详情
|
||||
const showDetails = (id: string) => {
|
||||
router.push({
|
||||
path: '/courseDetail',
|
||||
query:{
|
||||
id:String(id)
|
||||
}
|
||||
query: { id: String(id) }
|
||||
})
|
||||
}
|
||||
|
||||
// 查看章节详情
|
||||
const chapterDetails = (id: string) => {
|
||||
router.push({
|
||||
path: '/chapterDetail',
|
||||
query:{
|
||||
id:String(id)
|
||||
}
|
||||
query: { id: String(id) }
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 分割线样式 */
|
||||
:deep(.ant-divider-vertical) {
|
||||
border-color: rgba(0, 0, 0, 0.15);
|
||||
height: 1.2em;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item-label) {
|
||||
font-weight: 600;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.info-card :deep(.ant-card-head) {
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
/* 角色颜色映射 */
|
||||
:root {
|
||||
--role-user: #87d068;
|
||||
--role-admin: #f50;
|
||||
--role-boss: #722ed1;
|
||||
}
|
||||
|
||||
/* 原有样式保持不变 */
|
||||
.search-box {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 批量删除按钮样式 */
|
||||
.danger {
|
||||
background-color: #ff4d4f !important;
|
||||
border-color: #ff4d4f !important;
|
||||
}
|
||||
|
||||
.danger:hover,
|
||||
.danger:focus {
|
||||
background-color: #ff7875 !important;
|
||||
border-color: #ff7875 !important;
|
||||
}
|
||||
|
||||
/*橙色按钮*/
|
||||
.custom-button {
|
||||
background-color: #ffa940;
|
||||
border-color: #ffa940;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.custom-button:hover,
|
||||
.custom-button:focus {
|
||||
background-color: #ffa940;
|
||||
border-color: #ffa940;
|
||||
background-color: #fa8c16;
|
||||
border-color: #fa8c16;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@ -500,7 +491,6 @@ const chapterDetails=(id:string)=>{
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 危险按钮样式 */
|
||||
.custom-button.ant-btn-dangerous {
|
||||
background-color: #ff4d4f;
|
||||
border-color: #ff4d4f;
|
||||
@ -512,24 +502,13 @@ const chapterDetails=(id:string)=>{
|
||||
border-color: #ff7875;
|
||||
}
|
||||
|
||||
/* 保持原有的其他样式不变 */
|
||||
.search-box {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.custom-search :deep(.ant-input-search-button) {
|
||||
background-color: #ffa940;
|
||||
border-color: #ffa940;
|
||||
}
|
||||
|
||||
.custom-search :deep(.ant-input-search-button:hover),
|
||||
.custom-search :deep(.ant-input-search-button:focus) {
|
||||
background-color: #fa8c16;
|
||||
border-color: #fa8c16;
|
||||
}
|
||||
|
||||
/* 保持输入框原有样式不变 */
|
||||
.custom-search :deep(.ant-input) {
|
||||
border-right-color: #ffa940;
|
||||
.danger {
|
||||
background-color: #ff4d4f !important;
|
||||
border-color: #ff4d4f !important;
|
||||
}
|
||||
</style>
|
461
src/view/course/courseOrder.vue
Normal file
461
src/view/course/courseOrder.vue
Normal file
@ -0,0 +1,461 @@
|
||||
<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>
|
||||
</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 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'
|
||||
},
|
||||
{
|
||||
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',
|
||||
width: 95,
|
||||
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) {
|
||||
// 获取排序字段(使用列的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;
|
||||
|
||||
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>
|
Reference in New Issue
Block a user