<template>
  <el-card class="box-card" style="max-width: 900px; margin: 20px auto;">
    <h2>管理员反馈管理</h2>

    <el-table :data="feedbacks" style="width: 100%" v-loading="loading" :row-key="row => row.id" :expand-row-keys="expandedRows" @expand-change="handleExpand">
      <el-table-column type="expand">
        <template #default="{ row }">
          <el-form label-position="top" @submit.prevent>
            <el-form-item label="用户回复">
              <el-input
                  type="textarea"
                  rows="3"
                  v-model="replyMap[row.id]"
                  placeholder="填写回复"
              />
            </el-form-item>
            <el-form-item>
              <el-button type="primary" size="small" @click="submitReply(row.id)">提交回复</el-button>
            </el-form-item>
          </el-form>
        </template>
      </el-table-column>

      <el-table-column prop="id" label="ID" width="70" />
      <el-table-column prop="userId" label="用户ID" width="90" />
      <el-table-column prop="feedbackType" label="反馈类型" width="120" />
      <el-table-column prop="phone" label="手机号" width="140" />
      <el-table-column prop="content" label="内容" />
      <el-table-column prop="reply" label="回复" width="200" />
    </el-table>
  </el-card>
</template>

<script setup>

</script>

<style scoped>
.box-card {
  padding: 20px;
}
</style>
<template>
  <el-card class="box-card" style="max-width: 800px; margin: 20px auto;">
    <h2>用户反馈提交</h2>

    <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
      <el-form-item label="反馈类型" prop="feedbackType">
        <el-select v-model="form.feedbackType" placeholder="请选择反馈类型">
          <el-option label="系统故障" value="系统故障"></el-option>
          <el-option label="优化意见" value="优化意见"></el-option>
        </el-select>
      </el-form-item>

      <el-form-item label="手机号" prop="phone">
        <el-input v-model="form.phone" placeholder="请输入手机号"></el-input>
      </el-form-item>

      <el-form-item label="建议内容" prop="content">
        <el-input
            type="textarea"
            :rows="4"
            v-model="form.content"
            placeholder="请输入您的建议内容"
        ></el-input>
      </el-form-item>

      <el-form-item>
        <el-button type="primary" @click="submitFeedback">提交反馈</el-button>
        <el-button @click="resetForm">重置</el-button>
      </el-form-item>
    </el-form>

    <el-divider></el-divider>

    <h2>我的反馈列表</h2>
    <el-table :data="feedbacks" style="width: 100%" v-loading="loading">
      <el-table-column prop="feedbackType" label="反馈类型" width="120"></el-table-column>
      <el-table-column prop="phone" label="手机号" width="140"></el-table-column>
      <el-table-column prop="content" label="内容" />
      <el-table-column label="回复" width="200">
        <template #default="{ row }">
          <span v-if="row.reply">{{ row.reply }}</span>
          <span v-else style="color: #999">暂无回复</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="100">
        <template #default="{ row }">
          <el-button type="danger" size="mini" @click="deleteFeedback(row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </el-card>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'

const router = useRouter()

// 获取并解析userId，并做严格校验
const rawUserId = localStorage.getItem('userId')
const parsedUserId = parseInt(rawUserId)

if (!rawUserId || isNaN(parsedUserId) || parsedUserId <= 0) {
  console.error('无效userId:', rawUserId)
  ElMessage.error('请先登录')
  router.push('/login')
}

const userId = parsedUserId

const formRef = ref(null)
const form = ref({
  feedbackType: '',
  phone: '',
  content: ''
})

const rules = {
  feedbackType: [{ required: true, message: '请选择反馈类型', trigger: 'change' }],
  phone: [
    { required: true, message: '请输入手机号', trigger: 'blur' },
    { pattern: /^1[3-9]\d{9}$/, message: '请输入有效手机号', trigger: 'blur' }
  ],
  content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
}

const feedbacks = ref([])
const loading = ref(false)

async function fetchUserFeedback() {
  loading.value = true
  try {
    console.log('fetchUserFeedback userId:', userId)
    const resp = await axios.get('http://localhost:8080/feedback/user/list', {
      params: { userId }
    })
    if (resp.data) {
      feedbacks.value = resp.data
    }
  } catch (error) {
    console.error('获取反馈失败', error)
    ElMessage.error('获取反馈失败')
  } finally {
    loading.value = false
  }
}

async function submitFeedback() {
  formRef.value.validate(async valid => {
    if (!valid) return
    try {
      const payload = {
        feedbackType: form.value.feedbackType,
        phone: form.value.phone,
        content: form.value.content
      }
      console.log('submitFeedback payload, userId:', payload, userId)
      const resp = await axios.post('http://localhost:8080/feedback/user/add', payload, {
        params: { userId }
      })

      if (resp.data === '反馈提交成功') {
        ElMessage.success(resp.data)
        resetForm()
        fetchUserFeedback()
      } else {
        ElMessage.error(resp.data || '提交失败')
      }
    } catch (error) {
      console.error('提交失败', error)
      ElMessage.error('提交失败')
    }
  })
}

function resetForm() {
  form.value.feedbackType = ''
  form.value.phone = ''
  form.value.content = ''
  formRef.value.clearValidate()
}

async function deleteFeedback(id) {
  try {
    await ElMessageBox.confirm('确认删除该反馈吗？', '提示', { type: 'warning' })
    console.log('deleteFeedback id, userId:', id, userId)
    const resp = await axios.delete(`http://localhost:8080/feedback/user/delete/${id}`, {
      params: { userId }
    })
    if (resp.data === '删除成功') {
      ElMessage.success('删除成功')
      fetchUserFeedback()
    } else {
      ElMessage.error(resp.data || '删除失败')
    }
  } catch {
    // 用户取消，不处理
  }
}

