SpringBoot用户登录注册系统设计与实现

第一步 新建模块

第二步 引入依赖

第三步 项目代码

UserController类

java 复制代码
package com.linwu.controller;

import com.linwu.entity.User;
import com.linwu.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class UserController {

    @Autowired
    private UserRepository userRepository;

    // 默认管理员
    private final String adminUser = "tbb"; // ********************输入你自己的账户信息
    private final String adminPass = "123456";

    // 登录页面
    @GetMapping("/")
    public String showLoginPage() {
        return "login";
    }

    // 登录逻辑
    @PostMapping("/login")
    public String login(@RequestParam String username,
                        @RequestParam String password,
                        Model model) {

        // 管理员登录
        if (adminUser.equals(username) && adminPass.equals(password)) {
            model.addAttribute("username", username);  // ✅ 添加用户名到模型
            return "success";
        }

        // 普通用户登录
        User user = userRepository.findByUsername(username);
        if (user != null && user.getPassword().equals(password)) {
            model.addAttribute("username", user.getUsername()); // ✅ 添加用户名
            return "success";
        }

        model.addAttribute("error", "用户名或密码错误!");
        return "login";
    }


    // 注册页面
    @GetMapping("/register")
    public String showRegisterPage() {
        return "register";
    }

    // 注册逻辑
    @PostMapping("/register")
    public String register(@RequestParam String username,
                           @RequestParam String password,
                           Model model) {
        if (username.equals(adminUser)) {
            model.addAttribute("error", "管理员账号不可注册!");
            return "register";
        }
        if (userRepository.findByUsername(username) != null) {
            model.addAttribute("error", "该用户名已存在!");
            return "register";
        }
        User newUser = new User();
        newUser.setUsername(username);
        newUser.setPassword(password);
        userRepository.save(newUser);
        model.addAttribute("msg", "注册成功,请返回登录!");
        return "register";
    }
}

User类

java 复制代码
package com.linwu.entity;

import jakarta.persistence.*; // JPA 注解,用于定义实体类和数据库映射
import lombok.Data;          // Lombok 注解,自动生成 getter/setter/toString 等方法

/**
 * 用户实体类,对应数据库中的 users 表
 */
@Data // 自动生成 getter、setter、toString、equals、hashCode 等方法
@Entity // 标记这是一个 JPA 实体类,Spring Boot 会将其映射到数据库表
@Table(name = "users") // 指定对应的数据库表名为 "users"
public class User {

    @Id // 主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增策略,数据库自动生成主键
    private Long id;        // 用户ID,唯一标识

    @Column(nullable = false, unique = true) // 映射到数据库列,不能为空且唯一
    private String username; // 用户名

    @Column(nullable = false) // 映射到数据库列,不能为空
    private String password; // 用户密码
}

User类

java 复制代码
package com.linwu.repository;

import com.linwu.entity.User;           // 导入User实体类
import org.springframework.data.jpa.repository.JpaRepository; // 导入Spring Data JPA的Repository接口

/**
 * 用户仓库接口,用于操作数据库中的 users 表
 * JpaRepository 提供了常用的增删改查方法
 * 泛型 <User, Long> 表示操作的实体类是 User,主键类型是 Long
 */
public interface UserRepository extends JpaRepository<User, Long> {

    /**
     * 根据用户名查找用户
     * Spring Data JPA 会根据方法名自动生成对应的查询语句
     *
     * @param username 用户名
     * @return User 对象,如果找不到返回 null
     */
    User findByUsername(String username);
}

前端UserLogin.vue页面代码

html 复制代码
<template>
  <div class="login-container">
    <div class="login-background">
      <div class="cloud cloud-1"></div>
      <div class="cloud cloud-2"></div>
      <div class="cloud cloud-3"></div>
      <div class="sun"></div>
      <div class="bird bird-1"></div>
      <div class="bird bird-2"></div>
    </div>

    <div class="login-card animate__animated animate__fadeInUp">
      <div class="login-header">
        <div class="logo-wrapper">
          <div class="cartoon-avatar">
            <div class="avatar-face">
              <div class="eyes">
                <div class="eye left-eye"></div>
                <div class="eye right-eye"></div>
              </div>
              <div class="mouth"></div>
            </div>
          </div>
        </div>
        <h2>留言板</h2>
        <p class="welcome-text">{{ isLogin ? '欢迎回到快乐天地' : '加入我们的快乐大家庭' }}</p>
      </div>

      <!-- 登录表单 -->
      <el-form v-if="isLogin" ref="loginFormRef" :model="loginForm" :rules="loginRules" class="login-form"
        @submit.prevent="handleLogin">
        <el-form-item prop="username">
          <el-input v-model="loginForm.username" placeholder="请输入你的小名" size="large" clearable :prefix-icon="User" />
        </el-form-item>

        <el-form-item prop="password">
          <el-input v-model="loginForm.password" type="password" placeholder="请输入魔法密码" size="large" show-password
            :prefix-icon="Lock" />
        </el-form-item>

        <el-form-item prop="role">
          <el-select v-model="loginForm.role" placeholder="请选择你的身份" size="large" style="width: 100%">
            <el-option label="普通小伙伴" value="0">
              <span class="option-content">
                <el-icon>
                  <User />
                </el-icon>
                <span>普通小伙伴</span>
              </span>
            </el-option>
            <el-option label="管理员大大" value="1">
              <span class="option-content">
                <el-icon>
                  <Management />
                </el-icon>
                <span>管理员大大</span>
              </span>
            </el-option>
          </el-select>
        </el-form-item>

        <el-form-item>
          <el-button type="primary" size="large"  native-type="submit" :loading="loading" style="width: 100%"
            class="login-button">
            {{ loading ? '进入中...' : '进入乐园' }}
          </el-button>
        </el-form-item>
      </el-form>

      <!-- 注册表单 -->
      <el-form v-else ref="registerFormRef" :model="registerForm" :rules="registerRules" class="register-form"
        @submit.prevent="handleRegister">
        <el-form-item prop="username">
          <el-input v-model="registerForm.username" placeholder="给自己起个小名" size="large" clearable prefix-icon="User" />
        </el-form-item>

        <el-form-item prop="password">
          <el-input v-model="registerForm.password" type="password" placeholder="设置魔法密码" size="large" show-password
            prefix-icon="Lock" />
        </el-form-item>

        <el-form-item prop="confirmPassword">
          <el-input v-model="registerForm.confirmPassword" type="password" placeholder="再次确认密码" size="large"
            show-password prefix-icon="Lock" />
        </el-form-item>

        <el-form-item>
          <el-button type="primary" size="large" native-type="submit" :loading="loading" style="width: 100%"
            class="register-button">
            {{ loading ? '注册中...' : '成为小伙伴' }}
          </el-button>
        </el-form-item>
      </el-form>

      <!-- 错误提示 -->
      <el-alert v-if="errorMessage" :title="errorMessage" type="error" show-icon style="margin-top: 20px" closable
        class="error-alert" />

      <!-- 成功提示 -->
      <el-alert v-if="successMessage" :title="successMessage" type="success" show-icon style="margin-top: 20px" closable
        class="success-alert" />

      <!-- 切换链接 -->
      <div class="switch-form">
        <span v-if="isLogin">
          还没有加入我们?
          <el-button type="text" @click="switchToRegister">立即加入</el-button>
        </span>
        <span v-else>
          已经是小伙伴了?
          <el-button type="text" @click="switchToLogin">立即进入</el-button>
        </span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import type { FormInstance, FormRules } from 'element-plus'
