1
This commit is contained in:
parent
e9c59574c5
commit
0293c09ebd
111
src/components/CourseList.vue
Normal file
111
src/components/CourseList.vue
Normal file
@ -0,0 +1,111 @@
|
||||
<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>
|
||||
123
src/components/CreateEditCourseModel.vue
Normal file
123
src/components/CreateEditCourseModel.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<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>
|
||||
@ -1,9 +1,9 @@
|
||||
// import CourseList from '../views/course-management/CourseList.vue'
|
||||
// import CourseDetail from '../views/course-management/CourseDetail.vue'
|
||||
// import CourseEdit from '../views/course-management/CourseEdit.vue'
|
||||
import CourseManagement from '../views/course-management/CourseManagement.vue';
|
||||
|
||||
export default [
|
||||
// { path: '/courses', component: CourseList },
|
||||
// { path: '/courses/:id', component: CourseDetail },
|
||||
// { path: '/courses/:id/edit', component: CourseEdit }
|
||||
]
|
||||
{
|
||||
path: '/courses',
|
||||
name: 'Courses',
|
||||
component: CourseManagement
|
||||
}
|
||||
];
|
||||
@ -0,0 +1,27 @@
|
||||
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();
|
||||
@ -25,6 +25,10 @@ const login = async () => {
|
||||
console.error("Login error:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const goToRegister = () => {
|
||||
router.push('/register');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -40,6 +44,7 @@ const login = async () => {
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" native-type="submit">Login</el-button>
|
||||
<el-button type="default" @click="goToRegister">Register</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
@ -1,48 +1,51 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Register</h2>
|
||||
<form @submit.prevent="register">
|
||||
<div>
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" v-model="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" v-model="password" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" v-model="email" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="phoneNumber">Phone Number:</label>
|
||||
<input type="text" v-model="phoneNumber">
|
||||
</div>
|
||||
<div>
|
||||
<label for="company">Company:</label>
|
||||
<input type="text" v-model="company">
|
||||
</div>
|
||||
<div>
|
||||
<label for="role">Role:</label>
|
||||
<select v-model="role">
|
||||
<option value="USER">User</option>
|
||||
<option value="ADMIN">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="verificationCode">Verification Code:</label>
|
||||
<input type="text" v-model="verificationCode" required>
|
||||
<button type="button" @click="getVerificationCode">Get Verification Code</button>
|
||||
</div>
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
<p v-if="message">{{ message }}</p>
|
||||
</div>
|
||||
<el-container class="register-container">
|
||||
<el-main>
|
||||
<el-card class="register-card">
|
||||
<el-header>
|
||||
<h2>Register</h2>
|
||||
</el-header>
|
||||
<el-form @submit.prevent="register" label-position="top" class="register-form">
|
||||
<el-form-item label="Username" required>
|
||||
<el-input type="text" v-model="username"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Password" required>
|
||||
<el-input type="password" v-model="password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Email" required>
|
||||
<el-input type="email" v-model="email"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Phone Number" required>
|
||||
<el-input type="text" v-model="phoneNumber"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Company" required>
|
||||
<el-input type="text" v-model="company"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Role">
|
||||
<el-select v-model="role" placeholder="Select role">
|
||||
<el-option label="User" value="USER"></el-option>
|
||||
<el-option label="Admin" value="ADMIN"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Verification Code" required>
|
||||
<el-input type="text" v-model="verificationCode"></el-input>
|
||||
<el-button class="verification-button" type="primary" @click="getVerificationCode">Get Verification Code</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" native-type="submit">Register</el-button>
|
||||
<el-button type="default" @click="goToLogin">Back</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-alert v-if="message" type="info" :closable="false">{{ message }}</el-alert>
|
||||
</el-card>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import authenticationService from '../../services/authenticationService';
|
||||
|
||||
const username = ref('');
|
||||
@ -81,4 +84,50 @@ const register = async () => {
|
||||
message.value = error.response.data.message;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
const goToLogin = () => {
|
||||
router.push('/login');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.register-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background-image: url('@public/background1.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.register-card {
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.register-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-header h2 {
|
||||
text-align: center;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.verification-button {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.el-alert {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
19
src/views/course-management/CourseManagement.vue
Normal file
19
src/views/course-management/CourseManagement.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<CourseList />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CourseList from '@/components/CourseList.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CourseList
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 添加你的样式 */
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user