本文目录导读:

我来为您提供一个完整的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}`)
}
}
前端
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播