feat: 更新项目代码
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import { FiHardDrive, FiCloud } from 'react-icons/fi';
|
||||
import clsx from 'clsx';
|
||||
import { TOOLBOX_CATEGORIES } from '../config/Api60sConfig';
|
||||
import FullscreenEmbed from '../components/FullscreenEmbed';
|
||||
import {
|
||||
@@ -8,114 +8,15 @@ import {
|
||||
PageHeader,
|
||||
PageTitle,
|
||||
FeatureGrid,
|
||||
FeatureCard,
|
||||
CatalogCard,
|
||||
FeatureCardIcon,
|
||||
FeatureCardTitle,
|
||||
FeatureCardDesc,
|
||||
FeatureCardUseCount,
|
||||
} from '../styles/shared';
|
||||
import { FEATURE_CARD_SECTION } from '../config/featureCardSections';
|
||||
import { useFeatureCardClickStats } from '../hooks/useFeatureCardClickStats';
|
||||
|
||||
const fadeUp = keyframes`from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}`;
|
||||
|
||||
const Tabs = styled.div`
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin: 0 auto 18px;
|
||||
max-width: 1200px;
|
||||
padding: 0 20px;
|
||||
overflow-x: auto;
|
||||
justify-content: center;
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar { display: none; }
|
||||
@media (max-width: 640px) {
|
||||
justify-content: flex-start;
|
||||
padding: 0 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Tab = styled.button`
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 20px;
|
||||
border: 1px solid ${({ $on }) => ($on ? 'transparent' : 'rgba(255,255,255,0.55)')};
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
background: ${({ $on, $c }) =>
|
||||
$on ? `linear-gradient(135deg, ${$c} 0%, #22c55e 100%)` : 'rgba(255,255,255,0.92)'};
|
||||
color: ${({ $on }) => ($on ? '#fff' : '#166534')};
|
||||
text-shadow: ${({ $on }) => ($on ? '0 1px 2px rgba(0,0,0,0.12)' : 'none')};
|
||||
box-shadow: ${({ $on }) =>
|
||||
$on ? '0 4px 16px rgba(34,197,94,0.35)' : '0 2px 8px rgba(255,255,255,0.5)'};
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: ${({ $on }) => ($on ? 'transparent' : 'rgba(129,199,132,0.8)')};
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
padding: 9px 16px;
|
||||
font-size: 13px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Grid = styled(FeatureGrid)``;
|
||||
|
||||
const Card = styled(FeatureCard)`
|
||||
&::before {
|
||||
background: linear-gradient(90deg, ${({ $c }) => $c || '#4ade80'}, #86efac);
|
||||
}
|
||||
`;
|
||||
|
||||
const CardIcon = styled(FeatureCardIcon)``;
|
||||
|
||||
const CardTitle = styled(FeatureCardTitle)``;
|
||||
|
||||
const CardDesc = styled(FeatureCardDesc)``;
|
||||
|
||||
/** 右下角:可离线(本地缓存即可用) / 需联网(云端) */
|
||||
const CardCornerHint = styled.span`
|
||||
position: absolute;
|
||||
right: 7px;
|
||||
bottom: 7px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 9px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
background: ${({ $variant }) =>
|
||||
$variant === 'offline' ? 'rgba(236, 253, 245, 0.98)' : 'rgba(255, 251, 235, 0.98)'};
|
||||
color: ${({ $variant }) => ($variant === 'offline' ? '#15803d' : '#c2410c')};
|
||||
border: 1px solid
|
||||
${({ $variant }) =>
|
||||
$variant === 'offline' ? 'rgba(34, 197, 94, 0.28)' : 'rgba(251, 146, 60, 0.35)'};
|
||||
box-shadow: 0 1px 4px rgba(15, 80, 40, 0.06);
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
stroke-width: 2.25;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
border-radius: 8px;
|
||||
svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/** 根据工具项解析所属分类色(嵌入打开后切换 Tab 仍正确) */
|
||||
function headerColorForItem(item) {
|
||||
if (!item) return '#4ade80';
|
||||
for (const c of TOOLBOX_CATEGORIES) {
|
||||
@@ -127,6 +28,7 @@ function headerColorForItem(item) {
|
||||
const ToolboxPage = () => {
|
||||
const [catId, setCatId] = useState(TOOLBOX_CATEGORIES[0].id);
|
||||
const [embedItem, setEmbedItem] = useState(null);
|
||||
const { counts, bump } = useFeatureCardClickStats(FEATURE_CARD_SECTION.TOOLBOX);
|
||||
|
||||
const cat = TOOLBOX_CATEGORIES.find((c) => c.id === catId) || TOOLBOX_CATEGORIES[0];
|
||||
|
||||
@@ -136,50 +38,69 @@ const ToolboxPage = () => {
|
||||
<PageTitle>工具箱</PageTitle>
|
||||
</PageHeader>
|
||||
|
||||
<Tabs>
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-2.5 mx-auto mb-[18px] max-w-[1200px] px-5 overflow-x-auto justify-center sm:justify-start sm:px-4 sm:gap-2 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
||||
{TOOLBOX_CATEGORIES.map((c) => (
|
||||
<Tab
|
||||
<button
|
||||
key={c.id}
|
||||
type="button"
|
||||
$on={catId === c.id}
|
||||
$c={c.color}
|
||||
onClick={() => setCatId(c.id)}
|
||||
className={clsx(
|
||||
'flex-shrink-0 flex items-center gap-1.5 px-5 py-2.5 rounded-xl text-sm font-semibold font-[inherit] cursor-pointer transition-all whitespace-nowrap border',
|
||||
'sm:px-4 sm:py-[9px] sm:text-[13px] sm:rounded-[10px]',
|
||||
catId === c.id
|
||||
? 'border-transparent text-white [text-shadow:0_1px_2px_rgba(0,0,0,0.12)] shadow-[0_4px_16px_rgba(34,197,94,0.35)] hover:-translate-y-px'
|
||||
: 'border-white/55 bg-white/92 text-[#166534] hover:-translate-y-px hover:border-[rgba(129,199,132,0.8)]',
|
||||
)}
|
||||
style={catId === c.id ? { background: `linear-gradient(135deg, ${c.color} 0%, #22c55e 100%)` } : {}}
|
||||
>
|
||||
{c.icon} {c.title}
|
||||
</Tab>
|
||||
</button>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<Grid key={catId}>
|
||||
<FeatureGrid key={catId}>
|
||||
{cat.items.map((item) => (
|
||||
<Card
|
||||
<CatalogCard
|
||||
key={item.id}
|
||||
$c={cat.color}
|
||||
onClick={() => setEmbedItem(item)}
|
||||
onClick={() => {
|
||||
bump(item.id);
|
||||
setEmbedItem(item);
|
||||
}}
|
||||
>
|
||||
<CardIcon>{item.icon}</CardIcon>
|
||||
<CardTitle>{item.title}</CardTitle>
|
||||
<CardDesc>{item.desc}</CardDesc>
|
||||
{item.offlineOk ? (
|
||||
<CardCornerHint
|
||||
$variant="offline"
|
||||
title="可离线:PWA 缓存后页面与脚本可本地加载(使用网络流/API 时仍需联网)"
|
||||
aria-label="可离线"
|
||||
>
|
||||
<FiHardDrive aria-hidden />
|
||||
</CardCornerHint>
|
||||
) : (
|
||||
<CardCornerHint
|
||||
$variant="online"
|
||||
title="需联网:依赖云端服务"
|
||||
aria-label="需联网"
|
||||
>
|
||||
<FiCloud aria-hidden />
|
||||
</CardCornerHint>
|
||||
)}
|
||||
</Card>
|
||||
<FeatureCardUseCount>{Number(counts[item.id]) || 0}</FeatureCardUseCount>
|
||||
<FeatureCardIcon>{item.icon}</FeatureCardIcon>
|
||||
<FeatureCardTitle>{item.title}</FeatureCardTitle>
|
||||
<FeatureCardDesc>{item.desc}</FeatureCardDesc>
|
||||
|
||||
{/* 右下角 离线/联网 标识 */}
|
||||
<span
|
||||
className={clsx(
|
||||
'absolute right-[7px] bottom-[7px] w-[30px] h-[30px] rounded-[9px]',
|
||||
'flex items-center justify-center pointer-events-none',
|
||||
'border shadow-[0_1px_4px_rgba(15,80,40,0.06)]',
|
||||
'sm:w-[26px] sm:h-[26px] sm:right-[5px] sm:bottom-[5px] sm:rounded-[8px]',
|
||||
item.offlineOk
|
||||
? 'bg-[rgba(236,253,245,0.98)] text-[#15803d] border-[rgba(34,197,94,0.28)]'
|
||||
: 'bg-[rgba(255,251,235,0.98)] text-[#c2410c] border-[rgba(251,146,60,0.35)]',
|
||||
)}
|
||||
title={
|
||||
item.offlineOk
|
||||
? '可离线:PWA 缓存后页面与脚本可本地加载(使用网络流/API 时仍需联网)'
|
||||
: '需联网:依赖云端服务'
|
||||
}
|
||||
aria-label={item.offlineOk ? '可离线' : '需联网'}
|
||||
>
|
||||
{item.offlineOk ? (
|
||||
<FiHardDrive className="w-4 h-4 sm:w-3.5 sm:h-3.5" strokeWidth={2.25} aria-hidden />
|
||||
) : (
|
||||
<FiCloud className="w-4 h-4 sm:w-3.5 sm:h-3.5" strokeWidth={2.25} aria-hidden />
|
||||
)}
|
||||
</span>
|
||||
</CatalogCard>
|
||||
))}
|
||||
</Grid>
|
||||
</FeatureGrid>
|
||||
|
||||
{embedItem && (
|
||||
<FullscreenEmbed
|
||||
|
||||
Reference in New Issue
Block a user