import { User, Lock, Management, UserFilled, Message } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { loginUsingPost , registerUsingPost } from '../api/user'

interface LoginForm {
  username: string
  password: string
  role: number | undefined
}

interface RegisterForm {
  username: string
  password: string
  confirmPassword: string
  role: string
}

const router = useRouter()
const loginFormRef = ref<FormInstance>()
const registerFormRef = ref<FormInstance>()
const isLogin = ref(true)
const loading = ref(false)
const errorMessage = ref('')
const successMessage = ref('')

const loginForm = reactive<LoginForm>({
  username: '',
  password: '',
  role: undefined,
})

const registerForm = reactive<RegisterForm>({
  username: '',
  password: '',
  confirmPassword: '',
  role: '1',
})

const loginRules = reactive<FormRules>({
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 2, max: 20, message: '长度应在 2 到 20 个字符之间', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 3, max: 20, message: '长度应在 3 到 20 个字符之间', trigger: 'blur' }
  ],
  role: [
    { required: true, message: '请选择用户类型', trigger: 'blur' }
  ]
})

const registerRules = reactive<FormRules>({
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 2, max: 20, message: '长度应在 2 到 20 个字符之间', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 3, max: 20, message: '长度应在 3 到 20 个字符之间', trigger: 'blur' }
  ],
  confirmPassword: [
    { required: true, message: '请确认密码', trigger: 'blur' },
    {
      validator: (rule, value, callback) => {
        if (value !== registerForm.password) {
          callback(new Error('两次输入的密码不一致'))
        } else {
          callback()
        }
      },
      trigger: 'blur'
    }
  ]
})

const switchToRegister = () => {
  isLogin.value = false
  errorMessage.value = ''
  successMessage.value = ''
}

const switchToLogin = () => {
  isLogin.value = true
  errorMessage.value = ''
  successMessage.value = ''
}

const handleLogin = async () => {
  if (!validateLogin()) {
    return
  }
  
  const res = await loginUsingPost(loginForm)
  if (res.code === 0 && res.data) {
    successMessage.value = res.message
    switchToLogin()
    ElMessage.success('登录成功')
    router.push('/main')
  } else {
    ElMessage.error('登录失败')
  }
}

const handleRegister = async () => {
  if (!validateRegister()) {
    return
  }

  const res = await registerUsingPost(registerForm)
  if (res.code === 0 && res.data) {
    successMessage.value = res.message
    switchToRegister()
    ElMessage.success('注册成功')
    isLogin.value = true
  } else {
    ElMessage.error('注册失败')
  }
}

const validateLogin = (): boolean => {
  if (!loginForm.username || !loginForm.password) {
    ElMessage.error('用户名和密码不能为空')
    return false
  }
  return true
}

const validateRegister = (): boolean => { 
  if (!registerForm.username || !registerForm.password || !registerForm.confirmPassword) {
    ElMessage.error('所有字段均不能为空')
    return false
  }
  if (registerForm.password !== registerForm.confirmPassword) {
    ElMessage.error('两次输入的密码不一致')
    return false
  }
  return true
}

</script>

<style scoped>
@import url('https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css');

.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  background: linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%);
  padding: 20px;
  position: relative;
  overflow: hidden;
}

.login-background {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
}

.cloud {
  position: absolute;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 50%;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
}

.cloud-1 {
  width: 120px;
  height: 40px;
  top: 15%;
  left: 10%;
  animation: float 8s ease-in-out infinite;
}

.cloud-1::before {
  content: '';
  position: absolute;
  width: 60px;
  height: 60px;
  top: -30px;
  left: 10px;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 50%;
}

.cloud-1::after {
  content: '';
  position: absolute;
  width: 50px;
  height: 50px;
  top: -20px;
  right: 15px;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 50%;
}

.cloud-2 {
  width: 100px;
  height: 35px;
  top: 25%;
  right: 15%;
  animation: float 10s ease-in-out infinite;
}

.cloud-2::before {
  content: '';
  position: absolute;
  width: 50px;
  height: 50px;
  top: -25px;
  left: 5px;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 50%;
}

.cloud-2::after {
  content: '';
  position: absolute;
  width: 40px;
  height: 40px;
  top: -15px;
  right: 10px;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 50%;
}

.cloud-3 {
  width: 140px;
  height: 45px;
  bottom: 20%;
  left: 20%;
  animation: float 12s ease-in-out infinite;
}

.cloud-3::before {
  content: '';
  position: absolute;
  width: 70px;
  height: 70px;
  top: -35px;
  left: 15px;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 50%;
}

.cloud-3::after {
  content: '';
  position: absolute;
  width: 60px;
  height: 60px;
  top: -25px;
  right: 20px;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 50%;
}

.sun {
  position: absolute;
  width: 80px;
  height: 80px;
  top: 10%;
  right: 10%;
  background: #FFD166;
  border-radius: 50%;
  box-shadow: 0 0 40px #FFD166;
  animation: rotate 20s linear infinite;
}

.sun::before,
.sun::after {
  content: '';
  position: absolute;
  background: #FFD166;
  border-radius: 50%;
}

.sun::before {
  width: 20px;
  height: 20px;
  top: -30px;
  left: 30px;
}

.sun::after {
  width: 15px;
  height: 15px;
  bottom: -25px;
  right: 25px;
}

.bird {
  position: absolute;
  width: 30px;
  height: 15px;
  background: #6A994E;
  border-radius: 50% 50% 0 0;
  animation: fly 20s linear infinite;
}

.bird::before,
.bird::after {
  content: '';
  position: absolute;
  background: #6A994E;
  border-radius: 50%;
}

.bird::before {
  width: 10px;
  height: 10px;
  top: -5px;
  left: 5px;
}

.bird::after {
  width: 5px;
  height: 5px;
  top: -2px;
  left: 15px;
}

.bird-1 {
  top: 30%;
  left: -50px;
  animation-delay: 0s;
}

.bird-2 {
  top: 40%;
  left: -50px;
  animation-delay: 5s;
}

@keyframes float {
  0% {
    transform: translateY(0px);
  }
  50% {
    transform: translateY(-20px);
  }
  100% {
    transform: translateY(0px);
  }
}

@keyframes rotate {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

@keyframes fly {
  0% {
    transform: translateX(0) translateY(0);
  }
  100% {
    transform: translateX(2000px) translateY(100px);
  }
}

.login-card {
  background: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(5px);
  border-radius: 25px;
  box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
  padding: 40px;
  width: 100%;
  max-width: 450px;
  transition: transform 0.3s ease;
  position: relative;
  z-index: 1;
  border: 3px solid #A7C957;
}

.login-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}

