修改论坛、促销、登录,增加测试

Change-Id: I71883fc1da46a94db47f90a4cd61474c274a5b2c
diff --git a/src/pages/PromotionsPage/PromotionsPage.css b/src/pages/PromotionsPage/PromotionsPage.css
new file mode 100644
index 0000000..6752c69
--- /dev/null
+++ b/src/pages/PromotionsPage/PromotionsPage.css
@@ -0,0 +1,206 @@
+.promotions-page {
+    padding: 20px;
+    font-family: Arial, sans-serif;
+}
+
+.promotions-container {
+    max-width: 1200px;
+    margin: 0 auto;
+    background-color: #fff;
+    border-radius: 8px;
+    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+    padding: 20px;
+}
+
+h1, h2, h3 {
+    color: #333;
+}
+
+.admin-actions {
+    margin-bottom: 20px;
+}
+
+.create-button, .submit-button, .delete-button {
+    background-color: #4CAF50;
+    color: white;
+    border: none;
+    padding: 10px 15px;
+    border-radius: 4px;
+    cursor: pointer;
+    font-size: 14px;
+    transition: background-color 0.3s;
+}
+
+.create-button:hover, .submit-button:hover {
+    background-color: #45a049;
+}
+
+.delete-button {
+    background-color: #f44336;
+    margin-top: 10px;
+}
+
+.delete-button:hover {
+    background-color: #d32f2f;
+}
+
+.create-promotion-form {
+    background-color: #f9f9f9;
+    padding: 20px;
+    border-radius: 8px;
+    margin-bottom: 20px;
+}
+
+.form-group {
+    margin-bottom: 15px;
+}
+
+.form-row {
+    display: flex;
+    gap: 20px;
+}
+
+.form-row .form-group {
+    flex: 1;
+}
+
+label {
+    display: block;
+    margin-bottom: 5px;
+    font-weight: bold;
+}
+
+input, select {
+    width: 100%;
+    padding: 8px;
+    border: 1px solid #ddd;
+    border-radius: 4px;
+    box-sizing: border-box;
+}
+
+.error-message {
+    color: #f44336;
+    margin-bottom: 15px;
+    padding: 10px;
+    background-color: #ffebee;
+    border-radius: 4px;
+}
+
+.promotions-grid {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 20px;
+}
+
+.promotions-list {
+    border-right: 1px solid #eee;
+    padding-right: 20px;
+}
+
+.promotion-items {
+    max-height: 600px;
+    overflow-y: auto;
+}
+
+.promotion-item {
+    background-color: #f9f9f9;
+    padding: 15px;
+    border-radius: 8px;
+    margin-bottom: 15px;
+    cursor: pointer;
+    transition: background-color 0.3s;
+}
+
+.promotion-item:hover {
+    background-color: #f0f0f0;
+}
+
+.promotion-item.active {
+    background-color: #e3f2fd;
+    border-left: 4px solid #2196F3;
+}
+
+.promotion-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 10px;
+}
+
+.promotion-type {
+    background-color: #2196F3;
+    color: white;
+    padding: 3px 8px;
+    border-radius: 12px;
+    font-size: 12px;
+}
+
+.promotion-dates {
+    color: #666;
+    font-size: 14px;
+    margin-bottom: 10px;
+}
+
+.promotion-coeffs {
+    display: flex;
+    gap: 15px;
+    font-weight: bold;
+}
+
+.promotion-coeffs span {
+    background-color: #e3f2fd;
+    padding: 3px 8px;
+    border-radius: 4px;
+}
+
+.promotion-details {
+    padding: 0 20px;
+}
+
+.detail-item {
+    margin-bottom: 15px;
+}
+
+.detail-item label {
+    font-weight: bold;
+    color: #555;
+}
+
+.detail-item span {
+    display: block;
+    margin-top: 5px;
+    padding: 8px;
+    background-color: #f5f5f5;
+    border-radius: 4px;
+}
+
+.no-promotions, .no-selection, .loading {
+    text-align: center;
+    padding: 40px;
+    color: #888;
+}
+
+.pagination {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    gap: 15px;
+    margin-top: 20px;
+}
+
+.pagination button {
+    padding: 5px 10px;
+    background-color: #f0f0f0;
+    border: 1px solid #ddd;
+    border-radius: 4px;
+    cursor: pointer;
+}
+
+.pagination button:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+}
+
+.pagination span {
+    font-size: 14px;
+}
\ No newline at end of file
diff --git a/src/pages/PromotionsPage/PromotionsPage.jsx b/src/pages/PromotionsPage/PromotionsPage.jsx
new file mode 100644
index 0000000..bb50d72
--- /dev/null
+++ b/src/pages/PromotionsPage/PromotionsPage.jsx
@@ -0,0 +1,435 @@
+import React, { useState, useEffect } from 'react';
+import './PromotionsPage.css';
+
+const API_BASE = process.env.REACT_APP_API_BASE;
+function PromotionsPage() {
+  const [promotions, setPromotions] = useState([]);
+  const [currentPromotion, setCurrentPromotion] = useState(null);
+  const [isAdmin, setIsAdmin] = useState(false);
+  const [isLoading, setIsLoading] = useState(false);
+  const [error, setError] = useState(null);
+  const [formData, setFormData] = useState({
+    name: '',
+    uploadCoeff: 1,
+    downloadCoeff: 1,
+    timeRange: 0,
+    criteria: 1,
+    pStartTime: '',
+    pEndTime: ''
+  });
+  const [isCreating, setIsCreating] = useState(false);
+  const [currentPage, setCurrentPage] = useState(1);
+  const [perPage] = useState(10);
+  const [totalPromotions, setTotalPromotions] = useState(0);
+
+  const getAuthHeaders = () => {
+    const token = localStorage.getItem('token');
+    return {
+      'Authorization': token ? `Bearer ${token}` : '',
+      'Content-Type': 'application/json'
+    };
+  };
+
+  const fetchPromotions = async (page = 1) => {
+    setIsLoading(true);
+    try {
+      const response = await fetch(`${API_BASE}/promotions/list?page=${page}&per_page=${perPage}`, {
+        headers: getAuthHeaders()
+      });
+
+      if (!response.ok) {
+        throw new Error('获取促销活动失败');
+      }
+
+      const data = await response.json();
+      if (data.code === 0 && data.result) {
+        setPromotions(data.rows || []);
+        setTotalPromotions(data.total || 0);
+      } else {
+        throw new Error(data.msg || '获取促销活动失败');
+      }
+    } catch (err) {
+      console.error('获取促销活动错误:', err);
+      setError(err.message);
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  const fetchPromotionDetails = async (promoId) => {
+    setIsLoading(true);
+    try {
+      const response = await fetch(`${API_BASE}/promotions/${promoId}`, {
+        headers: getAuthHeaders()
+      });
+
+      if (!response.ok) {
+        throw new Error('获取促销详情失败');
+      }
+
+      const data = await response.json();
+      if (data.code === 0 && data.result) {
+        setCurrentPromotion(data.rows);
+      } else {
+        throw new Error(data.msg || '获取促销详情失败');
+      }
+    } catch (err) {
+      console.error('获取促销详情错误:', err);
+      setError(err.message);
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  const createPromotion = async () => {
+    if (!formData.name || !formData.pStartTime || !formData.pEndTime) {
+      alert('请填写完整活动信息');
+      return;
+    }
+
+    if (new Date(formData.pStartTime) >= new Date(formData.pEndTime)) {
+      alert('活动时间设置不正确,请重新设定');
+      return;
+    }
+
+    setIsLoading(true);
+    try {
+      const response = await fetch('${API_BASE}/promotions/add', {
+        method: 'POST',
+        headers: getAuthHeaders(),
+        body: JSON.stringify(formData)
+      });
+
+      if (!response.ok) {
+        throw new Error('创建促销活动失败');
+      }
+
+      const data = await response.json();
+      if (data.code === 0 && data.result) {
+        alert(`活动创建成功!活动ID: ${data.msg}`);
+        setIsCreating(false);
+        setFormData({
+          name: '',
+          uploadCoeff: 1,
+          downloadCoeff: 1,
+          timeRange: 0,
+          criteria: 1,
+          pStartTime: '',
+          pEndTime: ''
+        });
+        fetchPromotions();
+      } else {
+        throw new Error(data.msg || '创建促销活动失败');
+      }
+    } catch (err) {
+      console.error('创建促销活动错误:', err);
+      alert(err.message);
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  const deletePromotion = async (promoId) => {
+    if (!window.confirm('确定要删除这个促销活动吗?')) {
+      return;
+    }
+
+    setIsLoading(true);
+    try {
+      const response = await fetch(`${API_BASE}/promotions/delete/${promoId}`, {
+        method: 'DELETE',
+        headers: getAuthHeaders()
+      });
+
+      if (!response.ok) {
+        throw new Error('删除促销活动失败');
+      }
+
+      const data = await response.json();
+      if (data.code === 0 && data.result) {
+        alert('促销活动删除成功');
+        fetchPromotions();
+        if (currentPromotion && currentPromotion.promoId === promoId) {
+          setCurrentPromotion(null);
+        }
+      } else {
+        throw new Error(data.msg || '删除促销活动失败');
+      }
+    } catch (err) {
+      console.error('删除促销活动错误:', err);
+      alert(err.message);
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  const checkAdminStatus = () => {
+    const role = localStorage.getItem('role');
+    setIsAdmin(role === 'admin');
+  };
+
+  useEffect(() => {
+    checkAdminStatus();
+    fetchPromotions();
+  }, []);
+
+  const handleInputChange = (e) => {
+    const { name, value } = e.target;
+    setFormData(prev => ({
+      ...prev,
+      [name]: name === 'uploadCoeff' || name === 'downloadCoeff' || name === 'timeRange' || name === 'criteria'
+          ? parseFloat(value)
+          : value
+    }));
+  };
+
+  const handlePageChange = (newPage) => {
+    setCurrentPage(newPage);
+    fetchPromotions(newPage);
+  };
+
+  const getPromotionType = (promo) => {
+    if (promo.downloadCoeff === 0 && promo.uploadCoeff > 1) {
+      return '免费';
+    } else if (promo.downloadCoeff < 1 && promo.uploadCoeff > 1) {
+      return '折扣+上传奖励';
+    } else if (promo.downloadCoeff < 1) {
+      return '折扣';
+    } else if (promo.uploadCoeff > 1) {
+      return '上传奖励';
+    }
+    return '普通';
+  };
+
+  return (
+      <div className="promotions-page">
+        <div className="promotions-container">
+          <h1>促销活动</h1>
+
+          {isAdmin && (
+              <div className="admin-actions">
+                <button
+                    className="create-button"
+                    onClick={() => setIsCreating(!isCreating)}
+                >
+                  {isCreating ? '取消创建' : '创建新活动'}
+                </button>
+              </div>
+          )}
+
+          {isCreating && isAdmin && (
+              <div className="create-promotion-form">
+                <h2>创建新促销活动</h2>
+                <div className="form-group">
+                  <label>活动名称</label>
+                  <input
+                      type="text"
+                      name="name"
+                      value={formData.name}
+                      onChange={handleInputChange}
+                      placeholder="例如: 春节特惠"
+                  />
+                </div>
+
+                <div className="form-row">
+                  <div className="form-group">
+                    <label>上传量系数</label>
+                    <input
+                        type="number"
+                        name="uploadCoeff"
+                        min="0"
+                        step="0.1"
+                        value={formData.uploadCoeff}
+                        onChange={handleInputChange}
+                    />
+                  </div>
+
+                  <div className="form-group">
+                    <label>下载量系数</label>
+                    <input
+                        type="number"
+                        name="downloadCoeff"
+                        min="0"
+                        step="0.1"
+                        value={formData.downloadCoeff}
+                        onChange={handleInputChange}
+                    />
+                  </div>
+                </div>
+
+                <div className="form-row">
+                  <div className="form-group">
+                    <label>资源时间范围</label>
+                    <select
+                        name="timeRange"
+                        value={formData.timeRange}
+                        onChange={handleInputChange}
+                    >
+                      <option value="0">全站资源</option>
+                      <option value="1">当天上传</option>
+                      <option value="2">最近两天</option>
+                      <option value="7">最近一周</option>
+                      <option value="30">最近一个月</option>
+                    </select>
+                  </div>
+
+                  <div className="form-group">
+                    <label>最低用户等级</label>
+                    <input
+                        type="number"
+                        name="criteria"
+                        min="1"
+                        value={formData.criteria}
+                        onChange={handleInputChange}
+                    />
+                  </div>
+                </div>
+
+                <div className="form-row">
+                  <div className="form-group">
+                    <label>开始时间</label>
+                    <input
+                        type="datetime-local"
+                        name="pStartTime"
+                        value={formData.pStartTime}
+                        onChange={handleInputChange}
+                    />
+                  </div>
+
+                  <div className="form-group">
+                    <label>结束时间</label>
+                    <input
+                        type="datetime-local"
+                        name="pEndTime"
+                        value={formData.pEndTime}
+                        onChange={handleInputChange}
+                    />
+                  </div>
+                </div>
+
+                <button
+                    className="submit-button"
+                    onClick={createPromotion}
+                    disabled={isLoading}
+                >
+                  {isLoading ? '创建中...' : '提交创建'}
+                </button>
+              </div>
+          )}
+
+          {error && <div className="error-message">{error}</div>}
+
+          <div className="promotions-grid">
+            {/* 促销活动列表 */}
+            <div className="promotions-list">
+              <h2>当前促销活动</h2>
+              {isLoading && promotions.length === 0 ? (
+                  <div className="loading">加载中...</div>
+              ) : promotions.length === 0 ? (
+                  <div className="no-promotions">暂无促销活动</div>
+              ) : (
+                  <div className="promotion-items">
+                    {promotions.map(promo => (
+                        <div
+                            key={promo.promoId}
+                            className={`promotion-item ${currentPromotion && currentPromotion.promoId === promo.promoId ? 'active' : ''}`}
+                            onClick={() => fetchPromotionDetails(promo.promoId)}
+                        >
+                          <div className="promotion-header">
+                            <h3>{promo.name}</h3>
+                            <span className="promotion-type">{getPromotionType(promo)}</span>
+                          </div>
+                          <div className="promotion-dates">
+                            {new Date(promo.pStartTime).toLocaleString()} - {new Date(promo.pEndTime).toLocaleString()}
+                          </div>
+                          <div className="promotion-coeffs">
+                            <span>上传: {promo.uploadCoeff}x</span>
+                            <span>下载: {promo.downloadCoeff}x</span>
+                          </div>
+                          {isAdmin && (
+                              <button
+                                  className="delete-button"
+                                  onClick={(e) => {
+                                    e.stopPropagation();
+                                    deletePromotion(promo.promoId);
+                                  }}
+                                  disabled={isLoading}
+                              >
+                                删除
+                              </button>
+                          )}
+                        </div>
+                    ))}
+                  </div>
+              )}
+
+              {totalPromotions > perPage && (
+                  <div className="pagination">
+                    <button
+                        disabled={currentPage === 1}
+                        onClick={() => handlePageChange(currentPage - 1)}
+                    >
+                      上一页
+                    </button>
+                    <span>第 {currentPage} 页</span>
+                    <button
+                        disabled={currentPage * perPage >= totalPromotions}
+                        onClick={() => handlePageChange(currentPage + 1)}
+                    >
+                      下一页
+                    </button>
+                  </div>
+              )}
+            </div>
+
+            {/* 促销活动详情 */}
+            <div className="promotion-details">
+              {currentPromotion ? (
+                  <>
+                    <h2>{currentPromotion.name}</h2>
+                    <div className="detail-item">
+                      <label>活动ID:</label>
+                      <span>{currentPromotion.promoId}</span>
+                    </div>
+                    <div className="detail-item">
+                      <label>活动时间:</label>
+                      <span>
+                    {new Date(currentPromotion.pStartTime).toLocaleString()} - {new Date(currentPromotion.pEndTime).toLocaleString()}
+                  </span>
+                    </div>
+                    <div className="detail-item">
+                      <label>促销类型:</label>
+                      <span>{getPromotionType(currentPromotion)}</span>
+                    </div>
+                    <div className="detail-item">
+                      <label>上传量系数:</label>
+                      <span>{currentPromotion.uploadCoeff}x</span>
+                    </div>
+                    <div className="detail-item">
+                      <label>下载量系数:</label>
+                      <span>{currentPromotion.downloadCoeff}x</span>
+                    </div>
+                    <div className="detail-item">
+                      <label>适用资源:</label>
+                      <span>
+                    {currentPromotion.timeRange === 0
+                        ? '全站资源'
+                        : `最近${currentPromotion.timeRange}天内上传的资源`}
+                  </span>
+                    </div>
+                    <div className="detail-item">
+                      <label>参与条件:</label>
+                      <span>用户等级 ≥ {currentPromotion.criteria}</span>
+                    </div>
+                  </>
+              ) : (
+                  <div className="no-selection">请从左侧选择一个促销活动查看详情</div>
+              )}
+            </div>
+          </div>
+        </div>
+      </div>
+  );
+}
+
+export default PromotionsPage;
\ No newline at end of file