改了很多

This commit is contained in:
Chester.X 2024-07-04 23:06:05 +08:00
parent 0293c09ebd
commit e0c0a10a43
9 changed files with 379 additions and 285 deletions

3
package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "frontend", "name": "frontend",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.7.2", "axios": "^1.7.2",
"element-plus": "^2.7.6", "element-plus": "^2.7.6",
"vue": "^3.4.29", "vue": "^3.4.29",
@ -41,7 +42,7 @@
}, },
"node_modules/@element-plus/icons-vue": { "node_modules/@element-plus/icons-vue": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
"integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
"peerDependencies": { "peerDependencies": {
"vue": "^3.2.0" "vue": "^3.2.0"

View File

@ -9,6 +9,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.7.2", "axios": "^1.7.2",
"element-plus": "^2.7.6", "element-plus": "^2.7.6",
"vue": "^3.4.29", "vue": "^3.4.29",

View File

@ -1,111 +0,0 @@
<template>
<div>
<div class="search-bar">
<input v-model="searchName" placeholder="请输入课程名称">
<input v-model="searchOrder" type="number" placeholder="请输入课程排序">
<button @click="searchCourses">搜索</button>
<button @click="exportCourses">导出</button>
</div>
<div class="button-bar">
<button @click="showCreateModal">新增</button>
</div>
<table>
<thead>
<tr>
<th>课程ID</th>
<th>课程名称</th>
<th>课程简介</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="course in courses" :key="course.id">
<td>{{ course.id }}</td>
<td>{{ course.name }}</td>
<td>{{ course.description }}</td>
<td>
<button @click="showEditModal(course)">修改</button>
<button @click="deleteCourse(course.id)">删除</button>
</td>
</tr>
</tbody>
</table>
<!-- 创建/编辑课程模态框 -->
<CreateEditCourseModal v-if="showModal" :course="currentCourse" @close="closeModal" @refresh="fetchCourses" />
</div>
</template>
<script>
import axios from 'axios';
import CreateEditCourseModal from './CreateEditCourseModel.vue';
export default {
components: {
CreateEditCourseModal
},
data() {
return {
courses: [],
searchName: '',
searchOrder: '',
showModal: false,
currentCourse: null
};
},
methods: {
fetchCourses() {
axios.get('/api/courses')
.then(response => {
this.courses = response.data;
});
},
searchCourses() {
axios.get('/api/courses', {
params: {
name: this.searchName,
sortOrder: this.searchOrder
}
}).then(response => {
this.courses = response.data;
});
},
exportCourses() {
axios.get('/api/courses/export', { responseType: 'blob' })
.then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'courses.xlsx');
document.body.appendChild(link);
link.click();
});
},
showCreateModal() {
this.currentCourse = null;
this.showModal = true;
},
showEditModal(course) {
this.currentCourse = course;
this.showModal = true;
},
deleteCourse(courseId) {
if (confirm('确定要删除该课程吗?')) {
axios.delete(`/api/courses/${courseId}`)
.then(() => {
this.fetchCourses();
});
}
},
closeModal() {
this.showModal = false;
}
},
mounted() {
this.fetchCourses();
}
};
</script>
<style>
/* 添加你的样式 */
</style>

View File

