This commit is contained in:
Guwan 2025-03-13 23:19:30 +08:00
parent 770c78733d
commit 8542a52f4b
5 changed files with 247 additions and 134 deletions

View File

@ -2,9 +2,14 @@ import { get, post, put, del } from '../utils/request'
// 获取课程列表
export function getCourses(params) {
return get('/courses', params)
return get('/bs/courses', params)
}
export function getCoursesMethod(params) {
return get('/bs/courses', params)
}
// 获取课程详情
export function getCourseDetail(id) {
return get(`/courses/${id}`)
@ -49,11 +54,11 @@ export function toggleFavoriteCourse(id) {
export function searchCourses(params) {
// 处理数组参数
let queryParams = { ...params }
// 如果有标签数组,转换为逗号分隔的字符串
if (params.tags && Array.isArray(params.tags) && params.tags.length > 0) {
queryParams.tags = params.tags.join(',')
}
return get('/courses/search', queryParams)
}
}

View File

@ -20,6 +20,17 @@ const normalUser = {
role: 'user'
}
export function registerMethod(registerForm){
return post('/bs/user/register', registerForm)
}
export function sendVerifyCodeMethod(email){
return post('/bs/user/getEmailCode', { email })
}
// 用户登录
export function login(username, password) {
// 模拟登录逻辑
@ -27,7 +38,7 @@ export function login(username, password) {
setTimeout(() => {
// 根据用户名判断返回管理员还是普通用户
const userInfo = username === 'admin' ? adminUser : normalUser
resolve({
code: 200,
data: {
@ -43,4 +54,4 @@ export function login(username, password) {
// 更新用户信息
export function updateUserProfile(userId, data) {
return put(`/users/${userId}`, data)
}
}

View File

@ -5,7 +5,8 @@ import { useUserStore } from '../stores/user'
// 创建 axios 实例
const service = axios.create({
// baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 从环境变量获取 API 基础 URL
baseURL: 'http://localhost:8101', // 从环境变量获取 API 基础 URL
//baseURL: 'http://localhost:8101', // 从环境变量获取 API 基础 URL
baseURL: 'http://localhost:8084', // 从环境变量获取 API 基础 URL
timeout: 15000, // 请求超时时间
})

View File

@ -110,6 +110,7 @@ import {
QuestionFilled, School, DocumentChecked, DataAnalysis,
Reading, Edit, User, Star
} from '@element-plus/icons-vue'
import {getCoursesMethod} from "@/api/course.js";
const router = useRouter()
@ -133,13 +134,13 @@ const features = [
]
//
const popularCourses = ref([
/*const popularCourses = ref([
{
id: 1,
title: 'C++面向对象编程精讲',
category: 'cpp',
categoryName: 'C++编程',
coverImg: 'https://p5.img.cctvpic.com/photoworkspace/contentimg/2023/03/30/2023033011303020756.jpg',
coverImg: 'http://localhost:9000/file/c6d07740-7306-4fc1-b1f8-670684a73ed9/img/31c258e7-3fd4-402c-8f2f-c9455c6cfdaf.png',
studentCount: 1234,
rating: 4.8,
price: 0
@ -165,7 +166,16 @@ const popularCourses = ref([
categoryName: '测试',
coverImg: 'https://p5.img.cctvpic.com/photoworkspace/contentimg/2023/03/30/2023033011303020756.jpg',
}
])
])*/
const popularCourses = ref([])
const getCourses = async () => {
var axiosResponse = await getCoursesMethod();
console.log(axiosResponse)
popularCourses.value = axiosResponse.data
}
const viewCourse = (courseId) => {
router.push(`/course/detail/${courseId}`)
@ -217,6 +227,9 @@ onMounted(() => {
startGuide()
localStorage.setItem('hasVisited', 'true')
}
getCourses()
})
</script>

View File

@ -24,15 +24,15 @@
<div class="login-box">
<div class="form-tabs">
<div
class="tab-item"
<div
class="tab-item"
:class="{ active: activeTab === 'login' }"
@click="activeTab = 'login'"
>
登录
</div>
<div
class="tab-item"
<div
class="tab-item"
:class="{ active: activeTab === 'register' }"
@click="activeTab = 'register'"
>
@ -42,7 +42,7 @@
</div>
<div class="form-container" v-if="activeTab === 'login'">
<el-form
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginRules"
@ -51,7 +51,7 @@
<el-form-item prop="username">
<div class="custom-input" :class="{ focused: activeInput === 'username' }">
<el-icon><User /></el-icon>
<input
<input
v-model="loginForm.username"
type="text"
placeholder="请输入用户名"
@ -64,14 +64,14 @@
<el-form-item prop="password">
<div class="custom-input" :class="{ focused: activeInput === 'password' }">
<el-icon><Lock /></el-icon>
<input
<input
v-model="loginForm.password"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入密码"
@focus="activeInput = 'password'"
@blur="activeInput = ''"
>
<el-icon
<el-icon
class="password-toggle"
@click="showPassword = !showPassword"
>
@ -86,8 +86,8 @@
<el-button type="text" @click="forgotPassword">忘记密码</el-button>
</div>
<el-button
type="primary"
<el-button
type="primary"
class="submit-btn"
:loading="loading"
@click="handleLogin"
@ -114,17 +114,16 @@
</div>
<div class="form-container" v-else>
<el-form
<el-form
ref="registerFormRef"
:model="registerForm"
:rules="registerRules"
class="register-form"
>
<!-- 注册表单字段 -->
<el-form-item prop="username">
<div class="custom-input">
<el-icon><User /></el-icon>
<input
<input
v-model="registerForm.username"
type="text"
placeholder="请设置用户名"
@ -135,23 +134,43 @@
<el-form-item prop="email">
<div class="custom-input">
<el-icon><Message /></el-icon>
<input
<input
v-model="registerForm.email"
type="email"
placeholder="请输入邮箱"
>
<el-button
class="verify-btn"
type="primary"
:disabled="cooldown > 0"
@click="sendVerifyCode"
>
{{ cooldown > 0 ? `${cooldown}s` : '发送验证码' }}
</el-button>
</div>
</el-form-item>
<el-form-item prop="verifyCode">
<div class="custom-input">
<el-icon><Key /></el-icon>
<input
v-model="registerForm.verifyCode"
type="text"
placeholder="请输入验证码"
maxlength="6"
>
</div>
</el-form-item>
<el-form-item prop="password">
<div class="custom-input">
<el-icon><Lock /></el-icon>
<input
<input
v-model="registerForm.password"
:type="showPassword ? 'text' : 'password'"
placeholder="请设置密码"
>
<el-icon
<el-icon
class="password-toggle"
@click="showPassword = !showPassword"
>
@ -164,7 +183,7 @@
<el-form-item prop="confirmPassword">
<div class="custom-input">
<el-icon><Lock /></el-icon>
<input
<input
v-model="registerForm.confirmPassword"
:type="showPassword ? 'text' : 'password'"
placeholder="请确认密码"
@ -172,8 +191,8 @@
</div>
</el-form-item>
<el-button
type="primary"
<el-button
type="primary"
class="submit-btn"
:loading="loading"
@click="handleRegister"
@ -190,18 +209,22 @@
<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '../stores/user'
import { ElMessage } from 'element-plus'
import {
import {
User, Lock, View, Hide, Message,
ChatDotRound, Position
ChatDotRound, Position, Key
} from '@element-plus/icons-vue'
import { registerMethod, sendVerifyCodeMethod } from "@/api/user.js"
const router = useRouter()
const userStore = useUserStore()
const activeTab = ref('login')
const showPassword = ref(false)
const loading = ref(false)
const rememberMe = ref(false)
const activeInput = ref('')
const cooldown = ref(0)
const loginForm = reactive({
username: '',
@ -211,11 +234,11 @@ const loginForm = reactive({
const registerForm = reactive({
username: '',
email: '',
verifyCode: '',
password: '',
confirmPassword: ''
})
//
const loginRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
@ -227,7 +250,6 @@ const loginRules = {
]
}
//
const registerRules = {
username: [
{ required: true, message: '请设置用户名', trigger: 'blur' },
@ -237,59 +259,74 @@ const registerRules = {
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
verifyCode: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ len: 6, message: '验证码长度应为6位', trigger: 'blur' }
],
password: [
{ required: true, message: '请设置密码', trigger: 'blur' },
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== registerForm.password) {
callback(new Error('两次输入密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
]
}
//
const handleLogin = async () => {
loading.value = true
try {
//
await new Promise(resolve => setTimeout(resolve, 1000))
ElMessage.success('登录成功')
router.push('/')
if (loginForm.username === 'admin' && loginForm.password === 'admin123') {
await userStore.mockAdminLogin()
ElMessage.success('登录成功')
router.push('/')
} else {
ElMessage.error('用户名或密码错误')
}
} catch (error) {
ElMessage.error('登录失败')
ElMessage.error('登录失败: ' + error.message)
} finally {
loading.value = false
}
}
//
const handleRegister = async () => {
loading.value = true
try {
//
await new Promise(resolve => setTimeout(resolve, 1000))
ElMessage.success('注册成功')
activeTab.value = 'login'
} catch (error) {
ElMessage.error('注册失败')
} finally {
loading.value = false
const handleRegister = () => {
if (registerForm.password !== registerForm.confirmPassword){
ElMessage.error('两次密码不一致')
return
}
registerMethod(registerForm)
}
//
const forgotPassword = () => {
ElMessage.info('忘记密码功能开发中')
}
const sendVerifyCode = async () => {
if (!registerForm.email) {
ElMessage.warning('请先输入邮箱地址')
return
}
try {
var axiosResponse = await sendVerifyCodeMethod(registerForm.email);
ElMessage.success('验证码已发送')
cooldown.value = 60
const timer = setInterval(() => {
cooldown.value--
if (cooldown.value <= 0) {
clearInterval(timer)
}
}, 1000)
console.log(axiosResponse)
} catch (error) {
ElMessage.error('验证码发送失败: ' + error.message)
}
}
</script>
<style lang="scss" scoped>
@ -375,7 +412,7 @@ const forgotPassword = () => {
.login-header {
text-align: center;
.logo-wrapper {
.logo-text {
.logo-title {
@ -404,88 +441,86 @@ const forgotPassword = () => {
}
.login-box {
width: 100%;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 30px;
width: 100%;
max-width: 460px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
.form-tabs {
display: flex;
position: relative;
margin-bottom: 30px;
border-bottom: 2px solid #eee;
.form-container {
width: 100%;
}
.tab-item {
flex: 1;
text-align: center;
padding: 12px;
font-size: 16px;
color: #666;
cursor: pointer;
transition: all 0.3s;
&.active {
color: #2563eb;
font-weight: bold;
}
}
.tab-line {
position: absolute;
bottom: -2px;
height: 2px;
width: 50%;
background: #2563eb;
transition: all 0.3s;
&.register {
transform: translateX(100%);
}
}
.login-form,
.register-form {
width: 100%;
}
.custom-input {
position: relative;
background: #f3f4f6;
border-radius: 8px;
width: 100%;
background: #f8fafc;
border: 2px solid transparent;
border-radius: 12px;
padding: 12px 16px;
display: flex;
align-items: center;
transition: all 0.3s;
margin-bottom: 16px;
transition: all 0.3s ease;
margin-bottom: 20px;
&:hover {
background: #fff;
border-color: #e2e8f0;
}
&.focused {
background: white;
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
background: #fff;
border-color: #4080ff;
box-shadow: 0 0 0 4px rgba(64, 128, 255, 0.1);
}
.el-icon {
font-size: 20px;
color: #666;
font-size: 18px;
color: #94a3b8;
margin-right: 12px;
}
input {
width: 100%;
flex: 1;
border: none;
background: none;
outline: none;
font-size: 15px;
color: #333;
color: #1e293b;
&::placeholder {
color: #999;
color: #94a3b8;
}
}
.password-toggle {
cursor: pointer;
margin-left: 12px;
color: #94a3b8;
transition: color 0.3s;
&:hover {
color: #2563eb;
color: #4080ff;
}
}
.verify-btn {
padding: 6px 12px;
height: 32px;
font-size: 14px;
border-radius: 6px;
margin-left: 8px;
flex-shrink: 0;
&:disabled {
cursor: not-allowed;
opacity: 0.6;
}
}
}
@ -494,28 +529,47 @@ const forgotPassword = () => {
display: flex;
justify-content: space-between;
align-items: center;
margin: 20px 0;
margin: 24px 0;
.el-checkbox {
.el-checkbox__label {
color: #64748b;
}
}
.el-button {
color: #4080ff;
&:hover {
color: #2563eb;
}
}
}
.submit-btn {
width: 100%;
height: 42px;
font-size: 15px;
border-radius: 8px;
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
height: 44px;
font-size: 16px;
font-weight: 500;
border-radius: 12px;
background: linear-gradient(135deg, #4080ff 0%, #2563eb 100%);
border: none;
margin-bottom: 20px;
margin-bottom: 24px;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2);
}
&:active {
transform: translateY(0);
}
}
.divider {
position: relative;
text-align: center;
margin: 30px 0;
margin: 32px 0;
&::before,
&::after {
@ -524,25 +578,56 @@ const forgotPassword = () => {
top: 50%;
width: calc(50% - 30px);
height: 1px;
background: #ddd;
background: #e2e8f0;
}
&::before {
left: 0;
}
&::after {
right: 0;
}
&::before { left: 0; }
&::after { right: 0; }
span {
background: white;
padding: 0 10px;
color: #666;
padding: 0 12px;
color: #64748b;
font-size: 14px;
}
}
.form-tabs {
display: flex;
margin-bottom: 30px;
position: relative;
border-bottom: 2px solid #e2e8f0;
.tab-item {
flex: 1;
text-align: center;
padding: 12px;
font-size: 16px;
color: #64748b;
cursor: pointer;
transition: all 0.3s;
&.active {
color: #4080ff;
font-weight: 500;
}
}
.tab-line {
position: absolute;
bottom: -2px;
left: 0;
width: 50%;
height: 2px;
background: #4080ff;
transition: transform 0.3s;
&.register {
transform: translateX(100%);
}
}
}
.social-login {
display: flex;
justify-content: center;
@ -589,7 +674,6 @@ const forgotPassword = () => {
}
}
//
@keyframes float {
0%, 100% {
transform: translate(0, 0);
@ -608,11 +692,10 @@ const forgotPassword = () => {
}
}
//
@media (max-width: 768px) {
.login-container {
padding: 15px;
.login-content {
.login-header {
.logo-wrapper {
@ -629,10 +712,10 @@ const forgotPassword = () => {
.login-box {
padding: 25px 20px;
.custom-input {
padding: 10px 14px;
input {
font-size: 14px;
}
@ -646,4 +729,4 @@ const forgotPassword = () => {
}
}
}
</style>
</style>