From c09b9d66a6ec02da154cabfecb3793a5c72c2f98 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Tue, 15 Sep 2026 08:39:07 +0800 Subject: [PATCH] feat(points): apps/web/src/pages/points/Packages.tsx --- apps/web/src/pages/points/Packages.tsx | 167 +++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 apps/web/src/pages/points/Packages.tsx diff --git a/apps/web/src/pages/points/Packages.tsx b/apps/web/src/pages/points/Packages.tsx new file mode 100644 index 000000000..17ef5f025 --- /dev/null +++ b/apps/web/src/pages/points/Packages.tsx @@ -0,0 +1,167 @@ +/** + * 积分充值页(/app/points/recharge) + * 单独展示积分包,供入口直接跳转使用 + * 主 Plans 页面也有充值区,这里提供独立 URL 方便从"积分不足"弹窗跳转 + */ +import React, { useEffect, useState } from "react" +import { Card, Col, Row, Button, Tag, Typography, Space, Modal, message, Tooltip } from "antd" +import { DiamondOutlined, SafetyCertificateOutlined, CrownFilled } from "@ant-design/icons" +import { useNavigate } from "react-router-dom" +import PageHead from "@/components/layout/PageHead" +import { usePointsStore } from "@/store/pointsStore" +import { createPointsOrder, getPointsPackages } from "@/api/points" +import type { PointsPackage } from "@/api/points/types" +import "./Points.css" + +const { Title, Text, Paragraph } = Typography + +const PointsRecharge: React.FC = () => { + const navigate = useNavigate() + const { balance, init } = usePointsStore() + const [packages, setPackages] = useState([]) + const [buying, setBuying] = useState(null) + + useEffect(() => { + init() + getPointsPackages() + .then((r) => setPackages(r.packages)) + .catch(() => {}) + }, [init]) + + const getPackPrice = (pkg: PointsPackage): number => { + const mt = balance?.member_type ?? "free" + const key = `discounted_price_for_${mt}` as const + return (pkg as any)[key] ?? pkg.price + } + + const handleBuy = async (pkg: PointsPackage) => { + try { + setBuying(pkg.id) + const order = await createPointsOrder({ package_id: pkg.id }) + Modal.info({ + title: "支付功能开发中", + icon: , + content: ( +
+ + 订单已创建({order.id.slice(0, 16)}…),金额{" "} + ¥{(order.price_cents / 100).toFixed(2).replace(/\.00$/, "")}。 + 微信/支付宝支付正在接入中。 + + + 现阶段所有功能免费体验,积分仅为演示数据。 + +
+ ), + okText: "知道了", + }) + } catch (e: any) { + message.error(e?.message ?? "下单失败") + } finally { + setBuying(null) + } + } + + return ( +
+ + {!balance?.is_member && ( + + )} + + + } + /> + + {balance && ( + + +
+ 当前可用积分 +
+ {balance.balance.toLocaleString()} +
+
+
+
+ )} + + 选择积分包 + + {packages.map((pkg) => { + const price = getPackPrice(pkg) + const discount = price < pkg.price ? Math.round((1 - price / pkg.price) * 100) : 0 + const unit = price / pkg.points + return ( + + 0 ? "has-discount" : ""}`} + hoverable + > + {pkg.id === "basic_pack" &&
热门
} + {discount > 0 && ( + + 会员{10 - discount / 10}折 + + )} +
{pkg.name}
+
+ {pkg.points.toLocaleString()} 积分 +
+
+ ¥ + + {(price / 100).toFixed(price % 100 === 0 ? 0 : 1).replace(/\.0$/, "")} + + {discount > 0 && ( + ¥{(pkg.price / 100).toFixed(0)} + )} +
+
≈¥{unit.toFixed(3)}/积分 · 永久有效
+ +
+ + ) + })} +
+ + + 积分消耗说明 +
    +
  • 智能混剪:3 积分/条(≤30s),每加 30s +1 积分
  • +
  • AI 配音 / 声音克隆合成:1 积分/分钟
  • +
  • AI 数字人:15 积分/分钟
  • +
  • 抖音链接提取 / AI 改写 / AI 标题 / AI 封面:1~2 积分/次
  • +
  • 声音克隆训练:免费
  • +
  • 免费用户每日 2 条混剪免费,其余 AI 功能消耗为会员价 ×1.15
  • +
+ + + 所有规则以页面实际显示为准 + + +
+
+ ) +} + +export default PointsRecharge +export const Component = PointsRecharge