@ -1,123 +0,0 @@
<template>
<div class="modal">
<div class="modal-content">
<h3>{{ course ? '编辑课程' : '新增课程' }}</h3>
<form @submit.prevent="submitForm">
<input v-model="form.name" placeholder="课程名称" required>
<textarea v-model="form.description" placeholder="课程简介" required></textarea>
<input v-model="form.author" placeholder="课程作者" required>
<input v-model="form.sortOrder" type="number" placeholder="课程排序" required>
<h2>上传封面图片</h2>
<input type="file" @change="handleFileChange">
<img v-if="imageUrl" :src="imageUrl" alt="Image preview" width="200" />
<h2>上传视频</h2>
<input type="file" @change="handleVideoChange">
<video v-if="videoUrl" :src="videoUrl" controls width="400"></video>
<button type="submit">确定</button>
<button @click="$emit('close')">取消</button>
</form>
</div>
</div>
</template>
<script>
import CourseService from '@/services/courseService';
export default {
props: ['course'],
data() {
return {
form: {
name: '',
description: '',
author: '',
sortOrder: '',
coverImage: null,
video: null
},
imageUrl: null,
videoUrl: null,
};
},
methods: {
handleFileChange(event) {
const file = event.target.files[0];
if (file) {
this.form.coverImage = file;
this.imageUrl = URL.createObjectURL(file);
}
},
handleVideoChange(event) {
const file = event.target.files[0];
if (file) {
this.form.video = file;
this.videoUrl = URL.createObjectURL(file);
}
},
submitForm() {
const formData = new FormData();
formData.append('name', this.form.name);
formData.append('description', this.form.description);
formData.append('author', this.form.author);
formData.append('sortOrder', this.form.sortOrder);
formData.append('coverImage', this.form.coverImage);
formData.append('video', this.form.video);
if (this.course) {
CourseService.editCourse(this.course.id, formData)
.then(() => {
this.$emit('refresh');
this.$emit('close');
})
.catch(error => {
alert('提交失败: ' + error.response.data.message);
});
} else {
CourseService.createCourse(formData)
.then(() => {
this.$emit('refresh');
this.$emit('close');
})
.catch(error => {
alert('提交失败: ' + error.response.data.message);
});
}
}
},
watch: {
course: {
immediate: true,
handler(newCourse) {
if (newCourse) {
this.form.name = newCourse.name;
this.form.description = newCourse.description;
this.form.author = newCourse.author;
this.form.sortOrder = newCourse.sortOrder;
} else {
this.form.name = '';
this.form.description = '';
this.form.author = '';
this.form.sortOrder = '';
this.form.coverImage = null;
this.form.video = null;
this.imageUrl = null;
this.videoUrl = null;
}
}
}
}
};
</script>
<style scoped>
.modal {
/* 添加你的样式 */
}
.modal-content {
/* 添加你的样式 */
}
</style>

View File

@ -1,9 +1,16 @@
import CourseManagement from '../views/course-management/CourseManagement.vue'; import Course from '../views/course-management/Course.vue';
import CourseList from '../views/course-management/CourseList.vue';
export default [ export default [
{ {
path: '/courses', path: '/course',
name: 'Courses', name: 'Course',
component: CourseManagement component: Course
},
{
path: '/courseList',
name: 'CourseList',
component: CourseList
} }
]; ];

View File