onMounted(() => {
  fetchUserFeedback()
})
</script>

<style scoped>
.box-card {
  padding: 20px;
}
</style>
package com.example.springboot.controller;

import com.example.springboot.entity.Feedback;
import com.example.springboot.service.FeedbackService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/feedback")
public class FeedbackController {

    private final FeedbackService feedbackService;

    @Autowired
    public FeedbackController(FeedbackService feedbackService) {
        this.feedbackService = feedbackService;
    }

    /**
     * 用户提交反馈(通过前端传 userId，需注意安全)
     */
    @PostMapping("/user/add")
    public String addFeedback(@RequestBody Feedback feedback, @RequestParam Integer userId) {
        System.out.println("POST /user/add -> userId = " + userId);
        if (userId == null || userId <= 0) {
            return "请先登录";
        }
        feedback.setUserId(userId);
        int result = feedbackService.addFeedback(feedback);
        return result > 0 ? "反馈提交成功" : "反馈提交失败";
    }

    @GetMapping("/user/list")
    public List<Feedback> getUserFeedback(@RequestParam Integer userId) {
        System.out.println("GET /user/list -> userId = " + userId);
        if (userId == null || userId <= 0) {
            return null;
        }
        return feedbackService.getFeedbackByUser(userId);
    }

    @DeleteMapping("/user/delete/{id}")
    public String deleteUserFeedback(@PathVariable Integer id, @RequestParam Integer userId) {
        System.out.println("DELETE /user/delete/" + id + " -> userId = " + userId);
        if (userId == null || userId <= 0) {
            return "请先登录";
        }
        Feedback feedback = feedbackService.getFeedbackById(id);
        if (feedback == null || !feedback.getUserId().equals(userId)) {
            return "删除失败，可能不是您的反馈或不存在";
        }
        int result = feedbackService.deleteFeedback(id, userId);
        return result > 0 ? "删除成功" : "删除失败";
    }
}
package com.example.springboot.mapper;

import com.example.springboot.entity.Feedback;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface FeedbackMapper {

    // 插入反馈
    int insertFeedback(Feedback feedback);

    // 根据id删除反馈
    int deleteById(@Param("id") Integer id);

    // 查询指定用户自己的反馈列表
    List<Feedback> selectByUserId(@Param("userId") Integer userId);

    // 查询所有反馈（管理员用）
    List<Feedback> selectAll();

    // 根据id查询反馈详情
    Feedback selectById(@Param("id") Integer id);

    // 管理员回复更新反馈
    int updateReply(@Param("id") Integer id, @Param("reply") String reply);
}
package com.example.springboot.service;

import com.example.springboot.entity.Feedback;
import com.example.springboot.mapper.FeedbackMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class FeedbackService {

    private final FeedbackMapper feedbackMapper;

    public FeedbackService(FeedbackMapper feedbackMapper) {
        this.feedbackMapper = feedbackMapper;
    }

    /**
     * 用户添加反馈
     */
    @Transactional
    public int addFeedback(Feedback feedback) {
        return feedbackMapper.insertFeedback(feedback);
    }

    /**
     * 用户删除自己的反馈
     */
    @Transactional
    public int deleteFeedback(Integer id, Integer userId) {
        Feedback feedback = feedbackMapper.selectById(id);
        if(feedback == null) {
            return 0;
        }
        // 只有反馈属于该用户或管理员可以删除
        if(feedback.getUserId().equals(userId)) {
            return feedbackMapper.deleteById(id);
        }
        return 0;
    }

    /**
     * 查询指定用户的反馈
     */
    public List<Feedback> getFeedbackByUser(Integer userId) {
        return feedbackMapper.selectByUserId(userId);
    }

    /**
     * 管理员查询所有反馈
     */
    public List<Feedback> getAllFeedback() {
        return feedbackMapper.selectAll();
    }

    /**
     * 管理员回复反馈
     */
    @Transactional
    public int replyFeedback(Integer id, String reply) {
        return feedbackMapper.updateReply(id, reply);
    }

    /**
     * 根据ID查询反馈详情
     */
    public Feedback getFeedbackById(Integer id) {
        return feedbackMapper.selectById(id);
    }
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.springboot.mapper.FeedbackMapper">

    <insert id="insertFeedback" parameterType="com.example.springboot.entity.Feedback" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO feedback (feedback_type, phone, content, user_id)
        VALUES (#{feedbackType}, #{phone}, #{content}, #{userId})
    </insert>

    <delete id="deleteById" parameterType="int">
        DELETE FROM feedback WHERE id = #{id}
    </delete>

    <select id="selectByUserId" resultType="com.example.springboot.entity.Feedback" parameterType="int">
        SELECT id, feedback_type, phone, content, reply, user_id, create_time, update_time
        FROM feedback
        WHERE user_id = #{userId}
        ORDER BY create_time DESC
    </select>

    <select id="selectAll" resultType="com.example.springboot.entity.Feedback">
        SELECT id, feedback_type, phone, content, reply, user_id, create_time, update_time
        FROM feedback
        ORDER BY create_time DESC
    </select>

    <select id="selectById" parameterType="int" resultType="com.example.springboot.entity.Feedback">
        SELECT id, feedback_type, phone, content, reply, user_id, create_time, update_time
        FROM feedback
        WHERE id = #{id}
    </select>

    <update id="updateReply" parameterType="map">
        UPDATE feedback SET reply = #{reply}, update_time = NOW() WHERE id = #{id}
    </update>

</mapper>
管理员只能编辑reply这部分，其他不能更改，帮忙adminfeedback的<script>