索要资源相关的前端以及管理员
Change-Id: I53afd377ff81e25d48951f7656a1cef3c9b3840c
diff --git a/src/api/request.js b/src/api/request.js
new file mode 100644
index 0000000..26d57b9
--- /dev/null
+++ b/src/api/request.js
@@ -0,0 +1,123 @@
+import axios from 'axios';
+
+const BASE_URL = 'http://localhost:8080/request';
+
+// 创建求助帖(支持上传图片)
+export const createRequest = (formData) => {
+ return axios.post(`${BASE_URL}/create`, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ });
+};
+
+// 修改求助帖金额
+export const updateMoney = (requestid, money) => {
+ return axios.put(`${BASE_URL}/updateMoney/${requestid}`, null, {
+ params: { money },
+ });
+};
+
+// 根据名称批量更新被协助用户 ID
+export const updateLoaduserByName = (name, loaduser) => {
+ return axios.post(`${BASE_URL}/updateLoaduserByName`, null, {
+ params: { name, loaduser },
+ });
+};
+
+// ✅ 新增:根据 requestid 更新 torrentid
+export const updateTorrentid = (requestid, torrentid) => {
+ return axios.put(`${BASE_URL}/updateTorrentid/${requestid}`, null, {
+ params: { torrentid },
+ });
+};
+
+// ✅ 新增:根据 requestid 获取 torrentid
+export const getTorrentid = async (requestid) => {
+ try {
+ const response = await axios.get(`${BASE_URL}/getTorrentid/${requestid}`);
+ return response.data;
+ } catch (error) {
+ console.error('获取 torrentid 失败', error);
+ return null;
+ }
+};
+
+// 删除求助帖
+export const deleteRequest = (requestid) => {
+ return axios.delete(`${BASE_URL}/delete/${requestid}`);
+};
+
+// 根据名称查找求助帖
+export const findByName = async (name) => {
+ try {
+ const response = await axios.get(`${BASE_URL}/findByName`, {
+ params: { name },
+ });
+ return Array.isArray(response.data) ? response.data : [];
+ } catch (error) {
+ console.error('按名称查找求助帖失败', error);
+ return [];
+ }
+};
+
+// 根据发帖用户 ID 查找求助帖
+export const findByUserid = async (userid) => {
+ try {
+ const response = await axios.get(`${BASE_URL}/findByUserid`, {
+ params: { userid },
+ });
+ return Array.isArray(response.data) ? response.data : [];
+ } catch (error) {
+ console.error('按用户ID查找求助帖失败', error);
+ return [];
+ }
+};
+
+// 根据被协助用户 ID 查找求助帖
+export const findByLoaduser = async (loaduser) => {
+ try {
+ const response = await axios.get(`${BASE_URL}/findByLoaduser`, {
+ params: { loaduser },
+ });
+ return Array.isArray(response.data) ? response.data : [];
+ } catch (error) {
+ console.error('按被协助用户ID查找求助帖失败', error);
+ return [];
+ }
+};
+
+// 获取某名称的总金额
+export const getTotalMoneyByName = async (name) => {
+ try {
+ const response = await axios.get(`${BASE_URL}/totalMoneyByName`, {
+ params: { name },
+ });
+ return typeof response.data === 'number' ? response.data : 0;
+ } catch (error) {
+ console.error('获取总金额失败', error);
+ return 0;
+ }
+};
+
+// 获取所有求助帖
+export const getAllRequests = async () => {
+ try {
+ const response = await axios.get(`${BASE_URL}/all`);
+ return Array.isArray(response.data) ? response.data : [];
+ } catch (error) {
+ console.error('获取全部求助帖失败', error);
+ return [];
+ }
+};
+
+// ✅ 新增:根据 requestid 获取求助帖的部分信息(torrentid、money、loaduser)
+export const getInfoByRequestId = async (requestid) => {
+ try {
+ const response = await axios.get(`${BASE_URL}/info/${requestid}`);
+ return response.data || {};
+ } catch (error) {
+ console.error('获取求助帖信息失败', error);
+ return {};
+ }
+};
diff --git a/src/components/RequestAdminPanel.jsx b/src/components/RequestAdminPanel.jsx
new file mode 100644
index 0000000..ecf4ee1
--- /dev/null
+++ b/src/components/RequestAdminPanel.jsx
@@ -0,0 +1,228 @@
+import React, { useEffect, useState } from 'react';
+import {
+ Table,
+ Button,
+ Modal,
+ Image,
+ message,
+ Tag,
+ Space,
+ Spin,
+ Tooltip,
+} from 'antd';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import {
+ getAllRequests,
+ deleteRequest,
+} from '../api/request';
+import { useNavigate } from 'react-router-dom';
+
+const { confirm } = Modal;
+
+const RequestAdminPanel = () => {
+ const [requests, setRequests] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const navigate = useNavigate();
+
+ // 获取所有求助帖
+ const fetchRequests = async () => {
+ setLoading(true);
+ try {
+ const data = await getAllRequests();
+ setRequests(data);
+ } catch (error) {
+ message.error('获取求助帖失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchRequests();
+ }, []);
+
+ // 删除确认弹窗
+ const showDeleteConfirm = (requestid) => {
+ confirm({
+ title: '确认删除该求助帖吗?',
+ icon: <ExclamationCircleOutlined />,
+ okText: '删除',
+ okType: 'danger',
+ cancelText: '取消',
+ onOk() {
+ handleDelete(requestid);
+ },
+ });
+ };
+
+ // 删除求助帖
+ const handleDelete = async (requestid) => {
+ try {
+ const success = await deleteRequest(requestid);
+ if (success) {
+ message.success('删除成功');
+ fetchRequests();
+ } else {
+ message.error('删除失败');
+ }
+ } catch {
+ message.error('删除请求失败');
+ }
+ };
+
+ // 处理按钮示例
+ const handleProcess = (request) => {
+ navigate(`/process/${request.requestid}`, {
+ state: {
+ requestid: request.requestid,
+ helpedid: request.userid,
+ loaduser: request.loaduser,
+ money: request.money,
+ torrentid: request.torrentid
+ },
+ });
+ };
+
+ console.log('请求数据:', requests);
+
+ // 表格列定义
+ const columns = [
+ {
+ title: 'ID',
+ dataIndex: 'requestid',
+ key: 'requestid',
+ width: 60,
+ fixed: 'left',
+ },
+ {
+ title: '发帖ID',
+ dataIndex: 'userid',
+ key: 'userid',
+ width: 100,
+ },
+ {
+ title: '上传者ID',
+ dataIndex: 'loaduser',
+ key: 'loaduser',
+ width: 100,
+ render: (val) => val ?? <Tag color="default">目前没有</Tag>,
+ },
+ {
+ title: '资源名字',
+ dataIndex: 'name',
+ key: 'name',
+ width: 120,
+ ellipsis: true,
+ },
+ {
+ title: '内容描述',
+ dataIndex: 'plot',
+ key: 'plot',
+ ellipsis: { showTitle: false },
+ render: (text) => (
+ <Tooltip placement="topLeft" title={text}>
+ {text}
+ </Tooltip>
+ ),
+ },
+ {
+ title: '悬赏金额',
+ dataIndex: 'money',
+ key: 'money',
+ width: 80,
+ render: (money) => <Tag color="volcano">{money}元</Tag>,
+ },
+ {
+ title: '照片',
+ dataIndex: 'photo',
+ key: 'photo',
+ width: 100,
+ render: (url) =>
+ url ? (
+ <Image
+ src={`http://localhost:8080${url}`}
+ width={80}
+ height={80}
+ style={{ objectFit: 'cover' }}
+ placeholder={<Spin />}
+ />
+ ) : (
+ <Tag color="default">无</Tag>
+ ),
+ },
+ {
+ title: '年份',
+ dataIndex: 'year',
+ key: 'year',
+ width: 80,
+ },
+ {
+ title: '国家',
+ dataIndex: 'country',
+ key: 'country',
+ width: 100,
+ },
+ {
+ title: '种子号',
+ dataIndex: 'torrentid',
+ key: 'torrentid',
+ width: 120,
+ render: (val) =>
+ val !== null && val !== undefined ? (
+ <Tag color="green">{val}</Tag>
+ ) : (
+ <Tag color="default">暂无</Tag>
+ ),
+ },
+ {
+ title: '发布时间',
+ dataIndex: 'requesttime',
+ key: 'requesttime',
+ width: 180,
+ render: (text) => {
+ if (!text) return <Tag color="default">暂无</Tag>;
+ return new Date(text).toLocaleString();
+ },
+ },
+ {
+ title: '操作',
+ key: 'action',
+ fixed: 'right',
+ width: 150,
+ render: (_, record) => (
+ <Space size="middle">
+ <Button
+ type="primary"
+ onClick={() => handleProcess(record)}
+ >
+ 处理
+ </Button>
+ <Button
+ danger
+ onClick={() => showDeleteConfirm(record.requestid)}
+ >
+ 删除
+ </Button>
+ </Space>
+ ),
+ },
+ ];
+
+ return (
+ <div style={{ padding: 20 }}>
+ <h2 style={{ marginBottom: 20 }}>求助帖管理面板</h2>
+ <Table
+ rowKey="requestid"
+ columns={columns}
+ dataSource={requests}
+ loading={loading}
+ scroll={{ x: 1600 }}
+ pagination={{ pageSize: 10 }}
+ bordered
+ size="middle"
+ />
+ </div>
+ );
+};
+
+export default RequestAdminPanel;
diff --git a/src/components/RequestBoard.css b/src/components/RequestBoard.css
new file mode 100644
index 0000000..0fbe1a9
--- /dev/null
+++ b/src/components/RequestBoard.css
@@ -0,0 +1,443 @@
+/* 容器和布局 */
+.request-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+}
+
+.request-header {
+ margin-bottom: 30px;
+ border-bottom: 1px solid #f0f0f0;
+ padding-bottom: 20px;
+}
+
+.request-search {
+ background-color: #fff8f0;
+ border-radius: 10px;
+ padding: 20px;
+ margin-bottom: 30px;
+ box-shadow: 0 4px 12px rgba(255, 102, 0, 0.1);
+}
+
+.request-money-total {
+ background-color: #fff1e6;
+ border-radius: 8px;
+ padding: 10px 15px;
+ display: inline-block;
+ font-size: 16px;
+}
+
+/* 卡片样式 */
+.request-card {
+ background-color: white;
+ border-radius: 12px;
+ padding: 20px;
+ margin-bottom: 20px;
+ box-shadow: 0 4px 15px rgba(255, 102, 0, 0.08);
+ transition: all 0.3s ease;
+ border: 1px solid #ffe8d6;
+}
+
+.request-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 10px 25px rgba(255, 102, 0, 0.15);
+}
+
+.request-card-title {
+ font-size: 18px;
+ font-weight: 700;
+ color: #d15700;
+ margin-bottom: 8px;
+}
+
+.request-card-money {
+ font-size: 20px;
+ font-weight: 800;
+ color: #ff5722;
+ letter-spacing: 0.5px;
+}
+
+.request-card-content {
+ color: #555;
+ line-height: 1.6;
+ margin: 15px 0;
+ padding: 15px;
+ background-color: #f9f9f9;
+ border-radius: 8px;
+ border-left: 4px solid #ffaa33;
+}
+
+.request-card-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ margin: 15px 0;
+ padding: 10px 0;
+ border-top: 1px dashed #ffe0b3;
+ border-bottom: 1px dashed #ffe0b3;
+ font-size: 14px;
+}
+
+.request-meta-item {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+}
+
+.request-meta-label {
+ font-weight: 600;
+ color: #ff8d00;
+}
+
+.request-card-image-container {
+ margin: 15px 0;
+}
+
+.request-card-image {
+ max-width: 300px;
+ border-radius: 8px;
+ border: 1px solid #ffd8b3;
+ box-shadow: 0 4px 8px rgba(255, 140, 0, 0.15);
+}
+
+/* 用户信息 - 重点修改 */
+.request-user-info-container {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: 10px;
+ background-color: #fffaf0;
+ border-radius: 10px;
+ box-shadow: 0 2px 8px rgba(255, 140, 0, 0.1);
+ min-width: 140px;
+}
+
+.request-user-divider {
+ height: 1px;
+ width: 100%;
+ background-color: #ffd8b3;
+ margin: 4px 0;
+}
+
+.request-user-info {
+ padding: 5px;
+}
+
+.request-user-details {
+ min-width: 60px;
+ overflow: hidden;
+}
+
+/* 按钮样式 */
+.request-button {
+ padding: 10px 16px;
+ border: none;
+ border-radius: 8px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 14px;
+}
+
+.request-button:hover {
+ transform: translateY(-2px);
+}
+
+.request-button-back {
+ background-color: #f5f5f5;
+ color: #666;
+}
+
+.request-button-back:hover {
+ background-color: #e0e0e0;
+}
+
+.request-button-create {
+ background: linear-gradient(135deg, #ffa500, #ff7043);
+ color: white;
+ box-shadow: 0 4px 10px rgba(255, 140, 0, 0.3);
+}
+
+.request-button-create:hover {
+ background: linear-gradient(135deg, #ff8c00, #ff5722);
+ box-shadow: 0 6px 14px rgba(255, 140, 0, 0.4);
+}
+
+.request-button-search {
+ background: linear-gradient(135deg, #4db6ac, #26a69a);
+ color: white;
+ box-shadow: 0 4px 10px rgba(77, 182, 172, 0.3);
+}
+
+.request-button-search:hover {
+ background: linear-gradient(135deg, #26a69a, #00897b);
+}
+
+.request-button-submit {
+ background: linear-gradient(135deg, #ffa726, #fb8c00);
+ color: white;
+ padding: 12px 30px;
+ font-size: 16px;
+ box-shadow: 0 4px 12px rgba(255, 140, 0, 0.4);
+}
+
+.request-button-submit:hover {
+ background: linear-gradient(135deg, #fb8c00, #f57c00);
+}
+
+.request-button-takeover {
+ background: linear-gradient(135deg, #42a5f5, #1e88e5);
+ color: white;
+ box-shadow: 0 4px 10px rgba(66, 165, 245, 0.3);
+ width: 100%;
+ font-size: 13px;
+ padding: 8px 12px;
+}
+
+.request-button-takeover:hover {
+ background: linear-gradient(135deg, #1e88e5, #1565c0);
+}
+
+.request-button-delete {
+ background: linear-gradient(135deg, #ef5350, #e53935);
+ color: white;
+ box-shadow: 0 4px 10px rgba(239, 83, 80, 0.3);
+ font-size: 13px;
+ padding: 8px 12px;
+}
+
+.request-button-delete:hover {
+ background: linear-gradient(135deg, #e53935, #c62828);
+}
+
+/* 标签页 */
+.request-tab {
+ padding: 8px 16px;
+ border-radius: 20px;
+ background-color: #f1f1f1;
+ border: 1px solid #e0e0e0;
+ font-weight: 600;
+ color: #666;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ font-size: 13px;
+}
+
+.request-tab-active {
+ background: linear-gradient(135deg, #ffb74d, #ff9800);
+ color: white;
+ border-color: #ff9800;
+ box-shadow: 0 4px 8px rgba(255, 152, 0, 0.2);
+}
+
+/* 表单元素 */
+.request-form {
+ background-color: #fffaf5;
+ border-radius: 12px;
+ padding: 25px;
+ box-shadow: 0 5px 15px rgba(255, 140, 0, 0.1);
+ margin-top: 20px;
+}
+
+.request-label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 600;
+ color: #ff8d00;
+ font-size: 14px;
+}
+
+.request-input {
+ width: 100%;
+ padding: 10px 12px;
+ border: 1px solid #ffd8b3;
+ border-radius: 8px;
+ font-size: 14px;
+ transition: all 0.3s ease;
+ background-color: #fffdfa;
+}
+
+.request-input:focus {
+ outline: none;
+ border-color: #ffaa33;
+ box-shadow: 0 0 0 3px rgba(255, 170, 51, 0.2);
+}
+
+.request-textarea {
+ width: 100%;
+ padding: 12px 15px;
+ border: 1px solid #ffd8b3;
+ border-radius: 8px;
+ min-height: 120px;
+ resize: vertical;
+ font-size: 14px;
+ background-color: #fffdfa;
+}
+
+.request-textarea:focus {
+ outline: none;
+ border-color: #ffaa33;
+ box-shadow: 0 0 0 3px rgba(255, 170, 51, 0.2);
+}
+
+.request-file {
+ width: 100%;
+ padding: 10px;
+ background: #f9f9f9;
+ border-radius: 8px;
+ font-size: 13px;
+}
+
+/* 空状态 */
+.request-empty {
+ text-align: center;
+ padding: 50px 20px;
+ background-color: white;
+ border-radius: 12px;
+ box-shadow: 0 4px 15px rgba(255, 102, 0, 0.08);
+}
+
+.request-empty-icon {
+ font-size: 48px;
+ color: #ffc107;
+ margin-bottom: 20px;
+}
+
+.request-empty-text {
+ font-size: 18px;
+ color: #ff8d00;
+ font-weight: 500;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+ .request-container {
+ padding: 15px;
+ }
+
+ .request-card {
+ padding: 15px;
+ }
+
+ .request-header {
+ margin-bottom: 20px;
+ }
+
+ .request-card-title {
+ font-size: 16px;
+ }
+
+ .request-card-money {
+ font-size: 18px;
+ }
+
+ .request-user-info-container {
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 10px;
+ min-width: 100%;
+ margin-top: 10px;
+ }
+
+ .request-user-info {
+ flex: 1;
+ min-width: 130px;
+ }
+
+ .request-user-divider {
+ display: none;
+ }
+
+ .request-card-actions {
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .request-button {
+ padding: 8px 12px;
+ font-size: 12px;
+ }
+
+ .request-button-takeover,
+ .request-button-delete {
+ font-size: 12px;
+ }
+
+ /* 在您的CSS文件中添加以下样式 */
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
+
+ body {
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+ background-color: #fffaf5;
+ }
+
+ /* 全局动画效果 */
+ .transition {
+ transition: all 0.3s ease;
+ }
+
+ /* 卡片阴影效果 */
+ .shadow-lg {
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
+ }
+
+ .shadow-md {
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ }
+
+ /* 圆角 */
+ .rounded-xl {
+ border-radius: 1rem;
+ }
+
+ .rounded-lg {
+ border-radius: 0.75rem;
+ }
+
+ /* 渐变背景 */
+ .bg-gradient-to-r {
+ background-size: 200% auto;
+ transition: background-position 0.5s ease;
+ }
+
+ .bg-gradient-to-r:hover {
+ background-position: right center;
+ }
+
+ /* 表单输入框动画 */
+ input:focus,
+ textarea:focus {
+ transform: translateY(-1px);
+ }
+
+ /* 卡片悬停效果 */
+ .bg-white:hover {
+ transform: translateY(-3px);
+ }
+
+ /* 响应式调整 */
+ @media (max-width: 768px) {
+ .text-xl {
+ font-size: 1.25rem;
+ }
+
+ .text-2xl {
+ font-size: 1.5rem;
+ }
+
+ .p-6 {
+ padding: 1.25rem;
+ }
+
+ .p-12 {
+ padding: 2rem;
+ }
+
+ .grid-cols-2 {
+ grid-template-columns: 1fr;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/RequestBoard.jsx b/src/components/RequestBoard.jsx
new file mode 100644
index 0000000..3e63482
--- /dev/null
+++ b/src/components/RequestBoard.jsx
@@ -0,0 +1,660 @@
+import React, { useState, useEffect } from 'react';
+import {
+ createRequest,
+ deleteRequest,
+ updateMoney,
+ findByUserid,
+ findByName,
+ getTotalMoneyByName,
+ updateLoaduserByName,
+ getAllRequests
+} from '../api/request';
+import { useSetState } from '@mantine/hooks';
+import { useNavigate } from 'react-router-dom';
+import './RequestBoard.css';
+
+const RequestBoard = ({ currentUserId, onBack }) => {
+ const storedUser = localStorage.getItem('user');
+ //let currentUserId = null; // 初始化为 null
+
+ if (storedUser) {
+ try {
+ const parsedUser = JSON.parse(storedUser);
+ currentUserId = parsedUser.userid; // 直接赋值
+ } catch (error) {
+ console.error('解析用户数据失败:', error);
+ // 可以在这里处理 JSON 解析错误(如数据损坏)
+ }
+ } else {
+ console.log('用户未登录');
+ }
+
+ // 现在 currentUserId 可以在后续逻辑中使用
+ console.log('当前用户ID:', currentUserId);
+
+ const [requests, setRequests] = useState([]);
+ const [allRequests, setAllRequests] = useState([]);
+ const [searchedRequests, setSearchedRequests] = useState([]);
+ const [userInfos, setUserInfos] = useState({}); // 存储所有用户信息:{ userid: { username, avatar } }
+ const navigate = useNavigate();
+ const [formData, setFormData] = useState({
+ userid: currentUserId,
+ name: '',
+ plot: '',
+ money: '',
+ year: '',
+ country: '',
+ photo: null,
+ });
+ const [searchName, setSearchName] = useState('');
+ const [totalMoney, setTotalMoney] = useState(null);
+ const [viewMode, setViewMode] = useState('my');
+ const [showForm, setShowForm] = useState(false); // 控制表单折叠
+
+ // 获取用户信息的函数
+ const fetchUserInfo = async (userId) => {
+ try {
+ // 如果已经获取过该用户的信息,直接返回
+ if (userInfos[userId]) return;
+
+ const response = await fetch(`http://localhost:8080/user/getDecoration?userid=${userId}`);
+ if (response.ok) {
+ const data = await response.json();
+ if (data.success) {
+ // 更新用户信息状态
+ setUserInfos(prev => ({
+ ...prev,
+ [userId]: {
+ username: data.data.username,
+ avatar: data.data.image,
+ decoration: data.data.decoration
+ }
+ }));
+ } else {
+ console.error('获取用户信息失败:', data.message);
+ }
+ } else {
+ console.error('获取用户信息失败:', response.statusText);
+ }
+ } catch (error) {
+ console.error('获取用户信息出错:', error);
+ }
+ };
+
+ useEffect(() => {
+ loadUserRequests();
+ loadAllRequests();
+ }, [currentUserId]);
+
+ const loadUserRequests = async () => {
+ const data = await findByUserid(currentUserId);
+ setRequests(data);
+ // 为每个需求贴获取用户信息
+ data.forEach(request => {
+ fetchUserInfo(request.userid); // 获取创建者信息
+ if (request.loaduser) {
+ fetchUserInfo(request.loaduser); // 获取接管者信息
+ }
+ });
+ };
+
+ const loadAllRequests = async () => {
+ const data = await getAllRequests();
+ setAllRequests(data);
+ // 为每个需求贴获取用户信息
+ data.forEach(request => {
+ fetchUserInfo(request.userid); // 获取创建者信息
+ if (request.loaduser) {
+ fetchUserInfo(request.loaduser); // 获取接管者信息
+ }
+ });
+ };
+
+ // 处理搜索的需求贴
+ const processSearchedRequests = (data) => {
+ setSearchedRequests(data);
+ // 为每个需求贴获取用户信息
+ data.forEach(request => {
+ fetchUserInfo(request.userid); // 获取创建者信息
+ if (request.loaduser) {
+ fetchUserInfo(request.loaduser); // 获取接管者信息
+ }
+ });
+ };
+
+ const handleChange = (e) => {
+ const { name, value, files } = e.target;
+ setFormData((prev) => ({
+ ...prev,
+ [name]: files ? files[0] : value,
+ }));
+ };
+
+ const handleCreate = async (e) => {
+ e.preventDefault();
+ const fd = new FormData();
+ Object.entries(formData).forEach(([key, value]) => {
+ if (value !== '' && value !== null) fd.append(key, value);
+ });
+
+ const res = await createRequest(fd);
+ if (res.data === true) {
+ alert('创建成功');
+ setFormData(prev => ({
+ ...prev,
+ name: '',
+ plot: '',
+ money: '',
+ year: '',
+ country: '',
+ photo: null,
+ }));
+ setShowForm(false);
+ loadUserRequests();
+ loadAllRequests();
+ } else {
+ alert('创建失败');
+ }
+ };
+
+ const handleDelete = async (id) => {
+ if (window.confirm('确定要删除这个需求贴吗?')) {
+ await deleteRequest(id);
+ loadUserRequests();
+ loadAllRequests();
+ }
+ };
+
+ const handleUpdateMoney = async (id, newMoney) => {
+ if (!newMoney) return;
+ await updateMoney(id, newMoney);
+ loadUserRequests();
+ };
+
+ const handleSearch = async () => {
+ if (!searchName.trim()) {
+ alert('请输入搜索关键词');
+ return;
+ }
+ const data = await findByName(searchName);
+ const total = await getTotalMoneyByName(searchName);
+ setTotalMoney(total);
+ setViewMode('search');
+ processSearchedRequests(data);
+ };
+
+ const handleProcess = (request) => {
+ navigate(`/uploadfull/${request.requestid}`, {
+ state: {
+ requestid: request.requestid
+ },
+ });
+ };
+
+ const handleUploadLoaduser = async (name, requestid) => {
+ if (window.confirm(`确定要接管 "${name}" 的所有需求贴吗?`)) {
+ try {
+ await updateLoaduserByName(name, currentUserId);
+
+ alert('接管成功');
+ handleProcess({ requestid: requestid });
+ console.log(requestid);
+ loadUserRequests();
+ loadAllRequests();
+ } catch (error) {
+ alert('接管失败,请稍后重试');
+ console.error(error);
+ }
+ }
+ };
+
+ // 渲染用户信息展示
+ const renderUserInfo = (userId, prefix = '') => {
+ if (!userId) return null;
+
+ const userInfo = userInfos[userId];
+
+ return (
+ <div className="request-user-info">
+ <div className="flex items-center gap-2">
+ <div className="request-user-avatar">
+ {userInfo?.avatar ? (
+ <img
+ src={userInfo.avatar}
+ alt={`${prefix}头像`}
+ className="w-8 h-8 rounded-full object-cover border-2 border-orange-200 sm:w-6 sm:h-6"
+ />
+ ) : (
+ <div className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center text-gray-500 text-xs border-2 border-orange-200 sm:w-6 sm:h-6">
+ {prefix.charAt(0)}
+ </div>
+ )}
+ </div>
+ <div className="request-user-details">
+ <div className="text-xs sm:text-[11px] font-medium text-gray-600">
+ {prefix}:
+ </div>
+ <div className="text-sm sm:text-xs font-medium text-orange-600 truncate max-w-[100px]">
+ {userInfo?.username || `用户${userId}`}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ };
+
+ const renderActions = (request) => {
+ if (request.userid === currentUserId) {
+ return (
+ <div className="flex flex-col sm:flex-row gap-2 w-full">
+ <div className="flex gap-2 flex-1">
+ <input
+ type="number"
+ placeholder="新金额"
+ onChange={(e) => handleUpdateMoney(request.requestid, e.target.value)}
+ className="request-input flex-1 text-center py-1 sm:py-0"
+ />
+ <button
+ onClick={() => handleDelete(request.requestid)}
+ className="request-button request-button-delete"
+ >
+ 删除
+ </button>
+ </div>
+ </div>
+ );
+ }
+ return (
+ <button
+ onClick={() => handleUploadLoaduser(request.name, request.requestid)}
+ className="request-button request-button-takeover"
+ >
+ 接管任务
+ </button>
+ );
+ };
+
+ return (
+ <div className="request-container">
+ <div className="request-header">
+ <div className="flex flex-col lg:flex-row justify-between gap-4 mb-6">
+ <h1 className="text-2xl font-bold text-orange-700 sm:text-xl">需求贴发布区</h1>
+ <div className="flex flex-wrap gap-2">
+ <button
+ className={`request-tab ${viewMode === 'my' ? 'request-tab-active' : ''}`}
+ onClick={() => setViewMode('my')}
+ >
+ 我的需求贴
+ </button>
+ <button
+ className={`request-tab ${viewMode === 'all' ? 'request-tab-active' : ''}`}
+ onClick={() => setViewMode('all')}
+ >
+ 所有需求贴
+ </button>
+ <button
+ className="request-button request-button-back"
+ onClick={onBack}
+ >
+ 返回主页面
+ </button>
+ <button
+ onClick={() => setShowForm(!showForm)}
+ className="request-button request-button-create"
+ >
+ {showForm ? '收起表单' : '创建新需求贴'}
+ </button>
+ </div>
+ </div>
+
+ {/* 折叠表单 */}
+ {showForm && (
+ <form className="request-form" onSubmit={handleCreate}>
+ <h2 className="text-xl font-semibold text-orange-700 mb-4 sm:text-lg">发布新需求</h2>
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+ <div>
+ <label className="request-label">需求标题 *</label>
+ <input
+ name="name"
+ placeholder="请输入需求标题"
+ className="request-input"
+ value={formData.name}
+ onChange={handleChange}
+ required
+ />
+ </div>
+ <div>
+ <label className="request-label">所需金额 (¥) *</label>
+ <input
+ name="money"
+ type="number"
+ placeholder="请输入金额"
+ className="request-input"
+ value={formData.money}
+ onChange={handleChange}
+ min="1"
+ required
+ />
+ </div>
+ </div>
+
+ <div className="mt-4">
+ <label className="request-label">详细描述 *</label>
+ <textarea
+ name="plot"
+ placeholder="请详细描述您的需求..."
+ className="request-textarea"
+ value={formData.plot}
+ onChange={handleChange}
+ required
+ />
+ </div>
+
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4">
+ <div>
+ <label className="request-label">年份</label>
+ <input
+ name="year"
+ type="number"
+ placeholder="如:2023"
+ className="request-input"
+ value={formData.year}
+ onChange={handleChange}
+ />
+ </div>
+ <div>
+ <label className="request-label">国家/地区</label>
+ <input
+ name="country"
+ placeholder="如:中国"
+ className="request-input"
+ value={formData.country}
+ onChange={handleChange}
+ />
+ </div>
+ <div>
+ <label className="request-label">相关图片</label>
+ <input
+ name="photo"
+ type="file"
+ className="request-file"
+ onChange={handleChange}
+ />
+ </div>
+ </div>
+
+ <button
+ type="submit"
+ className="request-button request-button-submit mt-6"
+ >
+ 发布需求贴
+ </button>
+ </form>
+ )}
+ </div>
+
+ {/* 搜索区域 */}
+ <div className="request-search">
+ <h2 className="text-xl font-semibold text-orange-700 mb-4 sm:text-lg">搜索需求贴</h2>
+ <div className="flex flex-col sm:flex-row gap-3">
+ <input
+ type="text"
+ placeholder="输入需求标题关键词..."
+ value={searchName}
+ onChange={(e) => setSearchName(e.target.value)}
+ className="request-input flex-1"
+ />
+ <button
+ onClick={handleSearch}
+ className="request-button request-button-search"
+ >
+ 搜索需求
+ </button>
+ </div>
+
+ {totalMoney !== null && viewMode === 'search' && (
+ <div className="request-money-total mt-4">
+ <span className="font-medium">"{searchName}" 需求总金额:</span>
+ <span className="font-bold text-red-600 ml-2">¥{totalMoney}</span>
+ </div>
+ )}
+ </div>
+
+ {/* 搜索结果 */}
+ {viewMode === 'search' && searchedRequests.length > 0 && (
+ <div className="request-results">
+ <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-3">
+ <h2 className="text-xl font-semibold text-orange-700 sm:text-lg">
+ 搜索结果:<span className="text-orange-500">{searchedRequests.length}</span> 条需求
+ </h2>
+ <button
+ onClick={() => setViewMode('my')}
+ className="request-button request-button-back"
+ >
+ 返回我的需求
+ </button>
+ </div>
+
+ <div className="request-cards">
+ {searchedRequests.map((request) => (
+ <div key={request.requestid} className="request-card">
+ <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3">
+ <div className="flex-1 min-w-0">
+ <h3 className="request-card-title">{request.name}</h3>
+ <div className="request-card-money">¥{request.money}</div>
+ </div>
+ <div className="request-user-info-container">
+ {renderUserInfo(request.userid, '创建者')}
+ {request.loaduser && (
+ <>
+ <div className="request-user-divider"></div>
+ {renderUserInfo(request.loaduser, '接管者')}
+ </>
+ )}
+ </div>
+ </div>
+
+ <div className="request-card-content">
+ {request.plot}
+ </div>
+
+ <div className="request-card-meta">
+ {request.year && (
+ <div className="request-meta-item">
+ <span className="request-meta-label">年份:</span>
+ <span>{request.year}</span>
+ </div>
+ )}
+ {request.country && (
+ <div className="request-meta-item">
+ <span className="request-meta-label">地区:</span>
+ <span>{request.country}</span>
+ </div>
+ )}
+ <div className="request-meta-item">
+ <span className="request-meta-label">发布时间:</span>
+ <span>{new Date(request.requestTime).toLocaleString()}</span>
+ </div>
+ </div>
+
+ {request.photo && (
+ <div className="request-card-image-container">
+ <img
+ src={`http://localhost:8080${request.photo}`}
+ alt="需求贴配图"
+ className="request-card-image"
+ />
+ </div>
+ )}
+
+ <div className="request-card-actions">
+ {renderActions(request)}
+ </div>
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
+
+ {/* 我的需求贴 */}
+ {viewMode === 'my' && (
+ <div className="request-my">
+ <h2 className="text-xl font-semibold text-orange-700 mb-6 sm:text-lg">我的需求贴</h2>
+
+ {requests.length === 0 ? (
+ <div className="request-empty">
+ <div className="request-empty-icon">📋</div>
+ <p className="request-empty-text">您还没有发布任何需求贴</p>
+ <button
+ className="request-button request-button-create mt-4"
+ onClick={() => {
+ setShowForm(true);
+ document.querySelector('.request-form')?.scrollIntoView({ behavior: 'smooth' });
+ }}
+ >
+ 立即发布需求
+ </button>
+ </div>
+ ) : (
+ <div className="request-cards">
+ {requests.map((request) => (
+ <div key={request.requestid} className="request-card">
+ <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3">
+ <div className="flex-1 min-w-0">
+ <h3 className="request-card-title">{request.name}</h3>
+ <div className="request-card-money">¥{request.money}</div>
+ </div>
+ <div className="request-user-info-container">
+ {renderUserInfo(request.userid, '创建者')}
+ {request.loaduser && (
+ <>
+ <div className="request-user-divider"></div>
+ {renderUserInfo(request.loaduser, '接管者')}
+ </>
+ )}
+ </div>
+ </div>
+
+ <div className="request-card-content">
+ {request.plot}
+ </div>
+
+ <div className="request-card-meta">
+ {request.year && (
+ <div className="request-meta-item">
+ <span className="request-meta-label">年份:</span>
+ <span>{request.year}</span>
+ </div>
+ )}
+ {request.country && (
+ <div className="request-meta-item">
+ <span className="request-meta-label">地区:</span>
+ <span>{request.country}</span>
+ </div>
+ )}
+ <div className="request-meta-item">
+ <span className="request-meta-label">发布时间:</span>
+ <span>{new Date(request.requestTime).toLocaleString()}</span>
+ </div>
+ </div>
+
+ {request.photo && (
+ <div className="request-card-image-container">
+ <img
+ src={`http://localhost:8080${request.photo}`}
+ alt="需求贴配图"
+ className="request-card-image"
+ />
+ </div>
+ )}
+
+ <div className="request-card-actions">
+ {renderActions(request)}
+ </div>
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+ )}
+
+ {/* 所有需求贴 */}
+ {viewMode === 'all' && (
+ <div className="request-all">
+ <div className="flex justify-between items-center mb-6">
+ <h2 className="text-xl font-semibold text-orange-700 sm:text-lg">所有需求贴</h2>
+ <span className="text-gray-600">共 {allRequests.length} 条需求</span>
+ </div>
+
+ {allRequests.length === 0 ? (
+ <div className="request-empty">
+ <div className="request-empty-icon">📭</div>
+ <p className="request-empty-text">当前平台暂无需求贴</p>
+ </div>
+ ) : (
+ <div className="request-cards">
+ {allRequests.map((request) => (
+ <div key={request.requestid} className="request-card">
+ <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3">
+ <div className="flex-1 min-w-0">
+ <h3 className="request-card-title">{request.name}</h3>
+ <div className="request-card-money">¥{request.money}</div>
+ </div>
+ <div className="request-user-info-container">
+ {renderUserInfo(request.userid, '创建者')}
+ {request.loaduser && (
+ <>
+ <div className="request-user-divider"></div>
+ {renderUserInfo(request.loaduser, '接管者')}
+ </>
+ )}
+ </div>
+ </div>
+
+ <div className="request-card-content">
+ {request.plot}
+ </div>
+
+ <div className="request-card-meta">
+ {request.year && (
+ <div className="request-meta-item">
+ <span className="request-meta-label">年份:</span>
+ <span>{request.year}</span>
+ </div>
+ )}
+ {request.country && (
+ <div className="request-meta-item">
+ <span className="request-meta-label">地区:</span>
+ <span>{request.country}</span>
+ </div>
+ )}
+ <div className="request-meta-item">
+ <span className="request-meta-label">发布时间:</span>
+ <span>{new Date(request.requestTime).toLocaleString()}</span>
+ </div>
+ </div>
+
+ {request.photo && (
+ <div className="request-card-image-container">
+ <img
+ src={`http://localhost:8080${request.photo}`}
+ alt="需求贴配图"
+ className="request-card-image"
+ />
+ </div>
+ )}
+
+ <div className="request-card-actions">
+ {renderActions(request)}
+ </div>
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+ )}
+ </div>
+ );
+};
+
+export default RequestBoard;
\ No newline at end of file