修改论坛、促销、登录,增加测试
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