@ -1,27 +0,0 @@
import axios from 'axios';
const API_URL = '/api';
class CourseService {
createCourse(course) {
return axios.post(`${API_URL}/course/create`, course, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
editCourse(courseId, course) {
return axios.put(`${API_URL}/course/edit/${courseId}`, course, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
getCourse(courseId) {
return axios.get(`${API_URL}/course/get/${courseId}`);
}
}
export default new CourseService();

View File

@ -0,0 +1,352 @@
<script setup>
import {onMounted, ref} from 'vue';
import {
ElButton,
ElForm,
ElFormItem,
ElInput,
ElMessage,
ElMessageBox,
ElOption,
ElSelect,
ElUpload
} from 'element-plus';
import {Delete, Plus, Refresh, ZoomIn} from "@element-plus/icons-vue";
import {useRoute, useRouter} from "vue-router";
import {useStore} from "vuex";
import axios from "axios";
const route = useRoute();
const router = useRouter();
const store = useStore();
const token = ref('')
const form = ref({
token: '',
title: '',
description: '',
orderNo: '',
author: '',
videoPath: '',
imagePath: ''
});
const createMode = ref(false);
const modeTitle = ref('');
const videoFileList = ref([]); //
const imageFileList = ref([]); //
const basePath = '/api/courses'
const videoUploadUrl = basePath + '/upload'; //
const imageUploadUrl = videoUploadUrl; //
const dialogImageUrl = ref('');
const dialogVisible = ref(false); //
//
const id = ref('');
const beforeVideoUpload = (file) => {
const isMp4 = file.type === 'video/mp4';
if (!isMp4) {
ElMessage.error('只能上传 mp4 格式的视频文件!');
return false;
}
const isLt500M = file.size / 1024 / 1024 < 500;
if (!isLt500M) {
ElMessage.error('上传视频大小不能超过 500MB');
return false;
}
return true;
};
const beforeImageUpload = (file) => {
const isImage = file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/jpeg';
if (!isImage) {
ElMessage.error('只能上传 png/jpg/jpeg 格式的图片文件!');
return false;
}
const isLt5M = file.size / 1024 / 1024 < 5;
if (!isLt5M) {
ElMessage.error('上传文件大小不能超过 5MB');
return false;
}
return true;
};
const handleVideoSuccess = (response, file, fileList) => {
file.url = response.url;
};
const handleImageSuccess = (response, file, fileList) => {
file.url = response.url;
};
const handleVideoError = (error, file, fileList) => {
ElMessage.error('视频上传失败,请重试!');
};
const handleImageError = (error, file, fileList) => {
ElMessage.error('图片上传失败,请重试!');
};
const handleVideoChange = (file, fileList) => {
if (fileList.length > 1) {
fileList.splice(0, fileList.length - 1)
}
};
const handleImageChange = (file, fileList) => {
if (fileList.length > 1) {
fileList.splice(0, fileList.length - 1)
}
};
const handlePictureCardPreview = (file) => {
dialogImageUrl.value = file.url
dialogVisible.value = true
}
const handleRemoveImage = (file) => {
imageFileList.value = imageFileList.value.filter((item) => item.uid !== file.uid);
}
const handleRemoveVideo = (file) => {
videoFileList.value = videoFileList.value.filter((item) => item.uid !== file.uid);
}
const handleCommit = async () => {
if (form.value.title === '' || form.value.description === '' || form.value.orderNo === '' || form.value.author === '') {
await ElMessageBox.alert('请填写完整信息!');
return;
}
if (imageFileList.value.length === 0 || videoFileList.value.length === 0) {
await ElMessageBox.alert('请选择封面图片和课程视频!');
return;
}
form.value.imagePath = imageFileList.value[0].url;
form.value.videoPath = videoFileList.value[0].url;
if (createMode.value) {
try {
const response = await axios.post(basePath, form.value);
ElMessage.success('添加成功!');
router.push('/courseList');
} catch (e) {
await ElMessageBox.alert(e.response.data.message);
}
} else {
try {
const response = await axios.put(`${basePath}/${id.value}`, form.value);
ElMessage.success('修改成功!');
router.push('/courseList');
} catch (e) {
await ElMessageBox.alert(e.response.data.message);
}
}
};
const isLayoutReady = ref(false);
const fetchCourseDetail = async () => {
let params = {
token: token.value,
}
const res = await axios.get(basePath + '/' + id.value, {params});
form.value.title = res.data.title;
form.value.description = res.data.description;
form.value.orderNo = res.data.orderNo;
form.value.author = res.data.author;
form.value.videoPath = res.data.videoPath;
form.value.imagePath = res.data.imagePath;
imageFileList.value.push({url: res.data.imagePath});
videoFileList.value.push({url: res.data.videoPath});
}
onMounted(() => {
token.value = store.getters['authentication/token'];
form.value.token = token.value;
if (route.query.mode === 'create' || route.params.mode === 'create') {
createMode.value = true;
modeTitle.value = '添加课程';
}
if (route.query.mode === 'edit' || route.params.mode === 'edit') {
id.value = route.params.id === undefined ? route.query.id : route.params.id;
createMode.value = false;
modeTitle.value = '修改课程';
fetchCourseDetail();
}
});
</script>
<template>
<div class="form-container">
<ElForm :model="form" label-width="120px" @submit.prevent="handleCommit">
<h2>{{ modeTitle }}</h2>
<ElFormItem label="课程名称" required>
<ElInput v-model="form.title" placeholder="请输入课程名称" required></ElInput>
</ElFormItem>
<ElFormItem label="课程封面" required>
<el-upload
:action="imageUploadUrl"
:limit="2"
:before-upload="beforeImageUpload"
:on-success="handleImageSuccess"
:on-error="handleImageError"
:on-change="handleImageChange"
:file-list="imageFileList"
list-type="picture-card"
auto-upload
v-model:file-list="imageFileList"
>
<template #file="{ file }">
<div>
<img class="el-upload-list__item-thumbnail" :src="file.url" alt=""/>
<span class="el-upload-list__item-actions">
<span
class="el-upload-list__item-preview"
@click="handlePictureCardPreview(file)"
>
<el-icon><zoom-in/></el-icon>
</span>
<span
class="el-upload-list__item-delete"
@click="handleRemoveImage(file)"
>
<el-icon><Delete/></el-icon>
</span>
</span>
</div>
</template>
<el-icon v-if="imageFileList.length === 0">
<Plus/>
</el-icon>
</el-upload>
<div class="tip">
请上传大小不超过 <span style="color: red;">5MB</span> 格式为 <span style="color: red;">png/jpg/jpeg</span> 的文件
</div>
</ElFormItem>
<ElFormItem label="课程视频" required>
<el-upload
:action="videoUploadUrl"
:limit="1"
:before-upload="beforeVideoUpload"
:on-success="handleVideoSuccess"
:on-error="handleVideoError"
:on-change="handleVideoChange"
:file-list="videoFileList"
list-type="picture-card"
auto-upload
v-model:file-list="videoFileList"
>
<template #file="{ file }">
<div>
<video class="el-upload-list__item-thumbnail" :src="file.url" controls></video>
<span class="el-upload-list__item-actions">
<span
class="el-upload-list__item-delete"
@click="handleRemoveVideo(file)"
>
<el-icon><Delete/></el-icon>
</span>
</span>
</div>
</template>
<el-icon v-if="videoFileList.length === 0">
<Plus/>
</el-icon>
</el-upload>
<div class="tip">
请上传大小不超过 <span style="color: red;">500MB</span> 格式为 <span style="color: red;">mp4</span> 的文件
</div>
</ElFormItem>
<ElFormItem label="课程简介" required>
<ElInput v-model="form.description" placeholder="请输入课程简介" required></ElInput>
</ElFormItem>
<ElFormItem label="课程排序" required>
<ElInput v-model="form.orderNo" placeholder="请输入课程排序" required></ElInput>
</ElFormItem>
<ElFormItem label="作者" required>
<ElInput v-model="form.author" placeholder="请输入作者" required></ElInput>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" native-type="submit">确定</ElButton>
<ElButton @click="router.push('/courses')">取消</ElButton>
</ElFormItem>
</ElForm>
</div>
<el-dialog v-model="dialogVisible" class="image-preview">
<img w-full :src="dialogImageUrl" alt="Preview Image"/>
</el-dialog>
</template>
<style scoped>
.form-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: white;
overflow-y: auto; /* 使容器在内容溢出时出现滚动条 */
}
.image-preview {
display: flex;
justify-content: center;
align-items: center;
}
.image-preview img {
max-width: 100%;
height: auto;
}
.tip {
margin-top: 8px;
color: #727272;
font-size: 12px;
}
.el-form {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 55%;
max-height: 90vh; /* 确保表单不会超过视口高度 */
overflow-y: auto; /* 使表单在内容溢出时出现滚动条 */
}
.el-form-item {
margin-bottom: 1rem;
text-align: left;
}
.el-form-item label {
display: block;
margin-bottom: 0.5rem;
}
.el-input,
.el-select {
width: 100%;
}
.el-button--primary {
background-color: #007bff;
color: white;
}
.el-button--primary:hover {
background-color: #0056b3;
}
.dynamic-width-select {
min-width: 30%;
max-width: 100%;
width: auto; /* 使宽度根据内容调整 */
}
</style>

View File

@ -0,0 +1,13 @@
<template>
</template>
<script>
export default {
name: "CourseList"
}
</script>
<style scoped>
</style>

View File

@ -1,19 +0,0 @@
<template>
<div>
<CourseList />
</div>
</template>
<script>
import CourseList from '@/components/CourseList.vue';
export default {
components: {
CourseList
}
};
</script>
<style>
/* 添加你的样式 */
</style>