美观和汉化

This commit is contained in:
Chester.X 2024-07-05 18:24:26 +08:00
parent d087dc171f
commit ca5ca2bb95
6 changed files with 313 additions and 129 deletions

View File

@ -1,5 +1,6 @@
import Course from '../views/course-management/Course.vue'; import Course from '../views/course-management/Course.vue';
import CourseList from '../views/course-management/CourseList.vue'; import CourseList from '../views/course-management/CourseList.vue';
import BackgroundWrapper from "@views/course-management/BackgroundWrapper.vue";
export default [ export default [
{ {
@ -12,5 +13,11 @@ export default [
path: '/courseList', path: '/courseList',
name: 'CourseList', name: 'CourseList',
component: CourseList component: CourseList
},
{
path: '/backgroundWrapper',
name: 'BackgroundWrapper',
component: BackgroundWrapper
} }
]; ];

View File

@ -18,11 +18,12 @@ const login = async () => {
const response = await AuthenticationService.login(credentials); const response = await AuthenticationService.login(credentials);
if (response.status === 200) { if (response.status === 200) {
store.commit('authentication/setUser', { token: response.data.token }); store.commit('authentication/setUser', { token: response.data.token });
}
router.push("/profile"); router.push("/profile");
ElMessage.success('登录成功');
}
} catch (error) { } catch (error) {
ElMessage.error('Invalid username or password'); ElMessage.error('用户名或密码错误');
console.error("Login error:", error); console.error("登录错误:", error);
} }
}; };
@ -37,10 +38,10 @@ const goToRegister = () => {
<h1>用户登录</h1> <h1>用户登录</h1>
<el-form @submit.prevent="login" label-width="100px"> <el-form @submit.prevent="login" label-width="100px">
<el-form-item label="用户名:"> <el-form-item label="用户名:">
<el-input v-model="credentials.username" id="username"/> <el-input v-model="credentials.username" id="username" class="short-input"/>
</el-form-item> </el-form-item>
<el-form-item label="密码:"> <el-form-item label="密码:">
<el-input v-model="credentials.password" id="password" type="password"/> <el-input v-model="credentials.password" id="password" type="password" class="short-input"/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" native-type="submit">登录</el-button> <el-button type="primary" native-type="submit">登录</el-button>
@ -64,15 +65,29 @@ const goToRegister = () => {
.login-container { .login-container {
max-width: 400px; max-width: 400px;
width: 100%; width: 100%;
padding: 20px; padding: 30px;
border: 1px solid #ebeef5; border: 1px solid #ebeef5;
border-radius: 4px; border-radius: 10px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 3px 15px rgba(0, 0, 0, 0.2);
background-color: #fff; background-color: rgba(255, 255, 255, 0.9);
} }
h1 { h1 {
text-align: center; text-align: center;
margin-bottom: 30px;
font-size: 24px;
color: #333;
}
.el-form-item {
margin-bottom: 20px; margin-bottom: 20px;
} }
.short-input {
width: 80%;
}
.el-button {
margin-right: 10px;
}
</style> </style>

View File

@ -1,13 +1,14 @@
<template> <template>
<div class="profile-wrapper"> <div class="profile-wrapper">
<div class="profile-container"> <div class="profile-container">
<el-button type="danger" @click="confirmLogout" class="logout-button">注销</el-button>
<el-tabs v-model="activeTab"> <el-tabs v-model="activeTab">
<el-tab-pane label="用户信息" name="userInformation"></el-tab-pane> <el-tab-pane label="用户信息" name="userInformation"></el-tab-pane>
<el-tab-pane label="信息修改" name="profile"></el-tab-pane> <el-tab-pane label="信息修改" name="profile"></el-tab-pane>
<el-tab-pane label="更改密码" name="changePassword"></el-tab-pane> <el-tab-pane label="更改密码" name="changePassword"></el-tab-pane>
</el-tabs> </el-tabs>
<div v-if="activeTab === 'userInformation'"> <div v-if="activeTab === 'userInformation'" class="user-information">
<h2>用户信息</h2> <h2>用户信息</h2>
<div class="user-info"> <div class="user-info">
<div class="user-details"> <div class="user-details">
@ -18,25 +19,25 @@
<p>创建日期: {{ user.createdDate }}</p> <p>创建日期: {{ user.createdDate }}</p>
</div> </div>
<div class="user-avatar"> <div class="user-avatar">
<img src= '@assets/avatar.jpg' alt="User Avatar" /> <img src='@assets/avatar.jpg' alt="用户头像" />
</div> </div>
</div> </div>
</div> </div>
<div v-if="activeTab === 'profile'"> <div v-if="activeTab === 'profile'" class="centered-content">
<h2>信息修改</h2> <h2>信息修改</h2>
<el-form @submit.prevent="updateProfile" label-width="120px" class="profile-form"> <el-form @submit.prevent="updateProfile" label-width="120px" class="centered-form profile-form">
<el-form-item label="用户名:"> <el-form-item label="用户名:">
<el-input v-model="user.username" id="username" disabled /> <el-input v-model="user.username" id="username" class="short-input" disabled />
</el-form-item> </el-form-item>
<el-form-item label="电子邮箱:"> <el-form-item label="电子邮箱:">
<el-input v-model="user.email" id="email" /> <el-input v-model="user.email" id="email" class="short-input" />
</el-form-item> </el-form-item>
<el-form-item label="电话号码:"> <el-form-item label="电话号码:">
<el-input v-model="user.phoneNumber" id="phoneNumber" /> <el-input v-model="user.phoneNumber" id="phoneNumber" class="short-input" />
</el-form-item> </el-form-item>
<el-form-item label="所属企业:"> <el-form-item label="所属企业:">
<el-input v-model="user.company" id="company" /> <el-input v-model="user.company" id="company" class="short-input" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" native-type="submit">更新</el-button> <el-button type="primary" native-type="submit">更新</el-button>
@ -44,17 +45,20 @@
</el-form> </el-form>
</div> </div>
<div v-if="activeTab === 'changePassword'"> <div v-if="activeTab === 'changePassword'" class="centered-content">
<h2>更改密码</h2> <h2>更改密码</h2>
<el-form @submit.prevent="changePassword" label-width="120px" class="password-form"> <el-form @submit.prevent="changePassword" label-width="120px" class="centered-form password-form">
<el-form-item label="旧密码:"> <el-form-item label="旧密码:">
<el-input v-model="passwords.oldPassword" id="oldPassword" type="password" /> <el-input v-model="passwords.oldPassword" :type="showOldPassword ? 'text' : 'password'" class="short-password-input" />
<el-button @click="togglePasswordVisibility('oldPassword')">{{ showOldPassword ? '隐藏' : '显示' }}</el-button>
</el-form-item> </el-form-item>
<el-form-item label="新密码:"> <el-form-item label="新密码:">
<el-input v-model="passwords.newPassword" id="newPassword" type="password" /> <el-input v-model="passwords.newPassword" :type="showNewPassword ? 'text' : 'password'" class="short-password-input" />
<el-button @click="togglePasswordVisibility('newPassword')">{{ showNewPassword ? '隐藏' : '显示' }}</el-button>
</el-form-item> </el-form-item>
<el-form-item label="再次确认:"> <el-form-item label="再次确认:">
<el-input v-model="passwords.confirmPassword" id="confirmPassword" type="password" /> <el-input v-model="passwords.confirmPassword" :type="showConfirmPassword ? 'text' : 'password'" class="short-password-input" />
<el-button @click="togglePasswordVisibility('confirmPassword')">{{ showConfirmPassword ? '隐藏' : '显示' }}</el-button>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" native-type="submit">更改</el-button> <el-button type="primary" native-type="submit">更改</el-button>
@ -68,10 +72,12 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { ElMessage } from 'element-plus'; import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus'; // ElMessageBox
import AuthenticationService from '../../services/authenticationService'; import AuthenticationService from '../../services/authenticationService';
const store = useStore(); const store = useStore();
const router = useRouter();
const activeTab = ref('userInformation'); const activeTab = ref('userInformation');
const user = ref({ const user = ref({
@ -89,6 +95,10 @@ const passwords = ref({
confirmPassword: '' confirmPassword: ''
}); });
const showOldPassword = ref(false);
const showNewPassword = ref(false);
const showConfirmPassword = ref(false);
const token = store.getters['authentication/token']; const token = store.getters['authentication/token'];
const fetchUserInfo = async () => { const fetchUserInfo = async () => {
@ -96,24 +106,24 @@ const fetchUserInfo = async () => {
const response = await AuthenticationService.getUserInfo(token); const response = await AuthenticationService.getUserInfo(token);
user.value = response.data; user.value = response.data;
} catch (error) { } catch (error) {
console.error("Error fetching user info:", error); console.error("获取用户信息错误:", error);
} }
}; };
const updateProfile = async () => { const updateProfile = async () => {
try { try {
await AuthenticationService.updateUserInfo(user.value); await AuthenticationService.updateUserInfo(user.value);
ElMessage.success('Profile updated successfully'); ElMessage.success('个人信息更新成功');
await fetchUserInfo(); await fetchUserInfo();
} catch (error) { } catch (error) {
console.error("Error updating profile:", error); console.error("更新个人信息错误:", error);
ElMessage.error('Error updating profile'); ElMessage.error('更新个人信息失败');
} }
}; };
const changePassword = async () => { const changePassword = async () => {
if (passwords.value.newPassword !== passwords.value.confirmPassword) { if (passwords.value.newPassword !== passwords.value.confirmPassword) {
ElMessage.error('New password and confirm password do not match'); ElMessage.error('新密码和确认密码不一致');
return; return;
} }
try { try {
@ -122,13 +132,36 @@ const changePassword = async () => {
oldPassword: passwords.value.oldPassword, oldPassword: passwords.value.oldPassword,
newPassword: passwords.value.newPassword newPassword: passwords.value.newPassword
}); });
ElMessage.success('Password changed successfully'); ElMessage.success('密码更改成功');
} catch (error) { } catch (error) {
console.error("Error changing password:", error); console.error("更改密码错误:", error);
ElMessage.error('Old password is incorrect'); ElMessage.error('旧密码错误');
} }
}; };
const togglePasswordVisibility = (field) => {
if (field === 'oldPassword') {
showOldPassword.value = !showOldPassword.value;
} else if (field === 'newPassword') {
showNewPassword.value = !showNewPassword.value;
} else if (field === 'confirmPassword') {
showConfirmPassword.value = !showConfirmPassword.value;
}
};
const confirmLogout = () => {
ElMessageBox.confirm('是否确认注销?', '注销确认', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}).then(logout).catch(() => {});
};
const logout = () => {
store.commit('authentication/logout');
router.push('/login');
};
onMounted(() => { onMounted(() => {
fetchUserInfo(); fetchUserInfo();
}); });
@ -136,54 +169,83 @@ onMounted(() => {
<style scoped> <style scoped>
.profile-wrapper { .profile-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 100vh; height: 100vh;
background-image: url('@public/background2.jpg'); background-image: url('@public/background2.jpg');
background-size: cover; background-size: cover;
background-position: center; background-position: center;
} }
.profile-container { .profile-container {
max-width: 600px; max-width: 600px;
width: 100%; width: 100%;
padding: 20px; padding: 20px;
background-color: #fff; background-color: #fff;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
position: relative;
} }
h1, h2 { h1, h2 {
text-align: center; text-align: center;
margin-bottom: 20px; margin-bottom: 20px;
} }
.profile-form, .password-form { .profile-form, .password-form {
margin-bottom: 30px; margin-bottom: 30px;
}
.centered-content {
display: flex;
flex-direction: column;
align-items: center;
}
.centered-form {
width: 100%;
max-width: 400px;
} }
.user-info { .user-info {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 20px; padding: 20px 20px 20px 80px;
border-top: 1px solid #ebeef5; border-top: 1px solid #ebeef5;
margin-top: 20px; margin-top: 20px;
} }
.user-details { .user-details {
flex: 1; flex: 1;
padding-right: 20px;
} }
.user-avatar { .user-avatar {
margin-left: 20px; padding-right: 50px;
} }
.user-avatar img { .user-avatar img {
width: 100px; width: 150px;
height: 100px; height: 150px;
border-radius: 50%; border-radius: 50%;
object-fit: cover; object-fit: cover;
}
.short-input {
width: 90%;
}
.short-password-input {
width: 80%;
}
.logout-button {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
padding: 10px 20px;
} }
</style> </style>

View File

@ -1,34 +1,34 @@
<template> <template>
<el-container class="register-container"> <el-container class="register-container">
<el-main> <el-main class="register-main">
<el-card class="register-card"> <el-card class="register-card">
<el-header> <el-header>
<h2>注册</h2> <h2>注册</h2>
</el-header> </el-header>
<el-form @submit.prevent="register" label-position="top" class="register-form"> <el-form @submit.prevent="register" label-position="top" class="register-form">
<el-form-item label="用户名" required> <el-form-item label="用户名" required>
<el-input type="text" v-model="username"></el-input> <el-input class="form-input" type="text" v-model="username"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="密码" required> <el-form-item label="密码" required>
<el-input type="password" v-model="password"></el-input> <el-input class="form-input" type="password" v-model="password"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="电子邮箱" required> <el-form-item label="电子邮箱" required>
<el-input type="email" v-model="email"></el-input> <el-input class="form-input" type="email" v-model="email"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="电话号码" required> <el-form-item label="电话号码" required>
<el-input type="text" v-model="phoneNumber"></el-input> <el-input class="form-input" type="text" v-model="phoneNumber"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="所属企业" required> <el-form-item label="所属企业" required>
<el-input type="text" v-model="company"></el-input> <el-input class="form-input" type="text" v-model="company"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="选择身份"> <el-form-item label="选择身份">
<el-select v-model="role" placeholder="Select role"> <el-select class="form-input" v-model="role" placeholder="选择身份">
<el-option label="租户" value="USER"></el-option> <el-option label="租户" value="USER"></el-option>
<el-option label="管理员" value="ADMIN"></el-option> <el-option label="管理员" value="ADMIN"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="请输入验证码" required> <el-form-item label="请输入验证码" required>
<el-input type="text" v-model="verificationCode"></el-input> <el-input class="form-input" type="text" v-model="verificationCode"></el-input>
<el-button class="verification-button" type="primary" @click="getVerificationCode">获取验证码</el-button> <el-button class="verification-button" type="primary" @click="getVerificationCode">获取验证码</el-button>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@ -64,8 +64,28 @@ const getVerificationCode = async () => {
}; };
const register = async () => { const register = async () => {
if (!username.value) {
ElMessage.error('用户名不能为空!');
return;
}
if (!password.value) {
ElMessage.error('密码不能为空!');
return;
}
if (!email.value) {
ElMessage.error('电子邮箱不能为空!');
return;
}
if (!phoneNumber.value) {
ElMessage.error('电话号码不能为空!');
return;
}
if (!company.value) {
ElMessage.error('所属企业不能为空!');
return;
}
if (!verificationCode.value) { if (!verificationCode.value) {
message.value = '验证码不能为空!'; ElMessage.error('验证码不能为空!');
return; return;
} }
try { try {
@ -80,7 +100,7 @@ const register = async () => {
message.value = response.data.message; message.value = response.data.message;
router.push('/login'); router.push('/login');
} catch (error) { } catch (error) {
console.error("Registration failed:", error.response.data.message); console.error("注册失败:", error.response.data.message);
message.value = error.response.data.message; message.value = error.response.data.message;
} }
}; };
@ -93,7 +113,6 @@ const goToLogin = () => {
<style scoped> <style scoped>
.register-container { .register-container {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 100vh; height: 100vh;
@ -101,7 +120,12 @@ const goToLogin = () => {
background-size: cover; background-size: cover;
background-position: center; background-position: center;
padding: 20px; padding: 20px;
overflow: hidden; }
.register-main {
display: flex;
align-items: center;
justify-content: center;
width: 100%; width: 100%;
} }
@ -111,11 +135,18 @@ const goToLogin = () => {
border-radius: 8px; border-radius: 8px;
background-color: white; background-color: white;
width: 100%; width: 100%;
max-width: 300px; max-width: 350px; /* 调整卡片的宽度 */
overflow: hidden;
} }
.register-form { .register-form {
width: 100%; width: 100%;
max-height: 400px;
overflow-y: auto; /* 使表单内容可滚动 */
}
.el-form-item {
margin-bottom: 15px; /* 调整表单项之间的间距 */
} }
.el-header h2 { .el-header h2 {
@ -123,11 +154,36 @@ const goToLogin = () => {
margin: 0 0 20px; margin: 0 0 20px;
} }
.form-input {
width: calc(100% - 12px); /* 调整输入框的宽度,与滚动条保持距离 */
padding-right: 12px; /* 增加右侧内边距 */
box-sizing: border-box; /* 确保宽度包含内边距 */
}
.verification-button { .verification-button {
margin-top: 20px; margin-top: 10px; /* 调整验证码按钮上方的间距 */
} }
.el-alert { .el-alert {
margin-top: 20px; margin-top: 20px;
} }
/* 自定义滚动条样式 */
.register-form::-webkit-scrollbar {
width: 8px; /* 滚动条宽度 */
}
.register-form::-webkit-scrollbar-track {
background: #f1f1f1; /* 滚动条轨道颜色 */
border-radius: 10px;
}
.register-form::-webkit-scrollbar-thumb {
background: #888; /* 滚动条滑块颜色 */
border-radius: 10px;
}
.register-form::-webkit-scrollbar-thumb:hover {
background: #555; /* 滚动条滑块在悬停时的颜色 */
}
</style> </style>

View File

@ -0,0 +1,17 @@
<template>
<div class="background-wrapper">
<slot></slot>
</div>
</template>
<style scoped>
.background-wrapper {
background: url('@public/background2.jpg') no-repeat center center fixed;
background-size: cover;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@ -5,6 +5,7 @@ import axios from "axios";
import { useStore } from "vuex"; import { useStore } from "vuex";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import Course from "@views/course-management/Course.vue"; import Course from "@views/course-management/Course.vue";
import BackgroundWrapper from './BackgroundWrapper.vue';
const router = useRouter(); const router = useRouter();
const store = useStore(); const store = useStore();
@ -31,7 +32,7 @@ const loadCourses = async (forceReload = false) => {
allCoursesData.value = []; allCoursesData.value = [];
} }
if (firstTimeLoad.value || allCoursesData.value.length < (currentPage.value * pageSize.value) && (currentPage.value * pageSize.value) <= coursesCount) { if (firstTimeLoad.value || allCoursesData.value.length < (currentPage.value * pageSize.value) && (currentPage.value * pageSize.value) <= coursesCount.value) {
let params = { let params = {
token: token, token: token,
start: allCoursesData.value.length, start: allCoursesData.value.length,
@ -59,7 +60,7 @@ const handleSearch = async () => {
const response = await axios.get('/api/courses/search', { const response = await axios.get('/api/courses/search', {
params: { params: {
token: token, token: token,
title: searchTitle.value || '', // title: searchTitle.value || '',
author: searchAuthor.value || '', author: searchAuthor.value || '',
description: searchDescription.value || '', description: searchDescription.value || '',
sortOrder: sortOrder.value || '', sortOrder: sortOrder.value || '',
@ -98,6 +99,10 @@ const handleSort = async () => {
} }
}); });
const data = response.data; const data = response.data;
//
console.log(data.courseList);
coursesCount.value = data.courseCount; coursesCount.value = data.courseCount;
allCoursesData.value = data.courseList; allCoursesData.value = data.courseList;
coursesData.value = allCoursesData.value.slice(0, pageSize.value); coursesData.value = allCoursesData.value.slice(0, pageSize.value);
@ -164,6 +169,8 @@ const handleDeleteInTable = async (index) => {
</script> </script>
<template> <template>
<BackgroundWrapper>
<div class="center-wrapper">
<div class="container"> <div class="container">
<div class="search-container"> <div class="search-container">
<el-form inline> <el-form inline>
@ -222,11 +229,31 @@ const handleDeleteInTable = async (index) => {
</el-pagination> </el-pagination>
</div> </div>
</div> </div>
</div>
</BackgroundWrapper>
</template> </template>
<style scoped> <style scoped>
html, body {
height: 100%;
margin: 0;
}
.center-wrapper {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.container { .container {
padding: 20px; padding: 20px;
background: rgba(255, 255, 255, 0.8); /* 为了让内容清晰,可加上一层半透明白色背景 */
border-radius: 15px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
max-width: 800px; /* 控制页面内容宽度 */
width: 100%;
} }
.search-container { .search-container {