不错
This commit is contained in:
parent
61f4176dba
commit
46237d6283
BIN
public/background1.jpg
Normal file
BIN
public/background1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 300 KiB |
BIN
public/background2.jpg
Normal file
BIN
public/background2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
BIN
src/assets/avatar.jpg
Normal file
BIN
src/assets/avatar.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref } from 'vue';
|
||||
|
||||
const count = ref(0)
|
||||
const count = ref(0);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -13,7 +13,7 @@ const count = ref(0)
|
||||
<img src="../assets/vue.svg" class="logo vue" alt="Vue logo" />
|
||||
</a>
|
||||
</div>
|
||||
<h1>{{ this.$route.meta.msg }}</h1>
|
||||
<h1>{{ $route.meta.msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<el-button type="success" @click="count++">count is {{ count }}</el-button>
|
||||
@ -25,19 +25,24 @@ const count = ref(0)
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank">create-vue</a>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
<a href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support" target="_blank">Vue Docs Scaling up Guide</a>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
|
||||
<!-- 添加导航链接 -->
|
||||
<ul>
|
||||
<li><router-link to="/login">Login</router-link></li>
|
||||
<li><router-link to="/register">Register</router-link></li>
|
||||
<li><router-link to="/courses">Courses</router-link></li>
|
||||
<li><router-link to="/meetings">Meetings</router-link></li>
|
||||
<li><router-link to="/news">News</router-link></li>
|
||||
<li><router-link to="/organizations">Organizations</router-link></li>
|
||||
<li><router-link to="/users">Users</router-link></li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -1,10 +1,23 @@
|
||||
// !!!这是一个示例,请根据实际情况修改!!!
|
||||
// import Login from '../views/authentication/Login.vue'
|
||||
// import Register from '../views/authentication/Register.vue'
|
||||
// import Profile from '../views/authentication/Profile.vue'
|
||||
// authentication.js
|
||||
import Login from '../views/authentication/Login.vue';
|
||||
import Register from '../views/authentication/Register.vue';
|
||||
import Profile from '../views/authentication/Profile.vue';
|
||||
|
||||
export default [
|
||||
// { path: '/login', component: Login },
|
||||
// { path: '/register', component: Register },
|
||||
// { path: '/profile', component: Profile }
|
||||
]
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: Login
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'Register',
|
||||
component: Register
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
// path: '/profile/:userId',
|
||||
name: 'Profile',
|
||||
component: Profile
|
||||
}
|
||||
];
|
||||
@ -0,0 +1,34 @@
|
||||
// authenticationService.js
|
||||
import axios from 'axios';
|
||||
|
||||
const API_URL = '/api';
|
||||
|
||||
class AuthenticationService {
|
||||
login(credentials) {
|
||||
return axios.post(`${API_URL}/login`, credentials);
|
||||
}
|
||||
|
||||
register(user) {
|
||||
return axios.post(`${API_URL}/register`, user);
|
||||
}
|
||||
|
||||
updateUserInfo(user) {
|
||||
return axios.post(`${API_URL}/update`, user);
|
||||
}
|
||||
|
||||
changePassword(data) {
|
||||
return axios.post(`${API_URL}/changePassword`, null, {
|
||||
params: {
|
||||
username: data.username,
|
||||
oldPassword: data.oldPassword,
|
||||
newPassword: data.newPassword
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getUserInfo(token) {
|
||||
return axios.get(`${API_URL}/profile?token=${token}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new AuthenticationService();
|
||||
@ -1,24 +1,25 @@
|
||||
const state = {
|
||||
user: null
|
||||
token: null
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
setUser(state, user) {
|
||||
state.user = user
|
||||
setUser(state, {token}) {
|
||||
state.token = token
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
login({ commit }, user) {
|
||||
commit('setUser', user)
|
||||
login({commit}, token) {
|
||||
commit('setUser', token)
|
||||
},
|
||||
logout({ commit }) {
|
||||
logout({commit}) {
|
||||
commit('setUser', null)
|
||||
}
|
||||
}
|
||||
|
||||
const getters = {
|
||||
isAuthenticated: state => !!state.user
|
||||
isAuthenticated: state => !!state.token,
|
||||
token: state => state.token
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
73
src/views/authentication/Login.vue
Normal file
73
src/views/authentication/Login.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<script setup>
|
||||
import { reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import AuthenticationService from '../../services/authenticationService';
|
||||
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
|
||||
const credentials = reactive({
|
||||
username: '',
|
||||
password: ''
|
||||
});
|
||||
|
||||
const login = async () => {
|
||||
try {
|
||||
const response = await AuthenticationService.login(credentials);
|
||||
if (response.status === 200) {
|
||||
store.commit('authentication/setUser', { token: response.data.token });
|
||||
}
|
||||
router.push("/profile");
|
||||
} catch (error) {
|
||||
ElMessage.error('Invalid username or password');
|
||||
console.error("Login error:", error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-wrapper">
|
||||
<div class="login-container">
|
||||
<h1>Login</h1>
|
||||
<el-form @submit.prevent="login" label-width="100px">
|
||||
<el-form-item label="Username:">
|
||||
<el-input v-model="credentials.username" id="username"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Password:">
|
||||
<el-input v-model="credentials.password" id="password" type="password"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" native-type="submit">Login</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background: url('@public/background1.jpg') no-repeat center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
189
src/views/authentication/Profile.vue
Normal file
189
src/views/authentication/Profile.vue
Normal file
@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="profile-wrapper">
|
||||
<div class="profile-container">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="User Information" name="userInformation"></el-tab-pane>
|
||||
<el-tab-pane label="Profile" name="profile"></el-tab-pane>
|
||||
<el-tab-pane label="Change Password" name="changePassword"></el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div v-if="activeTab === 'userInformation'">
|
||||
<h2>User Information</h2>
|
||||
<div class="user-info">
|
||||
<div class="user-details">
|
||||
<p>Username: {{ user.username }}</p>
|
||||
<p>Email: {{ user.email }}</p>
|
||||
<p>Phone Number: {{ user.phoneNumber }}</p>
|
||||
<p>Company: {{ user.company }}</p>
|
||||
<p>Created Date: {{ user.createdDate }}</p>
|
||||
</div>
|
||||
<div class="user-avatar">
|
||||
<img src= '@assets/avatar.jpg' alt="User Avatar" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'profile'">
|
||||
<h1>Profile</h1>
|
||||
<el-form @submit.prevent="updateProfile" label-width="120px" class="profile-form">
|
||||
<el-form-item label="Username:">
|
||||
<el-input v-model="user.username" id="username" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="Email:">
|
||||
<el-input v-model="user.email" id="email" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Phone Number:">
|
||||
<el-input v-model="user.phoneNumber" id="phoneNumber" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Company:">
|
||||
<el-input v-model="user.company" id="company" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" native-type="submit">Update Profile</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'changePassword'">
|
||||
<h2>Change Password</h2>
|
||||
<el-form @submit.prevent="changePassword" label-width="120px" class="password-form">
|
||||
<el-form-item label="Old Password:">
|
||||
<el-input v-model="passwords.oldPassword" id="oldPassword" type="password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="New Password:">
|
||||
<el-input v-model="passwords.newPassword" id="newPassword" type="password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Confirm Password:">
|
||||
<el-input v-model="passwords.confirmPassword" id="confirmPassword" type="password" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" native-type="submit">Change Password</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import AuthenticationService from '../../services/authenticationService';
|
||||
|
||||
const store = useStore();
|
||||
const activeTab = ref('userInformation');
|
||||
|
||||
const user = ref({
|
||||
id: null,
|
||||
username: '',
|
||||
email: '',
|
||||
phoneNumber: '',
|
||||
company: '',
|
||||
createdDate: ''
|
||||
});
|
||||
|
||||
const passwords = ref({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
|
||||
const token = store.getters['authentication/token'];
|
||||
|
||||
const fetchUserInfo = async () => {
|
||||
try {
|
||||
const response = await AuthenticationService.getUserInfo(token);
|
||||
user.value = response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching user info:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const updateProfile = async () => {
|
||||
try {
|
||||
await AuthenticationService.updateUserInfo(user.value);
|
||||
ElMessage.success('Profile updated successfully');
|
||||
await fetchUserInfo();
|
||||
} catch (error) {
|
||||
console.error("Error updating profile:", error);
|
||||
ElMessage.error('Error updating profile');
|
||||
}
|
||||
};
|
||||
|
||||
const changePassword = async () => {
|
||||
if (passwords.value.newPassword !== passwords.value.confirmPassword) {
|
||||
ElMessage.error('New password and confirm password do not match');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await AuthenticationService.changePassword({
|
||||
username: user.value.username,
|
||||
oldPassword: passwords.value.oldPassword,
|
||||
newPassword: passwords.value.newPassword
|
||||
});
|
||||
ElMessage.success('Password changed successfully');
|
||||
} catch (error) {
|
||||
console.error("Error changing password:", error);
|
||||
ElMessage.error('Old password is incorrect');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchUserInfo();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.profile-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background-image: url('@public/background2.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.profile-container {
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.profile-form, .password-form {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.user-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.user-avatar img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
84
src/views/authentication/Register.vue
Normal file
84
src/views/authentication/Register.vue
Normal file
@ -0,0 +1,84 @@
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import authenticationService from '../../services/authenticationService';
|
||||
|
||||
const username = ref('');
|
||||
const password = ref('');
|
||||
const email = ref('');
|
||||
const phoneNumber = ref('');
|
||||
const company = ref('');
|
||||
const role = ref('USER');
|
||||
const verificationCode = ref('');
|
||||
const message = ref('');
|
||||
const router = useRouter();
|
||||
|
||||
const getVerificationCode = async () => {
|
||||
// 模拟获取验证码
|
||||
message.value = 'Verification code sent. Please check your phone/email.';
|
||||
};
|
||||
|
||||
const register = async () => {
|
||||
if (!verificationCode.value) {
|
||||
message.value = 'Please enter the verification code.';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await authenticationService.register({
|
||||
username: username.value,
|
||||
password: password.value,
|
||||
email: email.value,
|
||||
phoneNumber: phoneNumber.value,
|
||||
company: company.value,
|
||||
role: role.value
|
||||
});
|
||||
message.value = response.data.message;
|
||||
router.push('/login');
|
||||
} catch (error) {
|
||||
console.error("Registration failed:", error.response.data.message);
|
||||
message.value = error.response.data.message;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
56
src/views/user-management/Profile.vue
Normal file
56
src/views/user-management/Profile.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Profile</h2>
|
||||
<form @submit.prevent="updateProfile">
|
||||
<div>
|
||||
<label for="nickname">Nickname:</label>
|
||||
<input type="text" v-model="user.nickname">
|
||||
</div>
|
||||
<div>
|
||||
<label for="phoneNumber">Phone Number:</label>
|
||||
<input type="text" v-model="user.phoneNumber">
|
||||
</div>
|
||||
<div>
|
||||
<label for="gender">Gender:</label>
|
||||
<input type="text" v-model="user.gender">
|
||||
</div>
|
||||
<button type="submit">Update Profile</button>
|
||||
</form>
|
||||
<form @submit.prevent="changePassword">
|
||||
<div>
|
||||
<label for="oldPassword">Old Password:</label>
|
||||
<input type="password" v-model="oldPassword" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="newPassword">New Password:</label>
|
||||
<input type="password" v-model="newPassword" required>
|
||||
</div>
|
||||
<button type="submit">Change Password</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
oldPassword: '',
|
||||
newPassword: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user'])
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateProfile', 'changePassword']),
|
||||
async updateProfile() {
|
||||
await this.updateProfile(this.user);
|
||||
},
|
||||
async changePassword() {
|
||||
await this.changePassword({ oldPassword: this.oldPassword, newPassword: this.newPassword });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
Loading…
Reference in New Issue
Block a user