update pay function

This commit is contained in:
2025-11-23 20:12:54 +08:00
parent 3fa3e52d65
commit 7538f2d935
5 changed files with 439 additions and 92 deletions

View File

@@ -0,0 +1,274 @@
/**
* 预测市场服务 - API 版本
* 调用真实的后端 API数据存储到 MySQL 数据库
*/
import axios from 'axios';
import { getApiBase } from '@utils/apiConfig';
const api = axios.create({
baseURL: getApiBase(),
timeout: 10000,
withCredentials: true, // 携带 Cookiesession
});
// ==================== 积分系统 API ====================
/**
* 获取用户积分账户
*/
export const getUserAccount = async () => {
try {
const response = await api.get('/api/prediction/credit/account');
return response.data;
} catch (error) {
console.error('获取积分账户失败:', error);
throw error;
}
};
/**
* 领取每日奖励100积分
*/
export const claimDailyBonus = async () => {
try {
const response = await api.post('/api/prediction/credit/daily-bonus');
return response.data;
} catch (error) {
console.error('领取每日奖励失败:', error);
throw error;
}
};
// ==================== 预测话题 API ====================
/**
* 创建预测话题
* @param {Object} topicData - { title, description, category, deadline }
*/
export const createTopic = async (topicData) => {
try {
const response = await api.post('/api/prediction/topics', topicData);
return response.data;
} catch (error) {
console.error('创建预测话题失败:', error);
throw error;
}
};
/**
* 获取预测话题列表
* @param {Object} params - { status, category, sort_by, page, per_page }
*/
export const getTopics = async (params = {}) => {
try {
const response = await api.get('/api/prediction/topics', { params });
return response.data;
} catch (error) {
console.error('获取话题列表失败:', error);
throw error;
}
};
/**
* 获取预测话题详情
* @param {number} topicId
*/
export const getTopicDetail = async (topicId) => {
try {
const response = await api.get(`/api/prediction/topics/${topicId}`);
return response.data;
} catch (error) {
console.error('获取话题详情失败:', error);
throw error;
}
};
/**
* 结算预测话题(仅创建者可操作)
* @param {number} topicId
* @param {string} result - 'yes' | 'no' | 'draw'
*/
export const settleTopic = async (topicId, result) => {
try {
const response = await api.post(`/api/prediction/topics/${topicId}/settle`, { result });
return response.data;
} catch (error) {
console.error('结算话题失败:', error);
throw error;
}
};
// ==================== 交易 API ====================
/**
* 买入预测份额
* @param {Object} tradeData - { topic_id, direction, shares }
*/
export const buyShares = async (tradeData) => {
try {
const response = await api.post('/api/prediction/trade/buy', tradeData);
return response.data;
} catch (error) {
console.error('买入份额失败:', error);
throw error;
}
};
/**
* 获取用户持仓列表
*/
export const getUserPositions = async () => {
try {
const response = await api.get('/api/prediction/positions');
return response.data;
} catch (error) {
console.error('获取持仓列表失败:', error);
throw error;
}
};
// ==================== 评论 API ====================
/**
* 发表话题评论
* @param {number} topicId
* @param {Object} commentData - { content, parent_id }
*/
export const createComment = async (topicId, commentData) => {
try {
const response = await api.post(`/api/prediction/topics/${topicId}/comments`, commentData);
return response.data;
} catch (error) {
console.error('发表评论失败:', error);
throw error;
}
};
/**
* 获取话题评论列表
* @param {number} topicId
* @param {Object} params - { page, per_page }
*/
export const getComments = async (topicId, params = {}) => {
try {
const response = await api.get(`/api/prediction/topics/${topicId}/comments`, { params });
return response.data;
} catch (error) {
console.error('获取评论列表失败:', error);
throw error;
}
};
/**
* 点赞/取消点赞评论
* @param {number} commentId
*/
export const likeComment = async (commentId) => {
try {
const response = await api.post(`/api/prediction/comments/${commentId}/like`);
return response.data;
} catch (error) {
console.error('点赞评论失败:', error);
throw error;
}
};
// ==================== 工具函数(价格计算保留在前端,用于实时预览)====================
export const MARKET_CONFIG = {
MAX_SEATS_PER_SIDE: 5,
TAX_RATE: 0.02,
MIN_PRICE: 50,
MAX_PRICE: 950,
BASE_PRICE: 500,
};
/**
* 计算当前价格简化版AMM
* @param {number} yesShares - Yes方总份额
* @param {number} noShares - No方总份额
* @returns {Object} {yes: price, no: price}
*/
export const calculatePrice = (yesShares, noShares) => {
const totalShares = yesShares + noShares;
if (totalShares === 0) {
return {
yes: MARKET_CONFIG.BASE_PRICE,
no: MARKET_CONFIG.BASE_PRICE,
};
}
const yesProb = yesShares / totalShares;
const noProb = noShares / totalShares;
let yesPrice = yesProb * 1000;
let noPrice = noProb * 1000;
yesPrice = Math.max(MARKET_CONFIG.MIN_PRICE, Math.min(MARKET_CONFIG.MAX_PRICE, yesPrice));
noPrice = Math.max(MARKET_CONFIG.MIN_PRICE, Math.min(MARKET_CONFIG.MAX_PRICE, noPrice));
return { yes: Math.round(yesPrice), no: Math.round(noPrice) };
};
/**
* 计算交易税
* @param {number} amount - 交易金额
* @returns {number} 税费
*/
export const calculateTax = (amount) => {
return Math.floor(amount * MARKET_CONFIG.TAX_RATE);
};
/**
* 计算买入成本(用于前端预览)
* @param {number} currentShares - 当前方总份额
* @param {number} otherShares - 对手方总份额
* @param {number} buyAmount - 买入数量
* @returns {Object} { amount, tax, total }
*/
export const calculateBuyCost = (currentShares, otherShares, buyAmount) => {
const currentPrice = calculatePrice(currentShares, otherShares);
const afterShares = currentShares + buyAmount;
const afterPrice = calculatePrice(afterShares, otherShares);
const avgPrice = (currentPrice.yes + afterPrice.yes) / 2;
const amount = avgPrice * buyAmount;
const tax = calculateTax(amount);
const total = amount + tax;
return {
amount: Math.round(amount),
tax: Math.round(tax),
total: Math.round(total),
avgPrice: Math.round(avgPrice),
};
};
export default {
// 积分系统
getUserAccount,
claimDailyBonus,
// 话题管理
createTopic,
getTopics,
getTopicDetail,
settleTopic,
// 交易
buyShares,
getUserPositions,
// 评论
createComment,
getComments,
likeComment,
// 工具函数
calculatePrice,
calculateTax,
calculateBuyCost,
MARKET_CONFIG,
};