.login-header {
  text-align: center;
  margin-bottom: 30px;
}

.logo-wrapper {
  display: flex;
  justify-content: center;
  margin-bottom: 15px;
}

.cartoon-avatar {
  width: 100px;
  height: 100px;
  position: relative;
  margin: 0 auto;
}

.avatar-face {
  width: 100px;
  height: 100px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 50%;
  position: relative;
  border: 4px solid #fff;
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}

.eyes {
  display: flex;
  justify-content: space-around;
  padding-top: 30px;
}

.eye {
  width: 15px;
  height: 15px;
  background: #fff;
  border-radius: 50%;
  position: relative;
  overflow: hidden;
}

.eye::before {
  content: '';
  position: absolute;
  width: 7px;
  height: 7px;
  background: #333;
  border-radius: 50%;
  top: 4px;
  left: 4px;
  animation: blink 5s infinite;
}

.mouth {
  width: 30px;
  height: 15px;
  background: #fff;
  border-radius: 0 0 15px 15px;
  margin: 15px auto 0;
  position: relative;
}

.mouth::before {
  content: '';
  position: absolute;
  width: 20px;
  height: 10px;
  background: #FF6B6B;
  border-radius: 0 0 10px 10px;
  bottom: 0;
  left: 5px;
}

@keyframes blink {
  0%, 45%, 55%, 100% {
    height: 7px;
  }
  50% {
    height: 2px;
  }
}

.login-header h2 {
  color: #333;
  font-size: 28px;
  margin-bottom: 10px;
  font-weight: 700;
  font-family: 'Comic Sans MS', cursive, sans-serif;
}

.welcome-text {
  color: #666;
  font-size: 16px;
  margin: 0;
  font-family: 'Comic Sans MS', cursive, sans-serif;
}

.login-form :deep(.el-form-item),
.register-form :deep(.el-form-item) {
  margin-bottom: 24px;
}

.login-form :deep(.el-input__wrapper),
.register-form :deep(.el-input__wrapper) {
  border-radius: 50px;
  background-color: #f0f8ff;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
  transition: all 0.3s ease;
  border: 2px solid #d1e8ff;
}

.login-form :deep(.el-input__wrapper:hover),
.register-form :deep(.el-input__wrapper:hover) {
  box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
  border-color: #a1c4fd;
}

.login-form :deep(.el-input__wrapper.is-focus),
.register-form :deep(.el-input__wrapper.is-focus) {
  box-shadow: 0 6px 15px rgba(102, 126, 234, 0.3);
  border-color: #667eea;
}

.login-form :deep(.el-select .el-input__wrapper),
.register-form :deep(.el-select .el-input__wrapper) {
  background-color: #f0f8ff;
}

.login-button,
.register-button {
  margin-top: 10px;
  border-radius: 50px;
  font-weight: 700;
  letter-spacing: 1px;
  height: 50px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border: none;
  box-shadow: 0 6px 15px rgba(102, 126, 234, 0.4);
  transition: all 0.3s ease;
  font-family: 'Comic Sans MS', cursive, sans-serif;
  font-size: 16px;
}

.login-button:hover,
.register-button:hover {
  box-shadow: 0 8px 20px rgba(102, 126, 234, 0.6);
  transform: translateY(-3px);
}

.option-content {
  display: flex;
  align-items: center;
  gap: 8px;
}

.switch-form {
  text-align: center;
  margin-top: 20px;
  color: #666;
  font-family: 'Comic Sans MS', cursive, sans-serif;
}

.switch-form .el-button {
  font-weight: 700;
  font-size: 16px;
  color: #667eea;
}

.switch-form .el-button:hover {
  color: #764ba2;
}

.error-alert :deep(.el-alert__content),
.success-alert :deep(.el-alert__content) {
  width: 100%;
}

@media (max-width: 480px) {
  .login-card {
    padding: 30px 20px;
  }

  .login-header h2 {
    font-size: 24px;
  }

  .login-form :deep(.el-form-item),
  .register-form :deep(.el-form-item) {
    margin-bottom: 20px;
  }
  
  .avatar-face {
    width: 80px;
    height: 80px;
  }
  
  .cartoon-avatar {
    width: 80px;
    height: 80px;
  }
  
  .eyes {
    padding-top: 25px;
  }
}
</style>

前端Main.vue

