feat(points): update apps/web/src/components/layout/Header.tsx
This commit is contained in:
@@ -1,15 +1,27 @@
|
||||
/**
|
||||
* Phase 1 Header 重构
|
||||
* 扁平化导航菜单 + 手机端汉堡菜单
|
||||
* v2: 增加积分徽章、升级会员入口、我的积分入口
|
||||
*/
|
||||
import React, { useState } from "react"
|
||||
import { Avatar, Dropdown, Drawer, Space } from "antd"
|
||||
import { LogoutOutlined, SettingOutlined, UserOutlined, MenuOutlined } from "@ant-design/icons"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { Avatar, Badge, Button, Dropdown, Drawer, Space, Tooltip } from "antd"
|
||||
import {
|
||||
LogoutOutlined,
|
||||
SettingOutlined,
|
||||
UserOutlined,
|
||||
MenuOutlined,
|
||||
CrownFilled,
|
||||
DiamondOutlined,
|
||||
HistoryOutlined,
|
||||
WalletOutlined,
|
||||
} from "@ant-design/icons"
|
||||
import { useLocation, useNavigate } from "react-router-dom"
|
||||
import { useAuthStore } from "@/store/authStore"
|
||||
import { useLogout } from "@/hooks/useAuth"
|
||||
import type { MenuProps } from "antd"
|
||||
import { NAV_ITEMS } from "@/config/navigation"
|
||||
import PointsBadge from "@/components/common/PointsBadge"
|
||||
import { usePointsStore } from "@/store/pointsStore"
|
||||
import "./Header.css"
|
||||
|
||||
const Header: React.FC = () => {
|
||||
@@ -19,6 +31,18 @@ const Header: React.FC = () => {
|
||||
const logoutMutation = useLogout()
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||
|
||||
// 积分与会员状态
|
||||
const { balance, init, loading } = usePointsStore()
|
||||
useEffect(() => {
|
||||
init()
|
||||
// 每 60s 刷新一次(余额可能变化)
|
||||
const t = setInterval(() => init(), 60_000)
|
||||
return () => clearInterval(t)
|
||||
}, [init])
|
||||
|
||||
const isMember = !!balance?.is_member
|
||||
const memberType = balance?.member_type ?? null
|
||||
|
||||
/** 用户下拉菜单 */
|
||||
const menuItems: MenuProps["items"] = [
|
||||
{
|
||||
@@ -33,6 +57,30 @@ const Header: React.FC = () => {
|
||||
label: "订阅管理",
|
||||
onClick: () => navigate("/app/subscription"),
|
||||
},
|
||||
// v2: 我的积分入口
|
||||
{
|
||||
key: "points-center",
|
||||
icon: <DiamondOutlined />,
|
||||
label: (
|
||||
<Space>
|
||||
我的积分
|
||||
{balance && <span style={{ color: "#8b5cf6", fontWeight: 700 }}>{balance.balance}</span>}
|
||||
</Space>
|
||||
),
|
||||
onClick: () => navigate("/app/points"),
|
||||
},
|
||||
{
|
||||
key: "points-history",
|
||||
icon: <HistoryOutlined />,
|
||||
label: "积分明细",
|
||||
onClick: () => navigate("/app/points/transactions"),
|
||||
},
|
||||
{
|
||||
key: "recharge",
|
||||
icon: <WalletOutlined />,
|
||||
label: "充值积分",
|
||||
onClick: () => navigate("/app/points/recharge"),
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "logout",
|
||||
@@ -44,7 +92,6 @@ const Header: React.FC = () => {
|
||||
|
||||
/** 判断导航项是否激活 */
|
||||
const isActive = (path: string) => {
|
||||
// 首页特殊处理:/ 和 /app/dashboard 都算激活
|
||||
if (path === "/app/dashboard") {
|
||||
return (
|
||||
location.pathname === "/" ||
|
||||
@@ -78,6 +125,53 @@ const Header: React.FC = () => {
|
||||
</nav>
|
||||
|
||||
<div className="xx-right-section">
|
||||
{/* v2: 积分徽章(顶部显示) */}
|
||||
{balance && (
|
||||
<PointsBadge
|
||||
balance={balance.balance}
|
||||
onClick={() => navigate("/app/points")}
|
||||
className="xx-header-points"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* v2: 升级会员入口(仅免费用户显示) */}
|
||||
{!isMember && (
|
||||
<Tooltip title="升级会员解锁无限混剪、批量导出,积分 8 折起">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<CrownFilled />}
|
||||
className="xx-upgrade-btn"
|
||||
onClick={() => navigate("/app/subscription")}
|
||||
>
|
||||
升级会员
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* v2: 付费会员显示金冠标识 */}
|
||||
{isMember && (
|
||||
<Tooltip
|
||||
title={`${memberType === "yearly" ? "年卡" : memberType === "quarterly" ? "季卡" : "月卡"}会员${
|
||||
balance?.member_expires_at
|
||||
? ` · 到期 ${new Date(balance.member_expires_at).toLocaleDateString("zh-CN")}`
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<Badge dot color="gold">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<CrownFilled style={{ color: "#f59e0b" }} />}
|
||||
className="xx-crown-btn"
|
||||
onClick={() => navigate("/app/subscription")}
|
||||
>
|
||||
{memberType === "yearly" ? "年卡" : memberType === "quarterly" ? "季卡" : "会员"}
|
||||
</Button>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* 手机端汉堡菜单按钮 */}
|
||||
<button className="xx-hamburger" type="button" onClick={() => setMobileMenuOpen(true)}>
|
||||
<MenuOutlined />
|
||||
@@ -103,6 +197,37 @@ const Header: React.FC = () => {
|
||||
width={260}
|
||||
className="xx-mobile-drawer"
|
||||
>
|
||||
{/* 手机端积分/会员入口 */}
|
||||
{balance && (
|
||||
<div className="xx-mobile-points-row">
|
||||
<PointsBadge
|
||||
balance={balance.balance}
|
||||
onClick={() => {
|
||||
navigate("/app/points")
|
||||
setMobileMenuOpen(false)
|
||||
}}
|
||||
/>
|
||||
{!isMember ? (
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon={<CrownFilled />}
|
||||
onClick={() => {
|
||||
navigate("/app/subscription")
|
||||
setMobileMenuOpen(false)
|
||||
}}
|
||||
>
|
||||
升级
|
||||
</Button>
|
||||
) : (
|
||||
<span className="xx-mobile-member-tag">
|
||||
<CrownFilled style={{ color: "#f59e0b" }} />{" "}
|
||||
{memberType === "yearly" ? "年卡" : memberType === "quarterly" ? "季卡" : "月卡"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="xx-mobile-nav">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user