View File

@@ -36,8 +36,7 @@ import {
import { useParams, useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import { forumColors } from '@theme/forumTheme';
import { getTopic } from '@services/predictionMarketService';
import { getUserAccount } from '@services/creditSystemService';
import { getTopicDetail, getUserAccount } from '@services/predictionMarketService.api';
import { useAuth } from '@contexts/AuthContext';
import TradeModal from './components/TradeModal';
@@ -63,10 +62,11 @@ const PredictionTopicDetail = () => {
// 加载话题数据
useEffect(() => {
const loadTopic = () => {
const topicData = getTopic(topicId);
if (topicData) {
setTopic(topicData);
const loadTopic = async () => {
try {
const response = await getTopicDetail(topicId);
if (response.success) {
setTopic(response.data);
} else {
toast({
title: '话题不存在',
@@ -75,14 +75,33 @@ const PredictionTopicDetail = () => {
});
navigate('/value-forum');
}
} catch (error) {
console.error('获取话题详情失败:', error);
toast({
title: '加载失败',
description: error.message,
status: 'error',
duration: 3000,
});
navigate('/value-forum');
}
};
const loadAccount = async () => {
if (!user) return;
try {
const response = await getUserAccount();
if (response.success) {
setUserAccount(response.data);
}
} catch (error) {
console.error('获取账户失败:', error);
}
};
loadTopic();
if (user) {
setUserAccount(getUserAccount(user.id));
}
}, [topicId, user]);
loadAccount();
}, [topicId, user, toast, navigate]);
// 打开交易弹窗
const handleOpenTrade = (mode) => {
@@ -100,10 +119,21 @@ const PredictionTopicDetail = () => {
};
// 交易成功回调
const handleTradeSuccess = () => {
const handleTradeSuccess = async () => {
// 刷新话题数据
setTopic(getTopic(topicId));
setUserAccount(getUserAccount(user.id));
try {
const topicResponse = await getTopicDetail(topicId);
if (topicResponse.success) {
setTopic(topicResponse.data);
}
const accountResponse = await getUserAccount();
if (accountResponse.success) {
setUserAccount(accountResponse.data);
}
} catch (error) {
console.error('刷新数据失败:', error);
}
};
if (!topic) {

View File

@@ -29,8 +29,8 @@ import {
} from '@chakra-ui/react';
import { Zap, Calendar, DollarSign } from 'lucide-react';
import { forumColors } from '@theme/forumTheme';
import { createTopic } from '@services/predictionMarketService';
import { getUserAccount, CREDIT_CONFIG } from '@services/creditSystemService';
import { createTopic, getUserAccount } from '@services/predictionMarketService.api';
import { CREDIT_CONFIG } from '@services/creditSystemService';
import { useAuth } from '@contexts/AuthContext';
const CreatePredictionModal = ({ isOpen, onClose, onTopicCreated }) => {
@@ -46,9 +46,23 @@ const CreatePredictionModal = ({ isOpen, onClose, onTopicCreated }) => {
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [userAccount, setUserAccount] = useState(null);
// 获取用户余额
const userAccount = user ? getUserAccount(user.id) : null;
// 异步获取用户余额
useEffect(() => {
const fetchAccount = async () => {
if (!user || !isOpen) return;
try {
const response = await getUserAccount();
if (response.success) {
setUserAccount(response.data);
}
} catch (error) {
console.error('获取账户失败:', error);
}
};
fetchAccount();
}, [user, isOpen]);
// 处理表单变化
const handleChange = (field, value) => {
@@ -80,7 +94,7 @@ const CreatePredictionModal = ({ isOpen, onClose, onTopicCreated }) => {
}
// 检查余额
if (userAccount.balance < CREDIT_CONFIG.CREATE_TOPIC_COST) {
if (!userAccount || userAccount.balance < CREDIT_CONFIG.CREATE_TOPIC_COST) {
toast({
title: '积分不足',
description: `创建话题需要${CREDIT_CONFIG.CREATE_TOPIC_COST}积分`,
@@ -94,24 +108,18 @@ const CreatePredictionModal = ({ isOpen, onClose, onTopicCreated }) => {
const deadline = new Date();
deadline.setDate(deadline.getDate() + parseInt(formData.deadline_days));
const settlement_date = new Date(deadline);
settlement_date.setDate(settlement_date.getDate() + 1);
// 创建话题
const newTopic = createTopic({
author_id: user.id,
author_name: user.name || user.username,
author_avatar: user.avatar,
// 调用 API 创建话题
const response = await createTopic({
title: formData.title,
description: formData.description,
category: formData.category,
deadline: deadline.toISOString(),
settlement_date: settlement_date.toISOString(),
});
if (response.success) {
toast({
title: '创建成功!',
description: `话题已发布,扣除${CREDIT_CONFIG.CREATE_TOPIC_COST}积分`,
description: `话题已发布,剩余 ${response.data.new_balance} 积分`,
status: 'success',
duration: 3000,
});
@@ -126,10 +134,17 @@ const CreatePredictionModal = ({ isOpen, onClose, onTopicCreated }) => {
// 通知父组件
if (onTopicCreated) {
onTopicCreated(newTopic);
onTopicCreated(response.data);
}
onClose();
// 刷新账户数据
const accountResponse = await getUserAccount();
if (accountResponse.success) {
setUserAccount(accountResponse.data);
}
}
} catch (error) {
console.error('创建话题失败:', error);
toast({

View File

@@ -33,14 +33,13 @@ import { TrendingUp, TrendingDown, DollarSign, AlertCircle, Zap } from 'lucide-r
import { motion } from 'framer-motion';
import { forumColors } from '@theme/forumTheme';
import {
buyPosition,
sellPosition,
buyShares,
getUserAccount,
calculateBuyCost,
calculateSellRevenue,
calculateTax,
getTopic,
} from '@services/predictionMarketService';
import { getUserAccount, CREDIT_CONFIG } from '@services/creditSystemService';
MARKET_CONFIG,
} from '@services/predictionMarketService.api';
import { CREDIT_CONFIG } from '@services/creditSystemService';
import { useAuth } from '@contexts/AuthContext';
const MotionBox = motion(Box);
@@ -53,9 +52,23 @@ const TradeModal = ({ isOpen, onClose, topic, mode = 'buy', onTradeSuccess }) =>
const [selectedOption, setSelectedOption] = useState('yes');
const [shares, setShares] = useState(1);
const [isSubmitting, setIsSubmitting] = useState(false);
const [userAccount, setUserAccount] = useState(null);
// 获取用户账户
const userAccount = user ? getUserAccount(user.id) : null;
// 异步获取用户账户
useEffect(() => {
const fetchAccount = async () => {
if (!user || !isOpen) return;
try {
const response = await getUserAccount();
if (response.success) {
setUserAccount(response.data);
}
} catch (error) {
console.error('获取账户失败:', error);
}
};
fetchAccount();
}, [user, isOpen]);
// 重置状态
useEffect(() => {
@@ -79,15 +92,22 @@ const TradeModal = ({ isOpen, onClose, topic, mode = 'buy', onTradeSuccess }) =>
let avgPrice = 0;
if (mode === 'buy') {
cost = calculateBuyCost(selectedSide.total_shares, otherSide.total_shares, shares);
tax = calculateTax(cost);
totalCost = cost + tax;
avgPrice = cost / shares;
const costData = calculateBuyCost(
selectedOption === 'yes' ? selectedSide.total_shares : otherSide.total_shares,
selectedOption === 'yes' ? otherSide.total_shares : selectedSide.total_shares,
shares
);
cost = costData.amount;
tax = costData.tax;
totalCost = costData.total;
avgPrice = costData.avgPrice;
} else {
cost = calculateSellRevenue(selectedSide.total_shares, otherSide.total_shares, shares);
// 卖出功能暂未实现,使用简化计算
const currentPrice = selectedSide.current_price || MARKET_CONFIG.BASE_PRICE;
cost = currentPrice * shares;
tax = calculateTax(cost);
totalCost = cost - tax;
avgPrice = cost / shares;
avgPrice = currentPrice;
}
// 获取用户在该方向的持仓
@@ -128,43 +148,49 @@ const TradeModal = ({ isOpen, onClose, topic, mode = 'buy', onTradeSuccess }) =>
try {
setIsSubmitting(true);
let result;
if (mode === 'buy') {
result = buyPosition({
user_id: user.id,
user_name: user.name || user.username,
user_avatar: user.avatar,
// 调用买入 API
const response = await buyShares({
topic_id: topic.id,
option_id: selectedOption,
direction: selectedOption,
shares,
});
} else {
result = sellPosition({
user_id: user.id,
topic_id: topic.id,
option_id: selectedOption,
shares,
});
}
if (response.success) {
toast({
title: mode === 'buy' ? '购买成功!' : '卖出成功!',
description: mode === 'buy' ? `花费${totalCost}积分` : `获得${totalCost}积分`,
title: '购买成功!',
description: `花费${totalCost}积分,剩余 ${response.data.new_balance} 积分`,
status: 'success',
duration: 3000,
});
// 刷新账户数据
const accountResponse = await getUserAccount();
if (accountResponse.success) {
setUserAccount(accountResponse.data);
}
// 通知父组件刷新
if (onTradeSuccess) {
onTradeSuccess(result);
onTradeSuccess(response.data);
}
onClose();
}
} else {
// 卖出功能暂未实现
toast({
title: '功能暂未开放',
description: '卖出功能正在开发中,敬请期待',
status: 'warning',
duration: 3000,
});
}
} catch (error) {
console.error('交易失败:', error);
toast({
title: '交易失败',
description: error.message,
description: error.response?.data?.message || error.message,
status: 'error',
duration: 3000,
});

View File

@@ -33,7 +33,7 @@ import { Search, PenSquare, TrendingUp, Clock, Heart, Zap, HelpCircle } from 'lu
import { motion, AnimatePresence } from 'framer-motion';
import { forumColors } from '@theme/forumTheme';
import { getPosts, searchPosts } from '@services/elasticsearchService';
import { getTopics } from '@services/predictionMarketService';
import { getTopics } from '@services/predictionMarketService.api';
import PostCard from './components/PostCard';
import PredictionTopicCard from './components/PredictionTopicCard';
import CreatePostModal from './components/CreatePostModal';
@@ -93,11 +93,13 @@ const ValueForum = () => {
};
// 获取预测话题列表
const fetchPredictionTopics = () => {
const fetchPredictionTopics = async () => {
try {
setLoading(true);
const topics = getTopics({ status: 'active', sortBy });
setPredictionTopics(topics);
const response = await getTopics({ status: 'active', sort_by: sortBy });
if (response.success) {
setPredictionTopics(response.data);
}
} catch (error) {
console.error('获取预测话题失败:', error);
} finally {