本文目录导读:

我来为您提供一个完整的Java前后端分离案例,使用Spring Boot作为后端,Vue.js作为前端。
项目结构
project-root/
├── backend/ # Spring Boot后端
└── frontend/ # Vue.js前端
后端(Spring Boot)
创建Spring Boot项目
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>user-management</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>user-management</name>
<description>用户管理系统</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
配置文件
application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_management?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
# JWT配置
jwt:
secret: your-secret-key-here-change-in-production
expiration: 86400000 # 24小时
# CORS配置
cors:
allowed-origins: http://localhost:3000
实体类
User.java
package com.example.usermanagement.entity;
import lombok.Data;
import javax.persistence.*;
import javax.validation.constraints.*;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
@Column(unique = true, nullable = false)
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
@Column(unique = true, nullable = false)
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, message = "密码至少6位")
private String password;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
数据传输对象 (DTO)
UserDTO.java
package com.example.usermanagement.dto;
import lombok.Data;
import javax.validation.constraints.*;
@Data
public class UserDTO {
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, message = "密码至少6位")
private String password;
}
LoginRequest.java
package com.example.usermanagement.dto;
import lombok.Data;
import javax.validation.constraints.*;
@Data
public class LoginRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
LoginResponse.java
package com.example.usermanagement.dto;
import lombok.Data;
@Data
public class LoginResponse {
private String token;
private String username;
private String email;
public LoginResponse(String token, String username, String email) {
this.token = token;
this.username = username;
this.email = email;
}
}
Repository
UserRepository.java
package com.example.usermanagement.repository;
import com.example.usermanagement.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
Boolean existsByUsername(String username);
Boolean existsByEmail(String email);
}
Service
UserService.java
package com.example.usermanagement.service;
import com.example.usermanagement.dto.*;
import com.example.usermanagement.entity.User;
import com.example.usermanagement.repository.UserRepository;
import com.example.usermanagement.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
public LoginResponse register(UserDTO userDTO) {
// 检查用户名是否已存在
if (userRepository.existsByUsername(userDTO.getUsername())) {
throw new RuntimeException("用户名已存在");
}
// 检查邮箱是否已存在
if (userRepository.existsByEmail(userDTO.getEmail())) {
throw new RuntimeException("邮箱已被注册");
}
// 创建新用户
User user = new User();
user.setUsername(userDTO.getUsername());
user.setEmail(userDTO.getEmail());
user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
User savedUser = userRepository.save(user);
// 生成JWT token
String token = jwtUtil.generateToken(savedUser.getUsername());
return new LoginResponse(token, savedUser.getUsername(), savedUser.getEmail());
}
public LoginResponse login(LoginRequest loginRequest) {
User user = userRepository.findByUsername(loginRequest.getUsername())
.orElseThrow(() -> new RuntimeException("用户不存在"));
if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
throw new RuntimeException("密码错误");
}
String token = jwtUtil.generateToken(user.getUsername());
return new LoginResponse(token, user.getUsername(), user.getEmail());
}
public List<UserDTO> getAllUsers() {
return userRepository.findAll().stream()
.map(user -> {
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setUsername(user.getUsername());
dto.setEmail(user.getEmail());
return dto;
})
.collect(Collectors.toList());
}
public UserDTO getUserById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setUsername(user.getUsername());
dto.setEmail(user.getEmail());
return dto;
}
public UserDTO updateUser(Long id, UserDTO userDTO) {
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
user.setUsername(userDTO.getUsername());
user.setEmail(userDTO.getEmail());
if (userDTO.getPassword() != null && !userDTO.getPassword().isEmpty()) {
user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
}
User updatedUser = userRepository.save(user);
UserDTO dto = new UserDTO();
dto.setId(updatedUser.getId());
dto.setUsername(updatedUser.getUsername());
dto.setEmail(updatedUser.getEmail());
return dto;
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
JWT工具类
JwtUtil.java
package com.example.usermanagement.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public Boolean validateToken(String token, String username) {
final String extractedUsername = extractUsername(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
}
过滤器
JwtRequestFilter.java
package com.example.usermanagement.filter;
import com.example.usermanagement.service.UserService;
import com.example.usermanagement.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
try {
username = jwtUtil.extractUsername(jwt);
} catch (Exception e) {
// Token无效
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
if (jwtUtil.validateToken(jwt, username)) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, null, null);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
chain.doFilter(request, response);
}
}
Controller
UserController.java
package com.example.usermanagement.controller;
import com.example.usermanagement.dto.*;
import com.example.usermanagement.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody UserDTO userDTO) {
try {
LoginResponse response = userService.register(userDTO);
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest loginRequest) {
try {
LoginResponse response = userService.login(loginRequest);
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
@GetMapping
public ResponseEntity<List<UserDTO>> getAllUsers() {
List<UserDTO> users = userService.getAllUsers();
return ResponseEntity.ok(users);
}
@GetMapping("/{id}")
public ResponseEntity<?> getUserById(@PathVariable Long id) {
try {
UserDTO user = userService.getUserById(id);
return ResponseEntity.ok(user);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("error", e.getMessage()));
}
}
@PutMapping("/{id}")
public ResponseEntity<?> updateUser(@PathVariable Long id, @Valid @RequestBody UserDTO userDTO) {
try {
UserDTO updatedUser = userService.updateUser(id, userDTO);
return ResponseEntity.ok(updatedUser);
} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
try {
userService.deleteUser(id);
Map<String, Boolean> response = new HashMap<>();
response.put("deleted", true);
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
}
}
}
Security配置
SecurityConfig.java
package com.example.usermanagement.config;
import com.example.usermanagement.filter.JwtRequestFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/users/register", "/api/users/login").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
主类
UserManagementApplication.java
package com.example.usermanagement;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = "com.example.usermanagement")
public class UserManagementApplication {
public static void main(String[] args) {
SpringApplication.run(UserManagementApplication.class, args);
}
}
前端(Vue.js)
创建Vue项目
npm install -g @vue/cli vue create frontend cd frontend npm install axios vue-router@3 vuex@3
主入口文件
main.js
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
import router from './router'
import store from './store'
axios.defaults.baseURL = 'http://localhost:8080/api'
// 添加token到请求头
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器
axios.interceptors.response.use(
response => response,
error => {
if (error.response.status === 401) {
localStorage.removeItem('token')
router.push('/login')
}
return Promise.reject(error)
}
)
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
路由配置
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue')
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue')
},
{
path: '/register',
name: 'Register',
component: () => import('../views/Register.vue')
},
{
path: '/users',
name: 'Users',
component: () => import('../views/UserList.vue')
},
{
path: '/profile',
name: 'Profile',
component: () => import('../views/Profile.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
const isAuthenticated = localStorage.getItem('token')
if (to.name !== 'Login' && to.name !== 'Register' && !isAuthenticated) {
next({ name: 'Login' })
} else {
next()
}
})
export default router
状态管理
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: null,
isAuthenticated: false
},
mutations: {
setUser(state, user) {
state.user = user
state.isAuthenticated = !!user
},
logout(state) {
state.user = null
state.isAuthenticated = false
localStorage.removeItem('token')
localStorage.removeItem('user')
}
},
actions: {
login({ commit }, userData) {
localStorage.setItem('token', userData.token)
localStorage.setItem('user', JSON.stringify({
username: userData.username,
email: userData.email
}))
commit('setUser', {
username: userData.username,
email: userData.email
})
},
logout({ commit }) {
commit('logout')
},
initUser({ commit }) {
const user = localStorage.getItem('user')
if (user) {
commit('setUser', JSON.parse(user))
}
}
},
getters: {
isAuthenticated: state => state.isAuthenticated,
currentUser: state => state.user
}
})
视图组件
views/Login.vue
<template>
<div class="container">
<div class="form-wrapper">
<h2>登录</h2>
<form @submit.prevent="handleLogin">
<div class="form-group">
<label>用户名</label>
<input
type="text"
v-model="loginForm.username"
required
>
</div>
<div class="form-group">
<label>密码</label>
<input
type="password"
v-model="loginForm.password"
required
>
</div>
<button type="submit" :disabled="loading">
{{ loading ? '登录中...' : '登录' }}
</button>
<p class="error" v-if="error">{{ error }}</p>
<p>
还没有账号?
<router-link to="/register">注册</router-link>
</p>
</form>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'Login',
data() {
return {
loginForm: {
username: '',
password: ''
},
loading: false,
error: ''
}
},
methods: {
async handleLogin() {
this.loading = true
this.error = ''
try {
const response = await axios.post('/users/login', this.loginForm)
this.$store.dispatch('login', response.data)
this.$router.push('/')
this.$message.success('登录成功')
} catch (error) {
this.error = error.response?.data?.error || '登录失败'
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
.container {
max-width: 400px;
margin: 50px auto;
padding: 20px;
}
.form-wrapper {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
button {
width: 100%;
padding: 10px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.error {
color: red;
text-align: center;
margin-top: 10px;
}
p {
text-align: center;
margin-top: 20px;
}
a {
color: #42b983;
text-decoration: none;
}
</style>
views/UserList.vue
<template>
<div class="container">
<h2>用户列表</h2>
<button @click="showAddModal = true" class="btn-add">
添加用户
</button>
<table class="user-table">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>
<button @click="editUser(user)" class="btn-edit">编辑</button>
<button @click="deleteUser(user.id)" class="btn-delete">删除</button>
</td>
</tr>
</tbody>
</table>
<!-- 添加/编辑模态框 -->
<div v-if="showAddModal || editingUser" class="modal">
<div class="modal-content">
<h3>{{ editingUser ? '编辑用户' : '添加用户' }}</h3>
<div class="form-group">
<label>用户名</label>
<input
type="text"
v-model="form.username"
@blur="validateUsername"
>
<span v-if="errors.username" class="error">{{ errors.username }}</span>
</div>
<div class="form-group">
<label>邮箱</label>
<input
type="email"
v-model="form.email"
@blur="validateEmail"
>
<span v-if="errors.email" class="error">{{ errors.email }}</span>
</div>
<div class="form-group">
<label>{{ editingUser ? '密码(留空保持不变)' : '密码' }}</label>
<input
type="password"
v-model="form.password"
@blur="validatePassword"
>
<span v-if="errors.password" class="error">{{ errors.password }}</span>
</div>
<div class="modal-actions">
<button @click="closeModal" class="btn-cancel">取消</button>
<button @click="saveUser" :disabled="!isValid" class="btn-save">
保存
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'UserList',
data() {
return {
users: [],
showAddModal: false,
editingUser: null,
form: {
username: '',
email: '',
password: ''
},
errors: {}
}
},
computed: {
isValid() {
return !Object.values(this.errors).some(error => error !== '')
}
},
created() {
this.fetchUsers()
},
methods: {
async fetchUsers() {
try {
const response = await axios.get('/users')
this.users = response.data
} catch (error) {
console.error('获取用户列表失败:', error)
}
},
validateUsername() {
if (!this.form.username) {
this.errors.username = '用户名不能为空'
} else if (this.form.username.length < 3) {
this.errors.username = '用户名至少3个字符'
} else {
this.errors.username = ''
}
},
validateEmail() {
if (!this.form.email) {
this.errors.email = '邮箱不能为空'
} else if (!this.isValidEmail(this.form.email)) {
this.errors.email = '邮箱格式不正确'
} else {
this.errors.email = ''
}
},
isValidEmail(email) {
return /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)
},
validatePassword() {
if (!this.editingUser && !this.form.password) {
this.errors.password = '密码不能为空'
} else if (this.form.password && this.form.password.length < 6) {
this.errors.password = '密码至少6个字符'
} else {
this.errors.password = ''
}
},
openAddModal() {
this.editingUser = null
this.showAddModal = true
this.form = {
username: '',
email: '',
password: ''
}
this.errors = {}
},
editUser(user) {
this.editingUser = user
this.showAddModal = true
this.form = {
username: user.username,
email: user.email,
password: ''
}
this.errors = {}
},
closeModal() {
this.showAddModal = false
this.editingUser = null
this.form = {
username: '',
email: '',
password: ''
}
this.errors = {}
},
async saveUser() {
if (!this.isValid) return
try {
if (this.editingUser) {
await axios.put(`/users/${this.editingUser.id}`, this.form)
this.$message.success('用户更新成功')
} else {
await axios.post('/users/register', this.form)
this.$message.success('用户添加成功')
}
this.closeModal()
this.fetchUsers()
} catch (error) {
this.$message.error(error.response?.data?.error || '保存失败')
}
},
async deleteUser(id) {
if (confirm('确定要删除该用户吗?')) {
try {
await axios.delete(`/users/${id}`)
this.$message.success('用户删除成功')
this.fetchUsers()
} catch (error) {
this.$message.error('删除失败')
}
}
}
}
}
</script>
<style scoped>
.container {
max-width: 1000px;
margin: 50px auto;
padding: 20px;
}
h2 {
margin-bottom: 20px;
}
.btn-add {
margin-bottom: 20px;
padding: 10px 20px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.user-table {
width: 100%;
border-collapse: collapse;
}
.user-table th,
.user-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.user-table th {
background-color: #f5f5f5;
}
.btn-edit,
.btn-delete {
padding: 5px 10px;
margin-right: 5px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-edit {
background-color: #2196f3;
color: white;
}
.btn-delete {
background-color: #f44336;
color: white;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 8px;
width: 400px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.btn-cancel,
.btn-save {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-cancel {
background-color: #f5f5f5;
}
.btn-save {
background-color: #42b983;
color: white;
}
.error {
color: red;
font-size: 12px;
}
</style>
主组件
App.vue
<template>
<div id="app">
<header>
<nav v-if="isAuthenticated">
<router-link to="/">首页</router-link>
<router-link to="/users">用户管理</router-link>
<router-link to="/profile">个人中心</router-link>
<button @click="handleLogout">退出登录</button>
</nav>
</header>
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
computed: {
isAuthenticated() {
return this.$store.getters.isAuthenticated
}
},
created() {
this.$store.dispatch('initUser')
},
methods: {
handleLogout() {
this.$store.dispatch('logout')
this.$router.push('/login')
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
header {
background-color: #42b983;
padding: 1rem;
}
nav {
display: flex;
justify-content: space-around;
align-items: center;
}
nav a {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
}
nav a:hover,
nav a.router-link-active {
background-color: #2c8f6e;
}
nav button {
background: none;
border: none;
color: white;
cursor: pointer;
padding: 0.5rem 1rem;
}
nav button:hover {
background-color: #2c8f6e;
border-radius: 4px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
</style>
组件注册
main.js 中添加消息组件
// 添加一个简单的消息提示组件
import Vue from 'vue'
Vue.prototype.$message = {
success(message) {
console.log(`Success: ${message}`)
alert(`Success: ${message}`)
},
error(message) {
console.error(`Error: ${message}`)
alert(`Error: ${message}`)
}
}
前端