html 复制代码
<template>
  <div class="message-board-container">
    <!-- 第一部分:搜索留言 -->
    <el-card class="search-card" shadow="hover">
      <div class="card-header">
        <el-icon>
          <Search />
        </el-icon>
        <span class="header-title">搜索留言</span>
      </div>
      <el-form :model="searchParams" class="search-form">
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item label="用户名">
              <el-input v-model="searchParams.username" placeholder="请输入用户名" clearable size="small">
                <template #prefix>
                  <el-icon><User /></el-icon>
                </template>
              </el-input>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="时间范围">
              <el-date-picker 
                v-model="dateRange" 
                type="daterange" 
                value-format="YYYY-MM-DD" 
                range-separator="至" 
                start-placeholder="开始日期"
                end-placeholder="结束日期" 
                size="small"
                class="date-picker"
              />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="内容">
              <el-input v-model="searchParams.content" placeholder="请输入搜索内容" clearable size="small">
                <template #prefix>
                  <el-icon><Document /></el-icon>
                </template>
              </el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24" class="search-buttons">
            <el-button type="primary" size="small" @click="handleSearch" class="action-button">
              <el-icon>
                <Search />
              </el-icon>搜索
            </el-button>
            <el-button size="small" @click="resetSearch" class="action-button">
              <el-icon>
                <Refresh />
              </el-icon>重置
            </el-button>
          </el-col>
        </el-row>
      </el-form>
    </el-card>

    <!-- 第二部分:添加留言 -->
    <el-card class="add-message-card" shadow="hover">
      <div class="card-header">
        <el-icon>
          <Edit />
        </el-icon>
        <span class="header-title">添加留言</span>
      </div>
      <el-form :model="MessagesAddDTO" label-width="0px">
        <el-form-item>
          <el-input 
            type="textarea" 
            v-model="MessagesAddDTO.content" 
            :rows="4" 
            placeholder="分享你的想法..." 
            maxlength="500"
            show-word-limit 
            class="message-textarea"
          >
          </el-input>
        </el-form-item>
        <div class="submit-section">
          <el-button type="primary" @click="submitMessage" size="small" class="submit-button">
            <el-icon>
              <Position />
            </el-icon>
            发布留言
          </el-button>
        </div>
      </el-form>
    </el-card>

    <!-- 第三部分:留言展示 -->
    <div class="messages-section">
      <div class="section-header">
        <h3>
          <el-icon>
            <ChatDotRound />
          </el-icon> 
          留言列表
        </h3>
        <div class="section-info">
          共 {{ dataList.length }} 条留言
        </div>
      </div>

      <transition-group name="message-list" tag="div" class="messages-container">
        <el-card v-for="message in dataList" :key="message.id" class="message-card" shadow="hover">
          <div class="message-header">
            <div class="user-info">
              <el-avatar :size="40" class="user-avatar">{{ message.username.charAt(0).toUpperCase() }}</el-avatar>
              <div class="user-details">
                <div class="username">{{ message.username }}</div>
                <div class="time">{{ dayjs(message.createdAt).format('YYYY-MM-DD HH:mm') }}</div>
              </div>
            </div>
            <div class="message-actions-header">
              <el-tag size="mini" type="info" class="message-id">#{{ message.id }}</el-tag>
              <!-- 管理员操作按钮 -->
              <div v-if="isAdmin === '1'" class="admin-actions">
                <el-button type="text" size="mini" @click="editMessage(message)" class="action-button-text">
                  <el-icon>
                    <Edit />
                  </el-icon>编辑
                </el-button>
                <el-button type="text" size="mini" @click="deleteDialogVisible = true; currentDeleteMessageId = message.id" class="action-button-text">
                  <el-icon>
                    <Delete />
                  </el-icon>删除
                </el-button>
              </div>
            </div>
          </div>

          <!-- 编辑留言表单 -->
          <div v-if="editingMessageId === message.id" class="edit-message-form">
            <el-input 
              type="textarea" 
              :rows="3" 
              v-model="editForm.content" 
              maxlength="500" 
              show-word-limit
              class="edit-textarea"
            >
            </el-input>
            <div class="edit-form-actions">
              <el-button type="primary" size="mini" @click="saveEditMessage" class="save-button">保存</el-button>
              <el-button size="mini" @click="cancelEdit" class="cancel-button">取消</el-button>
            </div>
          </div>

          <!-- 显示留言内容(非编辑状态) -->
          <div v-else class="message-content">
            {{ message.content }}
          </div>

          <div class="message-actions">
            <el-button type="text" @click="forwardMessage(message)" :class="{ 'forwarded': message.isForwarded }" class="action-button-text">
              <el-icon>
                <Share />
              </el-icon>
              转发
              <span class="count" v-if="message.forwardCount > 0">{{ message.forwardCount }}</span>
            </el-button>

            <el-button type="text" @click="toggleComments(message)" class="action-button-text comment-toggle">
              <el-icon>
                <ChatLineSquare />
              </el-icon>
              评论
              <span class="count" v-if="message.comments && message.comments.length > 0">{{ message.comments.length }}</span>
            </el-button>
          </div>

          <!-- 评论区域 -->
          <div class="comments-section" v-show="message.showComments">
            <div class="comments-list">
              <transition-group name="comment-list" tag="div">
                <div 
                  v-for="(comment, index) in message.comments" 
                  :key="comment.id" 
                  class="comment-item"
                  :class="{ 'highlight': index === message.comments.length - 1 && message.highlightNewComment }"
                >
                  <div class="comment-header">
                    <el-avatar :size="24" class="comment-avatar">{{ comment.username.charAt(0).toUpperCase() }}</el-avatar>
                    <div class="comment-user-info">
                      <span class="comment-user">{{ comment.username }}</span>
                      <span class="comment-time">{{ dayjs(comment.createdAt).format('MM-DD HH:mm') }}</span>
                    </div>
                  </div>
                  <div class="comment-content">
                    {{ comment.content }}
                  </div>
                </div>
              </transition-group>

              <div v-if="!message.comments || message.comments.length === 0" class="no-comments">
                <el-icon>
                  <ChatLineRound />
                </el-icon>
                <p>暂无评论,快来抢沙发吧!</p>
              </div>
            </div>

            <!-- 添加评论 -->
            <div class="add-comment">
              <el-input 
                v-model="message.newComment" 
                placeholder="写下你的评论..." 
                class="comment-input" 
                size="small"
                @keyup.enter="addComment(message)"
              >
                <template #append>
                  <el-button type="primary" size="small" @click="addComment(message)" class="comment-button">发表</el-button>
                </template>
              </el-input>
            </div>
          </div>
        </el-card>
      </transition-group>

      <!-- 空状态 -->
      <div v-if="dataList && dataList.length === 0" class="empty-state">
        <el-empty description="暂无匹配的留言" :image-size="100">
          <el-button type="primary" size="small" @click="resetSearch" class="reset-button">
            <el-icon>
              <Refresh />
            </el-icon>重置搜索
          </el-button>
        </el-empty>
      </div>
    </div>

    <!-- 删除确认对话框 -->
    <el-dialog v-model="deleteDialogVisible" title="确认删除" width="400px" custom-class="custom-dialog">
      <div class="dialog-content">
        <div class="dialog-icon">
          <el-icon class="warning-icon">
            <Warning />
          </el-icon>
        </div>
        <p class="dialog-text">确定要删除这条留言吗?此操作不可恢复。</p>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="deleteDialogVisible = false" class="cancel-dialog-button">取 消</el-button>
          <el-button type="danger" @click="confirmDelete" :loading="deleteLoading" class="confirm-dialog-button">确 定</el-button>
        </span>
      </template>
    </el-dialog>

    <!-- 转发对话框 -->
    <el-dialog title="转发留言" v-model="forwardDialogVisible" width="500px" class="forward-dialog" custom-class="custom-dialog">
      <el-form>
        <el-form-item label="选择用户">
          <el-select 
            v-model="selectedUserIds" 
            multiple 
            filterable 
            placeholder="请选择要转发的用户" 
            style="width: 100%"
            class="user-select"
          >
            <el-option 
              v-for="user in UserListDTO" 
              :key="user.id" 
              :label="user.username" 
              :value="user.id"
              class="user-option"
            >
              <el-avatar :size="20" class="option-avatar">{{ user.username.charAt(0).toUpperCase() }}</el-avatar>
              <span>{{ user.username }}</span>
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="附加消息">
          <el-input 
            v-model="forwardMessageText" 
            type="textarea" 
            :rows="3" 
            placeholder="请输入附加消息(可选)"
            class="forward-textarea"
          >
          </el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="forwardDialogVisible = false" class="cancel-dialog-button">取 消</el-button>
          <el-button type="primary" @click="confirmForward" :loading="forwardLoading" class="confirm-dialog-button">确 定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import {
  getMessages, addMessages, getUserPerson, updateMessage, deleteMessage, getUsersList
} from '../api/user';
import { reactive, Ref, ref, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import dayjs from 'dayjs';
import { User, Document } from '@element-plus/icons-vue';

interface MessageQueryDTO {
  username?: string;
  content?: string;
}

interface CommentItem {
  id: string;
  content: string;
  userId: string;
  username: string;
  createdAt: string;
}

interface DataItem {
  id: string;
  content: string;
  userId: string;
  parentId?: string;
  username: string;
  comments: CommentItem[];
  isForwarded?: boolean;
  forwardCount?: number;
  showComments?: boolean;
  newComment?: string;
  highlightNewComment?: boolean;
  createdAt: string;
  orignalMessageId?: string;
}

interface MessagesAddDTO {
  content: string;
  userId: string;
}

interface UserInfo {
  id: string;
  username: string;
  role: string;
}

interface UserItem {
  id: string;
  username: string;
}

interface MessageUpdateDTO {
  id?: string;
  content?: string;
}

const MessageUpdateDTO = reactive<MessageUpdateDTO>({
  id: '',
  content: ''
});

const MessagesAddDTO = reactive<MessagesAddDTO>({
  content: '',
  userId: '',
});

const searchParams = reactive<MessageQueryDTO>({
  username: '',
  content: ''
});

const dateRange = ref<[Date, Date] | undefined>(undefined);

const dataList: Ref<DataItem[]> = ref([]);
const UserLoginInfo: Ref<UserInfo | null> = ref(null);
const deleteDialogVisible = ref(false);
const deleteLoading = ref(false);
const currentDeleteMessageId = ref<string | null>(null);
const editingMessageId = ref<string | null>(null);
const editForm = reactive({ content: '' });
const isAdmin = ref();

// 转发相关数据
const forwardDialogVisible = ref(false);
const selectedUserIds = ref<string[]>([]);
const forwardMessageText = ref('');
const currentForwardMessage = ref<DataItem | null>(null);
const userList = ref<UserItem[]>([]);
const forwardLoading = ref(false);

// 获取数据
async function fetchData(params?: MessageQueryDTO & { startTime?: string, endTime?: string }) {
  try {
    const queryParams = params || { ...searchParams };

    // 处理时间范围参数
    if (dateRange.value && dateRange.value.length === 2) {
      queryParams.startTime = dateRange.value[0] + '';
      queryParams.endTime = dateRange.value[1] + '';
    }

    const res = await getMessages(queryParams);

    if (!res || !res.data) {
      ElMessage.error('接口响应格式异常');
      return;
    }

    if (res.code === 0 && res.data) {
      // 初始化评论和其他属性
      dataList.value = res.data.map(item => ({
        ...item,
        comments: item.comments ? (Array.isArray(item.comments) ? item.comments : []) : [],
        showComments: false,
        newComment: '',
        isForwarded: item.isForwarded || false,
        forwardCount: item.forwardCount || 0
      })) || [];
    } else {
      ElMessage.error('获取数据失败');
    }
  } catch (error) {
    ElMessage.error('请求失败,请检查网络');
    console.error('请求错误:', error);
  }
}

const handleSearch = () => {
  fetchData();
};

const resetSearch = () => {
  searchParams.username = '';
  searchParams.content = '';
  dateRange.value = undefined;
  fetchData();
};

const addComment = async (message: DataItem) => {
  if (!message.newComment?.trim()) {
    ElMessage.warning('请输入评论内容');
    return;
  }

  // 创建新评论对象
  const newComment: CommentItem = {
    id: Date.now().toString(),
    content: message.newComment,
    userId: UserLoginInfo.value?.id || '',
    username: UserLoginInfo.value?.username || '匿名用户',
    createdAt: new Date().toISOString()
  };

  if (!message.comments) {
    message.comments = [];
  }
  
  message.comments.push(newComment);
  message.newComment = '';
  message.highlightNewComment = true;

  ElMessage.success('评论发表成功!');
};

const submitMessage = async () => {
  if (!MessagesAddDTO.content.trim()) {
    ElMessage.warning('请输入留言内容');
    return;
  }

  MessagesAddDTO.userId = UserLoginInfo.value?.id || '';

  try {
    const res = await addMessages(MessagesAddDTO);

    if (res.code === 0) {
      ElMessage.success('留言发布成功!');
      MessagesAddDTO.content = '';
      fetchData();
    } else {
      ElMessage.error('留言发布失败');
    }
  } catch (error) {
    ElMessage.error('请求失败,请检查网络');
  }
};

const forwardMessage = async (message: DataItem) => {
  // 检查用户是否已登录
  if (!UserLoginInfo.value) {
    ElMessage.warning('请先登录后再进行转发操作');
    return;
  }

  // 设置当前转发的消息
  currentForwardMessage.value = message;

  // 获取用户列表
  await getUsersList();

  // 显示转发对话框
  forwardDialogVisible.value = true;
};

// 确认转发
const confirmForward = async () => {
  if (!currentForwardMessage.value) {
    ElMessage.error('转发消息异常');
    return;
  }

  if (selectedUserIds.value.length === 0) {
    ElMessage.warning('请选择要转发的用户');
    return;
  }

  forwardLoading.value = true;

  try {
    // 模拟转发API调用
    await new Promise(resolve => setTimeout(resolve, 1000));

    // 模拟API成功响应
    const res = { code: 0, message: '转发成功' };

    if (res.code === 0) {
      ElMessage.success('留言转发成功!');
      // 更新本地状态
      if (currentForwardMessage.value) {
        currentForwardMessage.value.isForwarded = true;
        currentForwardMessage.value.forwardCount =
          (currentForwardMessage.value.forwardCount || 0) + selectedUserIds.value.length;
      }
      // 关闭对话框并重置数据
      forwardDialogVisible.value = false;
      selectedUserIds.value = [];
      forwardMessageText.value = '';
      currentForwardMessage.value = null;
    } else {
      ElMessage.error(res.message || '转发失败');
    }
  } catch (error) {
    ElMessage.error('转发请求失败,请检查网络');
    console.error('转发错误:', error);
  } finally {
    forwardLoading.value = false;
  }
};

const toggleComments = (message: DataItem) => {
  message.showComments = !message.showComments;
  if (message.showComments) {
    message.highlightNewComment = false;
  }
};

const getUserInfo = async () => {
  try {
    const res = await getUserPerson();
    if (res.code === 0 && res.data) {
      UserLoginInfo.value = res.data;
      isAdmin.value = res.data.role;
      
    }
  } catch (error) {
    ElMessage.error('获取用户信息失败');
  }
};

// 页面加载时获取数据
onMounted(() => {
  getUserInfo();
  fetchData();
  getUser();
});

// 编辑相关函数
const editMessage = (message: DataItem) => {
  editingMessageId.value = message.id;
  editForm.content = message.content;
};

const saveEditMessage = async () => {
  MessageUpdateDTO.id = editingMessageId.value
  MessageUpdateDTO.content = editForm.content;

  const res = await updateMessage(MessageUpdateDTO);
  if (res.code === 0) {
    ElMessage.success('留言编辑成功!');
    editingMessageId.value = null;
    fetchData();
  } else {
    ElMessage.error('留言编辑失败');
  }
};

const cancelEdit = () => {
  editingMessageId.value = null;
};

const MessageDeleteDTO = ref({
  id: ''
});

const confirmDelete = async () => {
  if (!currentDeleteMessageId.value) {
    ElMessage.error('删除消息异常');
    return;
  }

  deleteLoading.value = true;
  MessageDeleteDTO.value.id = currentDeleteMessageId.value;
  
  try {
    const res = await deleteMessage(MessageDeleteDTO.value);

    if (res.code === 0) {
      ElMessage.success('留言删除成功!');
      // 关闭对话框
      deleteDialogVisible.value = false;
      // 重置当前删除消息ID
      currentDeleteMessageId.value = null;
      // 重新获取数据
      fetchData();
    }
  } catch (error) {
    console.error('删除错误:', error);
    ElMessage.error('删除失败,请重试');
  } finally {
    deleteLoading.value = false;
  }
};

interface UserListDTO {
  id?: string;
  username?: string;
}

const UserListDTO = ref<UserListDTO[]>([]);

const getUser = async () => {
  const res = await getUsersList();
  UserListDTO.value = res.data;
};
</script>

<style scoped>
.message-board-container {
  max-width: 1000px;
  margin: 0 auto;
  padding: 30px;
  background: linear-gradient(135deg, #ffd1dc 0%, #c9e9ff 50%, #ffd700 100%);
  min-height: 100vh;
  box-sizing: border-box;
  font-family: 'Comic Sans MS', '幼圆', cursive, sans-serif;
}

.card-header {
  display: flex;
  align-items: center;
  padding: 20px;
  background: linear-gradient(90deg, #ffb6c1 0%, #87cefa 100%);
  border-radius: 20px 20px 0 0;
  border-bottom: 2px dashed #ff69b4;
}

.card-header .el-icon {
  margin-right: 10px;
  color: #ff4500;
  font-size: 24px;
  animation: bounce 2s infinite;
}

@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

.header-title {
  font-weight: 700;
  font-size: 20px;
  color: #8b008b;
  text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
}

.search-card,
.add-message-card {
  margin-bottom: 25px;
  border-radius: 20px;
  border: 3px solid #ff69b4;
  box-shadow: 0 8px 20px rgba(255, 105, 180, 0.3);
  transition: all 0.3s ease;
  background: linear-gradient(135deg, #fffafa 0%, #f0ffff 100%);
  color: #8b008b;
  overflow: hidden;
}

.search-card:hover,
.add-message-card:hover {
  box-shadow: 0 12px 28px rgba(255, 105, 180, 0.5);
  transform: translateY(-5px) rotate(1deg);
}

.search-form {
  padding: 20px;
}

.search-buttons {
  text-align: right;
  padding-top: 10px;
}

.message-textarea ::v-deep .el-textarea__inner {
  border-radius: 15px;
  padding: 18px;
  font-size: 16px;
  border: 2px dashed #ff69b4;
  transition: all 0.3s ease;
  background: linear-gradient(135deg, #fff8dc 0%, #ffe4e1 100%);
  color: #8b008b;
  min-height: 120px;
  font-family: 'Comic Sans MS', '幼圆', cursive, sans-serif;
}

.message-textarea ::v-deep .el-textarea__inner:focus {
  border-color: #ff4500;
  box-shadow: 0 0 0 3px rgba(255, 69, 0, 0.3);
}

.message-textarea ::v-deep .el-textarea__inner::placeholder {
  color: #da70d6;
}

.submit-section {
  text-align: right;
  margin-top: 20px;
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 25px;
  padding: 20px;
  background: linear-gradient(90deg, #ffb6c1 0%, #87cefa 100%);
  border-radius: 20px;
  box-shadow: 0 6px 16px rgba(255, 105, 180, 0.3);
  border: 2px dashed #ff69b4;
}

.section-header h3 {
  margin: 0;
  color: #8b008b;
  font-size: 22px;
  display: flex;
  align-items: center;
}

.section-header h3 .el-icon {
  margin-right: 10px;
  color: #ff4500;
  font-size: 26px;
}

.section-info {
  font-size: 16px;
  color: #8b008b;
  font-weight: bold;
}

.messages-container {
  display: flex;
  flex-direction: column;
  gap: 25px;
}

.message-card {
  border-radius: 20px;
  border: 3px solid #ff69b4;
  box-shadow: 0 6px 20px rgba(255, 105, 180, 0.3);
  transition: all 0.3s ease;
  background: linear-gradient(135deg, #fffafa 0%, #f0ffff 100%);
  color: #8b008b;
  overflow: hidden;
}

.message-card:hover {
  box-shadow: 0 10px 28px rgba(255, 105, 180, 0.5);
  transform: translateY(-5px) rotate(-1deg);
}

.message-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding: 20px 20px 0 20px;
}

.user-info {
  display: flex;
  align-items: center;
}

.user-avatar {
  background: linear-gradient(135deg, #ff69b4 0%, #ff4500 100%);
  color: white;
  font-weight: bold;
  margin-right: 15px;
  border: 2px solid white;
  box-shadow: 0 0 8px rgba(255, 105, 180, 0.7);
}

.user-details {
  display: flex;
  flex-direction: column;
}

.username {
  font-weight: 700;
  color: #8b008b;
  font-size: 18px;
  margin-bottom: 4px;
}

.time {
  font-size: 14px;
  color: #da70d6;
}

.message-actions-header {
  display: flex;
  align-items: center;
  gap: 15px;
}

.message-id {
  background-color: #ffb6c1;
  border-color: #ff69b4;
  color: #8b008b;
  border-radius: 20px;
}

.admin-actions {
  display: flex;
  gap: 10px;
}

.action-button-text {
  padding: 0;
  font-size: 16px;
  color: #da70d6;
  transition: all 0.3s ease;
  font-weight: bold;
}

.action-button-text:hover {
  color: #ff4500;
  background-color: transparent;
  transform: scale(1.1);
}

.action-button-text .el-icon {
  margin-right: 5px;
}

.edit-message-form {
  margin: 20px;
  padding: 20px;
  background: linear-gradient(135deg, #fff8dc 0%, #e0ffff 100%);
  border-radius: 15px;
  border: 2px dashed #ff69b4;
}

.edit-textarea ::v-deep .el-textarea__inner {
  background: linear-gradient(135deg, #fffafa 0%, #f0ffff 100%);
  border: 2px dashed #ff69b4;
  color: #8b008b;
  border-radius: 12px;
  font-family: 'Comic Sans MS', '幼圆', cursive, sans-serif;
}

.edit-form-actions {
  margin-top: 20px;
  text-align: right;
}

.save-button {
  background: linear-gradient(135deg, #ff69b4 0%, #ff4500 100%);
  border: none;
  margin-right: 10px;
  border-radius: 20px;
  font-weight: bold;
  color: white;
}

.cancel-button {
  background-color: #ffb6c1;
  border: 2px solid #ff69b4;
  color: #8b008b;
  border-radius: 20px;
  font-weight: bold;
}

.message-content {
  margin: 0 20px 20px;
  line-height: 1.8;
  font-size: 17px;
  color: #8b008b;
  white-space: pre-wrap;
  padding: 20px;
  background: linear-gradient(135deg, #fff8dc 0%, #e0ffff 100%);
  border-radius: 15px;
  border: 2px dashed #ff69b4;
}

.message-actions {
  display: flex;
  padding: 0 20px 20px;
  border-top: 2px dashed #ff69b4;
  padding-top: 20px;
  gap: 25px;
}

.message-actions .forwarded {
  color: #ff4500;
}

.count {
  margin-left: 5px;
  font-size: 14px;
  background-color: #ffb6c1;
  color: #8b008b;
  padding: 4px 10px;
  border-radius: 20px;
  font-weight: 700;
}

.comments-section {
  padding: 0 20px 20px;
}

.comments-list {
  max-height: 400px;
  overflow-y: auto;
  margin-bottom: 20px;
  padding: 15px;
  background: linear-gradient(135deg, #fff8dc 0%, #e0ffff 100%);
  border-radius: 15px;
  border: 2px dashed #ff69b4;
}

.comment-item {
  padding: 15px;
  border-radius: 15px;
  margin-bottom: 15px;
  background-color: #ffb6c1;
  box-shadow: 0 3px 8px rgba(255, 105, 180, 0.3);
  transition: all 0.3s ease;
}

.comment-item:hover {
  background-color: #ffaebc;
  transform: scale(1.02);
}

.comment-item.highlight {
  background-color: #ff69b4;
  border-left: 5px solid #ff4500;
}

.comment-header {
  display: flex;
  align-items: center;
  margin-bottom: 12px;
}

.comment-avatar {
  background: linear-gradient(135deg, #ff69b4 0%, #ff4500 100%);
  color: white;
  margin-right: 10px;
  border: 2px solid white;
  box-shadow: 0 0 6px rgba(255, 105, 180, 0.7);
}

.comment-user-info {
  display: flex;
  flex-direction: column;
}

.comment-user {
  font-weight: 700;
  color: #8b008b;
  font-size: 16px;
  margin-bottom: 2px;
}

.comment-time {
  font-size: 13px;
  color: #da70d6;
}

.comment-content {
  font-size: 16px;
  color: #8b008b;
  line-height: 1.6;
  padding-left: 34px;
}

.no-comments {
  text-align: center;
  padding: 30px 20px;
  color: #da70d6;
  font-size: 17px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.no-comments .el-icon {
  font-size: 40px;
  margin-bottom: 15px;
  color: #ff69b4;
}

.no-comments p {
  margin: 0;
}

.add-comment ::v-deep .el-input-group__append {
  background: linear-gradient(135deg, #ff69b4 0%, #ff4500 100%);
  border-color: #ff69b4;
  color: white;
  transition: all 0.3s ease;
  border-radius: 0 10px 10px 0;
  font-weight: bold;
}

.add-comment ::v-deep .el-input-group__append:hover {
  background: linear-gradient(135deg, #ff4500 0%, #ff6347 100%);
  transform: scale(1.05);
}

.add-comment ::v-deep .el-input__inner {
  border-radius: 10px 0 0 10px;
  font-size: 16px;
  background: linear-gradient(135deg, #fffafa 0%, #f0ffff 100%);
  border: 2px dashed #ff69b4;
  color: #8b008b;
  height: 45px;
  font-family: 'Comic Sans MS', '幼圆', cursive, sans-serif;
}

.add-comment ::v-deep .el-input__inner::placeholder {
  color: #da70d6;
}

.comment-button {
  height: 45px;
  font-weight: bold;
}

/* 对话框样式 */
.custom-dialog {
  border-radius: 25px;
  overflow: hidden;
  background: linear-gradient(135deg, #fffafa 0%, #f0ffff 100%);
  border: 3px solid #ff69b4;
  box-shadow: 0 15px 40px rgba(255, 105, 180, 0.5);
}

.custom-dialog ::v-deep .el-dialog__header {
  background: linear-gradient(90deg, #ffb6c1 0%, #87cefa 100%);
  color: #8b008b;
  border-bottom: 2px dashed #ff69b4;
  padding: 20px;
  border-radius: 25px 25px 0 0;
}

.custom-dialog ::v-deep .el-dialog__title {
  color: #8b008b;
  font-size: 20px;
  font-weight: 700;
}

.custom-dialog ::v-deep .el-dialog__headerbtn .el-dialog__close {
  color: #da70d6;
  font-size: 22px;
}

.custom-dialog ::v-deep .el-dialog__headerbtn .el-dialog__close:hover {
  color: #ff4500;
}

.custom-dialog ::v-deep .el-dialog__body {
  padding: 25px;
}

.dialog-content {
  text-align: center;
  padding: 20px 0;
}

.dialog-icon {
  display: flex;
  justify-content: center;
  margin-bottom: 20px;
}

.warning-icon {
  font-size: 60px;
  color: #ff4500;
  animation: pulse 1.5s infinite;
}

@keyframes pulse {
  0% { transform: scale(1); }
  50% { transform: scale(1.1); }
  100% { transform: scale(1); }
}

.dialog-text {
  font-size: 18px;
  color: #8b008b;
  margin: 0;
  line-height: 1.6;
  font-weight: bold;
}

.dialog-footer {
  text-align: right;
  padding: 20px;
  background: linear-gradient(90deg, #ffb6c1 0%, #87cefa 100%);
  border-top: 2px dashed #ff69b4;
}

.cancel-dialog-button {
  background-color: #ffb6c1;
  border: 2px solid #ff69b4;
  color: #8b008b;
  margin-right: 12px;
  padding: 10px 22px;
  border-radius: 20px;
  font-weight: bold;
}

.confirm-dialog-button {
  background: linear-gradient(135deg, #ff4500 0%, #ff6347 100%);
  border: none;
  padding: 10px 22px;
  border-radius: 20px;
  font-weight: bold;
  color: white;
}

/* 转发对话框样式 */
.forward-dialog .el-select {
  width: 100%;
}

.user-select ::v-deep .el-select__tags {
  background: linear-gradient(135deg, #fffafa 0%, #f0ffff 100%);
}

.user-select ::v-deep .el-select__tags-text {
  color: #8b008b;
  font-weight: bold;
}

.user-select ::v-deep .el-tag {
  background-color: #ffb6c1;
  border-color: #ff69b4;
  color: #8b008b;
  border-radius: 20px;
}

.user-option ::v-deep .el-select-dropdown__item.selected {
  color: #ff4500;
  font-weight: 700;
  background-color: #fff0f5;
}

.option-avatar {
  background: linear-gradient(135deg, #ff69b4 0%, #ff4500 100%);
  color: white;
  margin-right: 10px;
  border: 2px solid white;
  box-shadow: 0 0 6px rgba(255, 105, 180, 0.7);
}

.forward-textarea ::v-deep .el-textarea__inner {
  background: linear-gradient(135deg, #fffafa 0%, #f0ffff 100%);
  border: 2px dashed #ff69b4;
  color: #8b008b;
  border-radius: 12px;
  font-family: 'Comic Sans MS', '幼圆', cursive, sans-serif;
}

.forward-textarea ::v-deep .el-textarea__inner:focus {
  border-color: #ff4500;
  box-shadow: 0 0 0 3px rgba(255, 69, 0, 0.3);
}

/* 表单标签 */
::v-deep .el-form-item__label {
  color: #8b008b;
  font-weight: 700;
  padding-bottom: 8px;
  font-size: 16px;
}

/* 输入框 */
::v-deep .el-input__wrapper {
  background: linear-gradient(135deg, #fffafa 0%, #f0ffff 100%);
  box-shadow: 0 0 0 2px #ff69b4 inset;
  border-radius: 12px;
}

::v-deep .el-input__wrapper.is-focus {
  box-shadow: 0 0 0 3px #ff4500 inset;
}

::v-deep .el-input__inner {
  background: linear-gradient(135deg, #fffafa 0%, #f0ffff 100%);
  color: #8b008b;
  height: 45px;
  font-family: 'Comic Sans MS', '幼圆', cursive, sans-serif;
  font-weight: bold;
}

::v-deep .el-input__inner::placeholder {
  color: #da70d6;
}

.date-picker ::v-deep .el-range-input {
  background-color: transparent;
  color: #8b008b;
  font-weight: bold;
}

.date-picker ::v-deep .el-range-separator {
  color: #da70d6;
  font-weight: bold;
}

/* 按钮 */
.action-button {
  background: linear-gradient(135deg, #ff69b4 0%, #ff4500 100%);
  border: none;
  padding: 12px 22px;
  font-weight: 700;
  margin-left: 10px;
  transition: all 0.3s ease;
  border-radius: 20px;
  color: white;
  font-size: 16px;
}

.action-button:hover {
  transform: translateY(-3px) scale(1.05);
  box-shadow: 0 6px 15px rgba(255, 105, 180, 0.5);
}

.submit-button {
  background: linear-gradient(135deg, #ff69b4 0%, #ff4500 100%);
  border: none;
  padding: 14px 28px;
  font-weight: 700;
  font-size: 17px;
  transition: all 0.3s ease;
  border-radius: 25px;
  color: white;
}

.submit-button:hover {
  transform: translateY(-3px) scale(1.05);
  box-shadow: 0 6px 18px rgba(255, 105, 180, 0.5);
}

.reset-button {
  background: linear-gradient(135deg, #ff69b4 0%, #ff4500 100%);
  border: none;
  padding: 12px 22px;
  font-weight: 700;
  transition: all 0.3s ease;
  border-radius: 20px;
  color: white;
}

.reset-button:hover {
  transform: translateY(-3px) scale(1.05);
  box-shadow: 0 6px 15px rgba(255, 105, 180, 0.5);
}

::v-deep .el-button {
  background: linear-gradient(135deg, #ffb6c1 0%, #87cefa 100%);
  border: 2px solid #ff69b4;
  color: #8b008b;
  border-radius: 12px;
  transition: all 0.3s ease;
  font-weight: bold;
}

::v-deep .el-button:hover {
  background: linear-gradient(135deg, #ffaebc 0%, #7ac5cd 100%);
  border-color: #ff4500;
  color: #8b008b;
  transform: translateY(-2px);
}

::v-deep .el-button--primary {
  background: linear-gradient(135deg, #ff69b4 0%, #ff4500 100%);
  border: none;
  font-weight: 700;
  color: white;
}

::v-deep .el-button--primary:hover {
  background: linear-gradient(135deg, #ff4500 0%, #ff6347 100%);
  transform: translateY(-2px) scale(1.05);
  box-shadow: 0 6px 15px rgba(255, 105, 180, 0.5);
}

::v-deep .el-button--danger {
  background: linear-gradient(135deg, #ff4500 0%, #ff6347 100%);
  border: none;
  color: white;
}

::v-deep .el-button--danger:hover {
  background: linear-gradient(135deg, #ff6347 0%, #ff4500 100%);
  transform: translateY(-2px) scale(1.05);
  box-shadow: 0 6px 15px rgba(255, 69, 0, 0.5);
}

/* 空状态 */
.empty-state ::v-deep .el-empty {
  background: linear-gradient(135deg, #fffafa 0%, #f0ffff 100%);
  padding: 40px 0;
}

.empty-state ::v-deep .el-empty__description {
  color: #da70d6;
  font-size: 18px;
  margin-top: 20px;
  font-weight: bold;
}

/* 标签 */
::v-deep .el-tag {
  background-color: #ffb6c1;
  border-color: #ff69b4;
  color: #8b008b;
  border-radius: 20px;
  padding: 6px 14px;
  font-weight: bold;
}

/* 动画效果 */
.message-list-enter-active {
  transition: all 0.5s ease;
}

.message-list-leave-active {
  transition: all 0.5s ease;
}

.message-list-enter-from {
  opacity: 0;
  transform: translateY(30px) rotate(5deg);
}

.message-list-leave-to {
  opacity: 0;
  transform: translateX(50px) rotate(-5deg);
}

.comment-list-enter-active {
  transition: all 0.4s ease;
}

.comment-list-leave-active {
  transition: all 0.3s ease;
}

.comment-list-enter-from {
  opacity: 0;
  transform: translateX(20px) scale(0.9);
}

.comment-list-leave-to {
  opacity: 0;
  transform: translateX(-20px) scale(0.9);
}

/* 响应式设计 */
@media (max-width: 768px) {
  .message-board-container {
    padding: 15px;
  }

  .card-header {
    padding: 15px;
  }

  .search-form {
    padding: 15px;
  }

  .el-col {
    margin-bottom: 15px;
  }

  .search-buttons {
    text-align: center;
  }

  .section-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }

  .message-actions {
    flex-wrap: wrap;
    gap: 15px;
  }

  .message-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 15px;
  }

  .message-actions-header {
    margin-top: 0;
    align-self: flex-start;
    width: 100%;
    justify-content: space-between;
  }

  .admin-actions {
    gap: 15px;
  }

  .message-content {
    padding: 15px;
  }

  .message-header,
  .message-content,
  .message-actions,
  .comments-section {
    padding-left: 15px;
    padding-right: 15px;
  }

  .edit-message-form,
  .comments-list {
    padding: 12px;
  }

  .custom-dialog {
    width: 90% !important;
  }
}

@media (max-width: 480px) {
  .message-board-container {
    padding: 10px;
  }

  .card-header .el-icon,
  .section-header h3 .el-icon {
    font-size: 20px;
  }

  .header-title,
  .section-header h3 {
    font-size: 18px;
  }

  .message-textarea ::v-deep .el-textarea__inner {
    padding: 12px;
    font-size: 15px;
  }

  .user-details {
    gap: 2px;
  }

  .username {
    font-size: 16px;
  }

  .message-content {
    font-size: 16px;
  }

  .comment-user {
    font-size: 14px;
  }

  .comment-content {
    font-size: 15px;
  }
}
</style>

第四步 运行效果如图所示

案例17

相关推荐
沐浴露z17 小时前
【JVM】详解 运行时数据区
java·jvm
召摇17 小时前
在浏览器中无缝运行Go工具:WebAssembly实战指南
后端·面试·go
召摇17 小时前
Spring Security入门指南
后端·spring·面试
笃行35017 小时前
Ubuntu 22.04 服务器安装 KingbaseES 电科金仓数据库详细教程
后端
数据小馒头18 小时前
浅谈SQL审核(一):SQL审核实现方式与常见工具的选择
后端
云泽80818 小时前
C/C++内存管理详解:从基础原理到自定义内存池原理
java·c语言·c++
Code小翊18 小时前
堆的基础操作,C语言示例
java·数据结构·算法
高山上有一只小老虎18 小时前
idea中设置快捷键风格
java·ide·intellij-idea
JH307318 小时前
IDEA自带的Maven安装位置
java·maven·intellij-idea
武子康18 小时前
大数据-128 - Flink 并行度详解:从概念到最佳实践,一文读懂任务并行执行机制 代码示例与性能优化
大数据·后端·flink