Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1d7079e779 |
@ -4,4 +4,3 @@ type: "manual"
|
||||
|
||||
1、在接下来的每一个步骤当中,请帮我实现对页面的响应式设计
|
||||
2、必须严格执行我给你的指令,一步一步执行,不得有缩减
|
||||
3、我们用的是naive UI组件 ,TS,vue3
|
||||
|
2
.env
@ -1,5 +1,5 @@
|
||||
# API配置
|
||||
VITE_API_BASE_URL=http://103.40.14.23:25526/jeecgboot
|
||||
VITE_API_BASE_URL=http://110.42.96.65:55510/api
|
||||
|
||||
# Mock配置 - 禁用Mock,使用真实API
|
||||
VITE_ENABLE_MOCK=false
|
||||
|
@ -1,7 +1,7 @@
|
||||
# 开发环境配置
|
||||
|
||||
# API配置
|
||||
VITE_API_BASE_URL=http://103.40.14.23:25526/jeecgboot
|
||||
VITE_API_BASE_URL=http://110.42.96.65:55510/api
|
||||
|
||||
# Mock配置
|
||||
# 设置为 true 使用Mock数据,false 使用真实API
|
||||
|
@ -1,7 +1,7 @@
|
||||
# 生产环境配置
|
||||
|
||||
# API配置
|
||||
VITE_API_BASE_URL=http://103.40.14.23:25526/jeecgboot
|
||||
VITE_API_BASE_URL=http://110.42.96.65:55510/api
|
||||
|
||||
# Mock配置 - 生产环境禁用Mock,使用真实API
|
||||
VITE_ENABLE_MOCK=false
|
||||
|
@ -6,6 +6,6 @@ services:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "25527:80"
|
||||
- "55514:80"
|
||||
container_name: vue3-nginx
|
||||
restart: unless-stopped
|
||||
|
@ -1,320 +0,0 @@
|
||||
# DPlayer 集成指南
|
||||
|
||||
## 什么是 DPlayer?
|
||||
|
||||
**DPlayer** 是由 [DIYGod](https://github.com/DIYGod) 开发的一个开源的 HTML5 视频播放器,具有以下特点:
|
||||
|
||||
- 🎨 **界面美观**:现代化的设计风格
|
||||
- 🎯 **轻量级**:体积小,加载快
|
||||
- 🌏 **中文友好**:由中国开发者开发,中文文档完善
|
||||
- 🎮 **功能丰富**:支持弹幕、快捷键、倍速播放等
|
||||
- 📱 **移动端适配**:响应式设计,支持移动设备
|
||||
|
||||
## 主要功能特性
|
||||
|
||||
### 基础功能
|
||||
- ✅ 播放/暂停控制
|
||||
- ✅ 音量控制
|
||||
- ✅ 进度条拖拽
|
||||
- ✅ 全屏切换
|
||||
- ✅ 倍速播放 (0.5x - 2x)
|
||||
|
||||
### 高级功能
|
||||
- 🎯 键盘快捷键支持
|
||||
- 🎨 自定义主题色
|
||||
- 📝 右键菜单自定义
|
||||
- 🎵 音频可视化
|
||||
- 📱 移动端手势支持
|
||||
|
||||
### 格式支持
|
||||
- MP4
|
||||
- WebM
|
||||
- Ogg
|
||||
- HLS (.m3u8)
|
||||
- FLV
|
||||
- 更多格式通过插件支持
|
||||
|
||||
## 安装和集成
|
||||
|
||||
### 方法1:CDN 引入(推荐用于快速测试)
|
||||
|
||||
```html
|
||||
<!-- 在 index.html 中引入 -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.js"></script>
|
||||
```
|
||||
|
||||
### 方法2:NPM 安装(推荐用于生产环境)
|
||||
|
||||
```bash
|
||||
npm install dplayer
|
||||
```
|
||||
|
||||
然后在组件中导入:
|
||||
|
||||
```javascript
|
||||
import DPlayer from 'dplayer'
|
||||
import 'dplayer/dist/DPlayer.min.css'
|
||||
```
|
||||
|
||||
## 基础使用
|
||||
|
||||
### 创建播放器
|
||||
|
||||
```javascript
|
||||
const player = new DPlayer({
|
||||
container: document.getElementById('dplayer'),
|
||||
video: {
|
||||
url: 'video.mp4',
|
||||
type: 'auto'
|
||||
},
|
||||
autoplay: false,
|
||||
theme: '#007bff',
|
||||
lang: 'zh-cn'
|
||||
})
|
||||
```
|
||||
|
||||
### 事件监听
|
||||
|
||||
```javascript
|
||||
player.on('play', () => {
|
||||
console.log('视频开始播放')
|
||||
})
|
||||
|
||||
player.on('pause', () => {
|
||||
console.log('视频暂停')
|
||||
})
|
||||
|
||||
player.on('ended', () => {
|
||||
console.log('视频播放结束')
|
||||
})
|
||||
|
||||
player.on('error', () => {
|
||||
console.log('播放出错')
|
||||
})
|
||||
```
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 基础配置
|
||||
|
||||
```javascript
|
||||
const options = {
|
||||
container: document.getElementById('dplayer'), // 容器元素
|
||||
video: {
|
||||
url: 'video.mp4', // 视频地址
|
||||
type: 'auto', // 视频类型:auto, normal, hls, flv
|
||||
defaultQuality: 0, // 默认画质
|
||||
pic: 'poster.jpg', // 封面图
|
||||
thumbnails: 'thumbnails.jpg' // 缩略图
|
||||
},
|
||||
autoplay: false, // 自动播放
|
||||
theme: '#007bff', // 主题色
|
||||
lang: 'zh-cn', // 语言:zh-cn, en
|
||||
hotkey: true, // 启用快捷键
|
||||
preload: 'auto', // 预加载:auto, metadata, none
|
||||
volume: 0.8, // 默认音量
|
||||
playbackSpeed: [0.5, 0.75, 1, 1.25, 1.5, 2], // 倍速选项
|
||||
contextmenu: [ // 右键菜单
|
||||
{
|
||||
text: '关于 DPlayer',
|
||||
link: 'https://github.com/DIYGod/DPlayer'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 高级配置
|
||||
|
||||
```javascript
|
||||
const advancedOptions = {
|
||||
// 弹幕配置
|
||||
danmaku: {
|
||||
id: 'dplayer-danmaku',
|
||||
api: 'https://api.prprpr.me/dplayer/',
|
||||
token: 'token',
|
||||
maximum: 1000,
|
||||
addition: ['https://api.prprpr.me/dplayer/bilibili?aid=4157142'],
|
||||
user: 'DIYGod',
|
||||
bottom: '15%',
|
||||
unlimited: true
|
||||
},
|
||||
|
||||
// 字幕配置
|
||||
subtitle: {
|
||||
url: 'subtitle.vtt',
|
||||
type: 'webvtt',
|
||||
fontSize: '20px',
|
||||
bottom: '10%',
|
||||
color: '#fff'
|
||||
},
|
||||
|
||||
// 画质切换
|
||||
video: {
|
||||
url: [
|
||||
{
|
||||
name: '1080P',
|
||||
url: 'video-1080p.mp4'
|
||||
},
|
||||
{
|
||||
name: '720P',
|
||||
url: 'video-720p.mp4'
|
||||
}
|
||||
],
|
||||
defaultQuality: 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 在 Vue 项目中使用
|
||||
|
||||
### 创建 DPlayer 组件
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="video-player-wrapper">
|
||||
<div ref="dplayerContainer" class="dplayer-container"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const dplayerContainer = ref()
|
||||
let player = null
|
||||
|
||||
onMounted(() => {
|
||||
// 确保 DPlayer 已加载
|
||||
if (window.DPlayer) {
|
||||
initPlayer()
|
||||
} else {
|
||||
loadDPlayer().then(() => {
|
||||
initPlayer()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const loadDPlayer = () => {
|
||||
return new Promise((resolve) => {
|
||||
const cssLink = document.createElement('link')
|
||||
cssLink.rel = 'stylesheet'
|
||||
cssLink.href = 'https://cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.css'
|
||||
document.head.appendChild(cssLink)
|
||||
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.js'
|
||||
script.onload = resolve
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
}
|
||||
|
||||
const initPlayer = () => {
|
||||
player = new window.DPlayer({
|
||||
container: dplayerContainer.value,
|
||||
video: {
|
||||
url: '/video/first.mp4',
|
||||
type: 'auto'
|
||||
},
|
||||
autoplay: false,
|
||||
theme: '#007bff',
|
||||
lang: 'zh-cn',
|
||||
hotkey: true
|
||||
})
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (player) {
|
||||
player.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.video-player-wrapper {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.dplayer-container {
|
||||
width: 100%;
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## 样式定制
|
||||
|
||||
### 自定义主题色
|
||||
|
||||
```css
|
||||
/* 修改播放器主题色 */
|
||||
.dplayer {
|
||||
--dplayer-theme: #007bff;
|
||||
}
|
||||
|
||||
/* 自定义进度条颜色 */
|
||||
.dplayer .dplayer-bar-wrap .dplayer-bar .dplayer-played {
|
||||
background: #007bff;
|
||||
}
|
||||
|
||||
/* 自定义控制按钮颜色 */
|
||||
.dplayer .dplayer-icons .dplayer-icon {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dplayer .dplayer-icons .dplayer-icon:hover {
|
||||
color: #007bff;
|
||||
}
|
||||
```
|
||||
|
||||
### 响应式设计
|
||||
|
||||
```css
|
||||
/* 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.dplayer {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dplayer .dplayer-icons .dplayer-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 快捷键支持
|
||||
|
||||
DPlayer 默认支持以下快捷键:
|
||||
|
||||
- `空格键` - 播放/暂停
|
||||
- `←` - 后退 10 秒
|
||||
- `→` - 前进 10 秒
|
||||
- `↑` - 音量 +10%
|
||||
- `↓` - 音量 -10%
|
||||
- `F` - 全屏切换
|
||||
- `M` - 静音切换
|
||||
|
||||
## 与 CKPlayer 对比
|
||||
|
||||
| 特性 | CKPlayer | DPlayer |
|
||||
|------|----------|---------|
|
||||
| 界面美观度 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| 功能丰富度 | ⭐⭐⭐ | ⭐⭐⭐⭐ |
|
||||
| 移动端支持 | ⭐⭐⭐ | ⭐⭐⭐⭐ |
|
||||
| 中文支持 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| 社区活跃度 | ⭐⭐ | ⭐⭐⭐⭐ |
|
||||
| 文档质量 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| 学习成本 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||||
|
||||
## 总结
|
||||
|
||||
**DPlayer 是一个优秀的视频播放器选择**,特别适合:
|
||||
|
||||
- 🎯 需要美观界面的项目
|
||||
- 🌏 中文用户群体
|
||||
- 📱 重视移动端体验
|
||||
- 🎨 需要自定义主题的项目
|
||||
- ⚡ 追求轻量级解决方案
|
||||
|
||||
相比当前的 CKPlayer,DPlayer 提供了更好的用户体验和更丰富的功能,是升级视频播放器的理想选择。
|
@ -1,523 +0,0 @@
|
||||
# 考试信息API使用文档
|
||||
|
||||
## 接口概述
|
||||
|
||||
`/aiol/aiolExam/getExamInfo` 接口用于获取教师名下的考试信息列表。
|
||||
|
||||
## 接口定义
|
||||
|
||||
### 请求方式
|
||||
- **方法**: GET
|
||||
- **路径**: `/aiol/aiolExam/getExamInfo`
|
||||
- **参数**: `userId` (查询参数)
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必选 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| userId | string | 是 | 教师用户ID |
|
||||
|
||||
### 响应格式
|
||||
|
||||
```typescript
|
||||
{
|
||||
"success": boolean,
|
||||
"message": string,
|
||||
"code": number,
|
||||
"result": ExamInfo[],
|
||||
"timestamp": number
|
||||
}
|
||||
```
|
||||
|
||||
### ExamInfo 数据结构
|
||||
|
||||
```typescript
|
||||
interface ExamInfo {
|
||||
id: string // 考试ID
|
||||
name: string // 考试名称
|
||||
paperId: string // 试卷ID
|
||||
startTime: string // 开始时间
|
||||
endTime: string // 结束时间
|
||||
totalTime: number // 考试时长(分钟)
|
||||
type: number // 考试类型:0=练习,1=考试
|
||||
status: number // 状态:0=未发布,1=发布中,2=已结束
|
||||
createBy: string // 创建人
|
||||
createTime: string // 创建时间
|
||||
updateBy: string // 更新人
|
||||
updateTime: string // 更新时间
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 在API模块中调用
|
||||
|
||||
```typescript
|
||||
import { ExamApi } from '@/api/modules/exam'
|
||||
|
||||
// 获取教师考试信息
|
||||
const response = await ExamApi.getExamInfo('teacher_user_id')
|
||||
console.log('考试信息:', response.data)
|
||||
```
|
||||
|
||||
### 2. 在Vue组件中使用
|
||||
|
||||
```typescript
|
||||
import { ExamApi } from '@/api/modules/exam'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const userStore = useUserStore()
|
||||
|
||||
const loadExamInfo = async () => {
|
||||
if (!userStore.user?.id) {
|
||||
console.error('请先登录')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await ExamApi.getExamInfo(userStore.user.id.toString())
|
||||
return response.data || []
|
||||
} catch (error) {
|
||||
console.error('加载考试信息失败:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loadExamInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 在试卷管理页面中使用
|
||||
|
||||
```typescript
|
||||
// 在 ExamLibrary.vue 中
|
||||
import { ExamApi } from '@/api/modules/exam'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import type { ExamInfo } from '@/api/types'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const loadExamInfo = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const currentUser = userStore.user
|
||||
if (!currentUser?.id) {
|
||||
message.error('请先登录')
|
||||
return
|
||||
}
|
||||
|
||||
const response = await ExamApi.getExamInfo(currentUser.id.toString())
|
||||
|
||||
if (response.data && Array.isArray(response.data)) {
|
||||
// 数据映射和显示逻辑
|
||||
const mappedList = response.data.map((item: ExamInfo) => {
|
||||
// 映射逻辑...
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
// ... 其他字段映射
|
||||
}
|
||||
})
|
||||
|
||||
examData.value = mappedList
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载考试信息失败:', error)
|
||||
message.error('加载考试信息失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 数据映射说明
|
||||
|
||||
### 状态映射
|
||||
```typescript
|
||||
const statusMap: { [key: number]: string } = {
|
||||
0: '未发布',
|
||||
1: '发布中',
|
||||
2: '已结束'
|
||||
}
|
||||
```
|
||||
|
||||
### 类型映射
|
||||
```typescript
|
||||
const categoryMap: { [key: number]: string } = {
|
||||
0: '练习',
|
||||
1: '考试'
|
||||
}
|
||||
```
|
||||
|
||||
### 难度映射
|
||||
```typescript
|
||||
const difficultyMap: { [key: number]: string } = {
|
||||
0: '易',
|
||||
1: '中',
|
||||
2: '难'
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
接口可能返回以下错误:
|
||||
|
||||
1. **401 Unauthorized**: 用户未登录或token过期
|
||||
2. **403 Forbidden**: 没有权限访问
|
||||
3. **500 Internal Server Error**: 服务器内部错误
|
||||
|
||||
建议在调用时添加适当的错误处理:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const response = await ExamApi.getExamInfo(userId)
|
||||
// 处理成功响应
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
// 处理认证错误
|
||||
console.error('登录已过期,请重新登录')
|
||||
} else {
|
||||
// 处理其他错误
|
||||
console.error('获取考试信息失败:', error.message)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 调用此接口前需要确保用户已登录
|
||||
2. 只有教师用户才能调用此接口
|
||||
3. 返回的考试信息按创建时间倒序排列
|
||||
4. 建议在组件挂载时调用此接口加载数据
|
||||
5. 可以根据需要添加分页、搜索等参数(需要后端支持)
|
||||
|
||||
## 相关文件
|
||||
|
||||
- API实现: `src/api/modules/exam.ts`
|
||||
- 类型定义: `src/api/types.ts`
|
||||
- 使用示例: `src/api/examples/getExamInfo-example.ts`
|
||||
- 页面实现: `src/views/teacher/ExamPages/ExamLibrary.vue`
|
||||
|
||||
---
|
||||
|
||||
# 创建试卷API使用文档
|
||||
|
||||
## 接口概述
|
||||
|
||||
`POST /aiol/aiolPaper/add` 接口用于创建新的试卷。
|
||||
|
||||
## 接口定义
|
||||
|
||||
### 请求方式
|
||||
- **方法**: POST
|
||||
- **路径**: `/aiol/aiolPaper/add`
|
||||
- **Content-Type**: `application/json`
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必选 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| title | string | 是 | 试卷标题 |
|
||||
| generateMode | number | 否 | 组卷模式:0=固定试卷组,1=随机抽题组卷 |
|
||||
| rules | string | 否 | 组卷规则(随机抽题时使用) |
|
||||
| repoId | string | 否 | 题库ID(随机抽题时使用) |
|
||||
| totalScore | number | 是 | 试卷总分 |
|
||||
| passScore | number | 否 | 及格分数(默认总分的60%) |
|
||||
| requireReview | number | 否 | 是否需要批阅:0=不需要,1=需要 |
|
||||
|
||||
### 响应格式
|
||||
|
||||
```typescript
|
||||
{
|
||||
"success": boolean,
|
||||
"message": string,
|
||||
"code": number,
|
||||
"result": string, // 试卷ID
|
||||
"timestamp": number
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 在API模块中调用
|
||||
|
||||
```typescript
|
||||
import { ExamApi } from '@/api/modules/exam'
|
||||
|
||||
// 创建固定试卷组
|
||||
const response = await ExamApi.createExamPaper({
|
||||
title: '数学期末考试试卷',
|
||||
generateMode: 0,
|
||||
totalScore: 100,
|
||||
passScore: 60,
|
||||
requireReview: 0
|
||||
})
|
||||
console.log('试卷ID:', response.data)
|
||||
```
|
||||
|
||||
### 2. 在Vue组件中使用
|
||||
|
||||
```typescript
|
||||
// 在 AddExam.vue 中
|
||||
const saveExam = async () => {
|
||||
try {
|
||||
const apiData = {
|
||||
title: examForm.title,
|
||||
generateMode: examForm.type === 1 ? 0 : 1,
|
||||
rules: '',
|
||||
repoId: '',
|
||||
totalScore: examForm.totalScore,
|
||||
passScore: examForm.passScore || Math.floor(examForm.totalScore * 0.6),
|
||||
requireReview: examForm.useAIGrading ? 1 : 0
|
||||
}
|
||||
|
||||
const response = await ExamApi.createExamPaper(apiData)
|
||||
console.log('创建试卷成功:', response.data)
|
||||
} catch (error) {
|
||||
console.error('创建试卷失败:', error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 不同组卷模式示例
|
||||
|
||||
```typescript
|
||||
// 固定试卷组
|
||||
const fixedPaper = {
|
||||
title: '固定试卷组示例',
|
||||
generateMode: 0,
|
||||
rules: '',
|
||||
repoId: '',
|
||||
totalScore: 100,
|
||||
passScore: 60,
|
||||
requireReview: 0
|
||||
}
|
||||
|
||||
// 随机抽题组卷
|
||||
const randomPaper = {
|
||||
title: '随机抽题组卷示例',
|
||||
generateMode: 1,
|
||||
rules: '{"difficulty": [1, 2, 3], "types": [0, 1, 2], "count": 20}',
|
||||
repoId: 'repo_123',
|
||||
totalScore: 100,
|
||||
passScore: 60,
|
||||
requireReview: 1
|
||||
}
|
||||
```
|
||||
|
||||
## 相关文件
|
||||
|
||||
- API实现: `src/api/modules/exam.ts`
|
||||
- 页面实现: `src/views/teacher/ExamPages/AddExam.vue`
|
||||
- 使用示例: `src/api/examples/createPaper-example.ts`
|
||||
|
||||
---
|
||||
|
||||
# 删除试卷API使用文档
|
||||
|
||||
## 接口概述
|
||||
|
||||
删除试卷相关的API接口包括单个删除和批量删除功能。
|
||||
|
||||
## 接口定义
|
||||
|
||||
### 1. 删除单个试卷
|
||||
|
||||
- **方法**: DELETE
|
||||
- **路径**: `/aiol/aiolPaper/delete?id={paperId}`
|
||||
- **参数**: `id` (查询参数) - 试卷ID
|
||||
|
||||
### 2. 批量删除试卷
|
||||
|
||||
- **实现方式**: 循环调用单个删除接口(因为后端可能不支持批量删除接口)
|
||||
- **方法**: 内部调用多个 `DELETE /aiol/aiolPaper/delete?id={paperId}`
|
||||
- **参数**: `ids: string[]` - 试卷ID数组
|
||||
|
||||
### 响应格式
|
||||
|
||||
**单个删除响应**:
|
||||
```typescript
|
||||
{
|
||||
"success": boolean,
|
||||
"message": string,
|
||||
"code": number,
|
||||
"result": string,
|
||||
"timestamp": number
|
||||
}
|
||||
```
|
||||
|
||||
**批量删除响应**:
|
||||
```typescript
|
||||
{
|
||||
"success": boolean,
|
||||
"message": string,
|
||||
"code": number,
|
||||
"result": {
|
||||
"success": number, // 成功删除的数量
|
||||
"failed": number, // 失败删除的数量
|
||||
"total": number, // 总数量
|
||||
"errors": string[] // 错误信息数组
|
||||
},
|
||||
"timestamp": number
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 删除单个试卷
|
||||
|
||||
```typescript
|
||||
import { ExamApi } from '@/api/modules/exam'
|
||||
|
||||
// 删除单个试卷
|
||||
const response = await ExamApi.deleteExamPaper('1962379646322384897')
|
||||
console.log('删除结果:', response.data)
|
||||
```
|
||||
|
||||
### 2. 批量删除试卷
|
||||
|
||||
```typescript
|
||||
// 批量删除试卷
|
||||
const paperIds = ['1962379646322384897', '1966450638717292545']
|
||||
const response = await ExamApi.batchDeleteExamPapers(paperIds)
|
||||
console.log('批量删除结果:', response.data)
|
||||
```
|
||||
|
||||
### 3. 在Vue组件中使用(使用 Naive UI 对话框组件)
|
||||
|
||||
```typescript
|
||||
// 在 ExamLibrary.vue 中
|
||||
import { useDialog, useMessage } from 'naive-ui'
|
||||
|
||||
const dialog = useDialog()
|
||||
const message = useMessage()
|
||||
|
||||
const handleDeletePaper = async (row: any) => {
|
||||
try {
|
||||
// 使用 Naive UI 对话框组件
|
||||
dialog.warning({
|
||||
title: '确认删除',
|
||||
content: `确定要删除试卷"${row.name}"吗?此操作不可撤销。`,
|
||||
positiveText: '确定删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
// 调用删除API
|
||||
const response = await ExamApi.deleteExamPaper(row.id)
|
||||
|
||||
// 显示成功消息
|
||||
message.success('试卷删除成功!')
|
||||
|
||||
// 重新加载试卷列表
|
||||
await loadExamPaperList()
|
||||
} catch (error) {
|
||||
console.error('删除试卷失败:', error)
|
||||
message.error('删除试卷失败,请重试')
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('删除试卷失败:', error)
|
||||
message.error('删除试卷失败,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 批量删除示例
|
||||
const handleBatchDelete = async () => {
|
||||
if (checkedRowKeys.value.length === 0) {
|
||||
message.warning('请先选择要删除的试卷')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用 Naive UI 对话框组件
|
||||
dialog.error({
|
||||
title: '确认批量删除',
|
||||
content: `确定要删除选中的 ${checkedRowKeys.value.length} 个试卷吗?此操作不可撤销。`,
|
||||
positiveText: '确定删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
// 调用批量删除API
|
||||
const response = await ExamApi.batchDeleteExamPapers(checkedRowKeys.value as string[])
|
||||
|
||||
// 显示成功消息
|
||||
message.success(`成功删除 ${checkedRowKeys.value.length} 个试卷!`)
|
||||
|
||||
// 清空选中状态
|
||||
checkedRowKeys.value = []
|
||||
|
||||
// 重新加载试卷列表
|
||||
await loadExamPaperList()
|
||||
} catch (error) {
|
||||
console.error('批量删除试卷失败:', error)
|
||||
message.error('批量删除试卷失败,请重试')
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('批量删除试卷失败:', error)
|
||||
message.error('批量删除试卷失败,请重试')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 错误处理
|
||||
|
||||
```typescript
|
||||
const deletePaperWithErrorHandling = async (paperId: string) => {
|
||||
try {
|
||||
const response = await ExamApi.deleteExamPaper(paperId)
|
||||
|
||||
if (response.data === 'success') {
|
||||
return { success: true, message: '删除成功' }
|
||||
} else {
|
||||
return { success: false, message: '删除失败,请重试' }
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
return { success: false, message: '试卷不存在' }
|
||||
} else if (error.response?.status === 403) {
|
||||
return { success: false, message: '没有权限删除此试卷' }
|
||||
}
|
||||
|
||||
return { success: false, message: '删除失败,请检查网络连接' }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 单个删除
|
||||
- 支持删除单个试卷
|
||||
- 使用 Naive UI 警告对话框组件
|
||||
- 删除成功后自动刷新列表
|
||||
- 完整的错误处理
|
||||
|
||||
### 批量删除
|
||||
- 支持同时删除多个试卷
|
||||
- 循环调用单个删除接口(避免后端接口不存在的问题)
|
||||
- 逐个删除,避免对服务器造成过大压力
|
||||
- 详细的删除结果反馈(成功/失败数量)
|
||||
- 使用 Naive UI 错误对话框组件(更醒目的警告)
|
||||
- 删除后清空选中状态
|
||||
|
||||
### 用户体验
|
||||
- **美观的确认对话框**: 使用 Naive UI 组件,样式统一美观
|
||||
- **不同类型的对话框**: 单个删除使用 warning,批量删除使用 error
|
||||
- **成功/失败消息提示**: 使用 Naive UI 的 message 组件
|
||||
- **按钮状态管理**: 批量删除按钮在未选中时禁用
|
||||
- **实时更新选中数量显示**: 动态显示选中的试卷数量
|
||||
- **异步操作处理**: 在对话框确认后才执行删除操作
|
||||
|
||||
## 相关文件
|
||||
|
||||
- API实现: `src/api/modules/exam.ts`
|
||||
- 页面实现: `src/views/teacher/ExamPages/ExamLibrary.vue`
|
||||
- 使用示例: `src/api/examples/deletePaper-example.ts`
|
@ -2,15 +2,13 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/logo/logo1.png">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>在线学习平台</title>
|
||||
<meta name="description" content="专业的在线学习平台,提供优质的编程和技术课程">
|
||||
<meta name="keywords" content="在线学习,编程课程,技术培训,Vue.js,React,Node.js">
|
||||
<!-- CKPlayer CSS -->
|
||||
<link rel="stylesheet" href="/ckplayer/css/ckplayer.css">
|
||||
<!-- HLS.js for m3u8 playback in CKPlayer -->
|
||||
<script src="/ckplayer/hls.js/hls.min.js"></script>
|
||||
<!-- CKPlayer JS -->
|
||||
<script src="/ckplayer/js/ckplayer.js"></script>
|
||||
</head>
|
||||
|
1094
package-lock.json
generated
14
package.json
@ -13,28 +13,16 @@
|
||||
"test:ui": "vitest --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"@wangeditor/plugin-upload-attachment": "^1.1.0",
|
||||
"axios": "^1.11.0",
|
||||
"ckplayer": "^3.1.2",
|
||||
"dplayer": "^1.27.1",
|
||||
"echarts": "5.6.0",
|
||||
"naive-ui": "^2.42.0",
|
||||
"naive-ui-editor": "^1.0.6",
|
||||
"pinia": "^3.0.3",
|
||||
"quill": "^2.0.3",
|
||||
"vue": "^3.5.17",
|
||||
"vue-echarts": "7.0.3",
|
||||
"vue-i18n": "^9.14.5",
|
||||
"vue-quill-editor": "^3.0.6",
|
||||
"vue-router": "^4.5.1",
|
||||
"vuedraggable": "^4.1.0"
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dplayer": "^1.25.5",
|
||||
"@types/node": "^24.0.15",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"typescript": "^5.8.3",
|
||||
|
916
pnpm-lock.yaml
generated
Before Width: | Height: | Size: 625 B |
Before Width: | Height: | Size: 806 B |
Before Width: | Height: | Size: 821 B |
Before Width: | Height: | Size: 562 B |
Before Width: | Height: | Size: 775 B |
Before Width: | Height: | Size: 585 B |
Before Width: | Height: | Size: 917 B |
Before Width: | Height: | Size: 834 B |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1011 B |
Before Width: | Height: | Size: 908 B After Width: | Height: | Size: 812 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 944 B |
Before Width: | Height: | Size: 844 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 182 KiB |
Before Width: | Height: | Size: 175 KiB |
Before Width: | Height: | Size: 368 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 16 MiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 295 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 4.1 MiB |
Before Width: | Height: | Size: 3.3 MiB |
Before Width: | Height: | Size: 263 KiB |
Before Width: | Height: | Size: 352 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 176 KiB |
Before Width: | Height: | Size: 447 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 442 B |
Before Width: | Height: | Size: 724 B |
Before Width: | Height: | Size: 516 B |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 858 B |
Before Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 252 KiB |
Before Width: | Height: | Size: 547 B |
Before Width: | Height: | Size: 594 B |
Before Width: | Height: | Size: 3.3 MiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 680 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 359 B |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 438 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 858 B |
Before Width: | Height: | Size: 594 B |
Before Width: | Height: | Size: 547 B |
Before Width: | Height: | Size: 273 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 236 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 394 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 4.3 KiB |
BIN
public/images/icon/customer.png
Normal file
After Width: | Height: | Size: 812 B |
Before Width: | Height: | Size: 2.2 KiB |