feat: refresh storefront ui

This commit is contained in:
2026-03-08 20:18:27 +08:00
parent 2e796bb55f
commit b0c9c193aa
10 changed files with 2119 additions and 1719 deletions

View File

@@ -1,15 +1,13 @@
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;800&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body { body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
background: #f5f5f5; color: var(--ink);
color: #333; }
#root,
.App {
min-height: 100vh;
} }
.app { .app {
@@ -25,32 +23,17 @@ body {
width: 100%; width: 100%;
} }
/* Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* Ant Design Overrides */
.ant-btn-primary { .ant-btn-primary {
background: #ff5a5a; background: linear-gradient(135deg, var(--brand) 0%, var(--brand-strong) 100%);
border-color: #ff5a5a; border-color: transparent;
box-shadow: none;
} }
.ant-btn-primary:hover { .ant-btn-primary:hover {
background: #ff7070 !important; background: linear-gradient(135deg, #f07869 0%, #df5545 100%) !important;
border-color: #ff7070 !important; border-color: transparent !important;
}
.ant-tag {
border-radius: 999px;
} }

View File

@@ -1,9 +1,11 @@
.header { .header {
background: #fff; background: rgba(255, 251, 246, 0.86);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); backdrop-filter: blur(18px);
position: fixed; border-bottom: 1px solid rgba(233, 221, 212, 0.88);
top: 0; box-shadow: 0 14px 34px rgba(61, 40, 24, 0.05);
left: 0; position: fixed;
top: 0;
left: 0;
right: 0; right: 0;
z-index: 1000; z-index: 1000;
} }
@@ -34,17 +36,17 @@
height: 24px; height: 24px;
} }
.logo-text { .logo-text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
color: #ff5a5a; color: #ef6a5b;
} }
.logo-domain { .logo-domain {
font-size: 10px; font-size: 10px;
color: #999; color: #8d8074;
margin-left: 4px; margin-left: 4px;
} }
.header-nav { .header-nav {
display: flex; display: flex;
@@ -52,20 +54,20 @@
gap: 24px; gap: 24px;
} }
.nav-item { .nav-item {
color: #333; color: #3b3027;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
transition: color 0.2s; transition: color 0.2s;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
position: relative; position: relative;
} }
.nav-item:hover { .nav-item:hover {
color: #ff5a5a; color: #ef6a5b;
} }
.nav-new { .nav-new {
position: relative; position: relative;
@@ -107,10 +109,11 @@
} }
.btn-invite { .btn-invite {
background: #ff5a5a; background: #ff5a5a;
border-color: #ff5a5a; background: linear-gradient(135deg, #ef6a5b 0%, #d84e3f 100%);
border-radius: 20px; border-color: #ff5a5a;
padding: 4px 20px; border-radius: 20px;
padding: 4px 20px;
height: 36px; height: 36px;
} }
@@ -120,30 +123,32 @@
} }
.btn-register { .btn-register {
border-radius: 20px; border-radius: 20px;
padding: 4px 20px; padding: 4px 20px;
height: 36px; height: 36px;
border-color: #333; border-color: #d9cabc;
color: #333; color: #3b3027;
} background: rgba(255, 255, 255, 0.72);
}
.btn-register:hover {
border-color: #ff5a5a !important; .btn-register:hover {
color: #ff5a5a !important; border-color: #ef6a5b !important;
} color: #ef6a5b !important;
}
.btn-login { .btn-login {
border-radius: 20px; border-radius: 20px;
padding: 4px 20px; padding: 4px 20px;
height: 36px; height: 36px;
border-color: #333; border-color: #d9cabc;
color: #333; color: #3b3027;
} background: rgba(255, 255, 255, 0.72);
}
.btn-login:hover {
border-color: #ff5a5a !important; .btn-login:hover {
color: #ff5a5a !important; border-color: #ef6a5b !important;
} color: #ef6a5b !important;
}
@media (max-width: 1024px) { @media (max-width: 1024px) {
.header-nav { .header-nav {

View File

@@ -1,177 +1,381 @@
/* ========== Hero 区块整体样式 ========== */ .hero {
.hero-section {
position: relative; position: relative;
height: 600px; padding: 32px 20px 24px;
}
.hero-shell {
max-width: 1400px;
margin: 0 auto;
padding: 44px;
border-radius: 36px;
background:
radial-gradient(circle at top left, rgba(239, 106, 91, 0.16), transparent 36%),
radial-gradient(circle at bottom right, rgba(244, 178, 84, 0.22), transparent 32%),
linear-gradient(135deg, #fffaf4 0%, #fff3e7 54%, #fffdf8 100%);
border: 1px solid rgba(225, 205, 186, 0.7);
box-shadow: 0 28px 80px rgba(76, 48, 30, 0.12);
display: grid;
grid-template-columns: minmax(0, 1.2fr) minmax(360px, 0.8fr);
gap: 28px;
overflow: hidden;
position: relative;
}
.hero-shell::before {
content: '';
position: absolute;
inset: 0;
background:
linear-gradient(120deg, rgba(255, 255, 255, 0.55), transparent 35%),
linear-gradient(305deg, rgba(255, 255, 255, 0.42), transparent 30%);
pointer-events: none;
}
.hero-copy,
.hero-stage {
position: relative;
z-index: 1;
}
.hero-copy {
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: center; justify-content: center;
overflow: hidden; gap: 22px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
animation: fadeIn 1s ease-out;
} }
/* ========== 动态背景 ========== */ .hero-eyebrow {
.hero-background { display: inline-flex;
position: absolute; align-items: center;
top: 0; width: fit-content;
left: 0; padding: 8px 14px;
right: 0; border-radius: 999px;
bottom: 0; background: rgba(255, 255, 255, 0.82);
overflow: hidden; border: 1px solid rgba(228, 202, 181, 0.9);
} color: #b55d43;
font-size: 13px;
.hero-particle { font-weight: 700;
position: absolute; letter-spacing: 0.08em;
width: 4px;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
animation: float 6s ease-in-out infinite;
}
.hero-particle:nth-child(1) { left: 10%; animation-delay: 0s; }
.hero-particle:nth-child(2) { left: 20%; animation-delay: 1s; }
.hero-particle:nth-child(3) { left: 30%; animation-delay: 2s; }
.hero-particle:nth-child(4) { left: 40%; animation-delay: 3s; }
.hero-particle:nth-child(5) { left: 50%; animation-delay: 4s; }
.hero-particle:nth-child(6) { left: 60%; animation-delay: 5s; }
.hero-particle:nth-child(7) { left: 70%; animation-delay: 6s; }
.hero-particle:nth-child(8) { left: 80%; animation-delay: 7s; }
.hero-particle:nth-child(9) { left: 90%; animation-delay: 8s; }
/* ========== Hero 内容 ========== */
.hero-content {
position: relative;
z-index: 2;
text-align: center;
color: white;
max-width: 800px;
padding: 0 20px;
animation: slideIn 1s ease-out;
} }
.hero-title { .hero-title {
font-size: 56px; margin: 0;
color: #211d18;
font-size: clamp(38px, 6vw, 64px);
line-height: 1.05;
font-weight: 800; font-weight: 800;
margin-bottom: 20px; max-width: 11ch;
line-height: 1.2;
text-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
} }
.hero-subtitle { .hero-title span {
font-size: 20px; display: block;
margin-bottom: 40px; color: #ef6a5b;
opacity: 0.95;
line-height: 1.6;
} }
/* ========== Hero 按钮 ========== */ .hero-description {
.hero-buttons { max-width: 620px;
margin: 0;
font-size: 17px;
line-height: 1.9;
color: #6f665d;
}
.hero-search-panel {
display: flex; display: flex;
gap: 20px; flex-direction: column;
justify-content: center; gap: 16px;
}
.hero-search-box {
padding: 14px;
border-radius: 26px;
background: rgba(255, 255, 255, 0.92);
border: 1px solid rgba(230, 215, 202, 0.92);
box-shadow: 0 16px 30px rgba(70, 44, 26, 0.08);
}
.hero-search-input.ant-input-affix-wrapper {
height: 62px;
border: none;
box-shadow: none;
padding: 0 10px;
background: transparent;
}
.hero-search-input.ant-input-affix-wrapper input {
font-size: 16px;
color: #2d261f;
}
.hero-search-input.ant-input-affix-wrapper input::placeholder {
color: #a19488;
}
.hero-search-prefix,
.hero-search-camera {
color: #b18a72;
font-size: 18px;
}
.hero-action-row {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap; flex-wrap: wrap;
} }
.btn-hero-primary { .hero-primary-btn.ant-btn {
background: white; height: 48px;
color: #667eea; padding: 0 22px;
padding: 16px 40px; border-radius: 999px;
border-radius: 30px;
font-size: 18px;
font-weight: 700;
border: none; border: none;
cursor: pointer; background: linear-gradient(135deg, #ef6a5b 0%, #d84e3f 100%);
transition: all 0.3s ease; box-shadow: 0 14px 30px rgba(216, 78, 63, 0.26);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.btn-hero-primary:hover {
transform: translateY(-3px);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
}
.btn-hero-secondary {
background: transparent;
color: white;
padding: 16px 40px;
border-radius: 30px;
font-size: 18px;
font-weight: 700; font-weight: 700;
border: 2px solid white; }
.hero-primary-btn.ant-btn:hover {
transform: translateY(-1px);
}
.hero-secondary-btn.ant-btn {
height: 48px;
padding: 0 22px;
border-radius: 999px;
border: 1px solid rgba(182, 149, 120, 0.42);
background: rgba(255, 255, 255, 0.72);
color: #4d3f31;
font-weight: 600;
}
.hero-secondary-btn.ant-btn:hover {
color: #ef6a5b !important;
border-color: rgba(239, 106, 91, 0.45) !important;
}
.hero-tags {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
}
.hero-tags-label {
font-size: 13px;
font-weight: 700;
color: #7d7267;
}
.hero-tag.ant-tag {
margin: 0;
padding: 8px 14px;
border-radius: 999px;
border: 1px solid rgba(221, 200, 182, 0.95);
background: rgba(255, 255, 255, 0.8);
color: #4b4036;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.2s ease;
} }
.btn-hero-secondary:hover { .hero-tag.ant-tag:hover {
background: rgba(255, 255, 255, 0.1); color: #ef6a5b;
transform: translateY(-3px); border-color: rgba(239, 106, 91, 0.45);
background: #fff4ef;
} }
/* ========== 装饰元素 ========== */ .hero-stats {
.hero-decoration { display: grid;
position: absolute; grid-template-columns: repeat(3, minmax(0, 1fr));
width: 400px; gap: 14px;
height: 400px; max-width: 520px;
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 50%;
animation: float 8s ease-in-out infinite;
} }
.hero-decoration-1 { .hero-stat {
top: -100px; padding: 18px 16px;
right: -100px; border-radius: 22px;
background: rgba(255, 255, 255, 0.72);
border: 1px solid rgba(228, 208, 190, 0.88);
display: flex;
flex-direction: column;
gap: 6px;
} }
.hero-decoration-2 { .hero-stat strong {
bottom: -150px; font-size: 28px;
left: -150px; line-height: 1;
width: 300px; color: #211d18;
height: 300px;
animation-delay: 2s;
} }
/* ========== 渐变遮罩 ========== */ .hero-stat span {
.hero-overlay { font-size: 13px;
position: absolute; color: #7d7267;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at center, transparent 0%, rgba(102, 126, 234, 0.3) 100%);
} }
/* ========== 响应式设计 ========== */ .hero-stage {
@media (max-width: 768px) { display: flex;
.hero-section { flex-direction: column;
height: 500px; gap: 16px;
justify-content: center;
}
.hero-stage-card {
border-radius: 28px;
border: 1px solid rgba(229, 210, 192, 0.9);
background: rgba(255, 255, 255, 0.72);
box-shadow: 0 18px 36px rgba(66, 43, 27, 0.08);
}
.hero-stage-main {
padding: 28px;
min-height: 280px;
display: flex;
flex-direction: column;
justify-content: space-between;
background:
radial-gradient(circle at top right, rgba(244, 178, 84, 0.2), transparent 35%),
linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(255, 248, 241, 0.94));
}
.hero-stage-topline {
display: flex;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.hero-stage-label,
.hero-stage-badge {
padding: 8px 12px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
}
.hero-stage-label {
background: rgba(239, 106, 91, 0.12);
color: #d84e3f;
}
.hero-stage-badge {
background: rgba(47, 103, 81, 0.12);
color: #2f6751;
}
.hero-stage-main h2 {
margin: 0;
font-size: 30px;
line-height: 1.2;
color: #211d18;
}
.hero-stage-main p {
margin: 0;
color: #6f665d;
line-height: 1.9;
}
.hero-stage-pills {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.hero-stage-pills span {
padding: 9px 14px;
border-radius: 999px;
background: #fff;
border: 1px solid rgba(229, 210, 192, 0.85);
color: #594c40;
font-size: 13px;
font-weight: 600;
}
.hero-stage-stack {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
}
.hero-stage-mini {
display: grid;
grid-template-columns: 46px minmax(0, 1fr);
gap: 14px;
align-items: start;
padding: 18px 20px;
}
.hero-stage-icon {
width: 46px;
height: 46px;
border-radius: 16px;
background: linear-gradient(135deg, #fff3ed 0%, #ffe2d5 100%);
color: #e35c4e;
display: grid;
place-items: center;
font-size: 20px;
}
.hero-stage-mini h3 {
margin: 0 0 6px;
font-size: 17px;
color: #211d18;
}
.hero-stage-mini p {
margin: 0;
color: #776c60;
line-height: 1.75;
font-size: 14px;
}
@media (max-width: 1200px) {
.hero-shell {
grid-template-columns: 1fr;
padding: 34px;
} }
.hero-title { .hero-title {
font-size: 36px; max-width: none;
}
.hero-subtitle {
font-size: 16px;
}
.hero-buttons {
flex-direction: column;
gap: 15px;
}
.btn-hero-primary,
.btn-hero-secondary {
padding: 14px 30px;
font-size: 16px;
width: 100%;
max-width: 300px;
} }
} }
/* ========== 暗黑模式 ========== */ @media (max-width: 768px) {
@media (prefers-color-scheme: dark) { .hero {
.hero-section { padding: 20px 12px 10px;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); }
.hero-shell {
padding: 24px 18px;
border-radius: 28px;
}
.hero-title {
font-size: 34px;
}
.hero-description {
font-size: 15px;
}
.hero-search-box {
padding: 10px 12px;
}
.hero-search-input.ant-input-affix-wrapper {
height: 52px;
}
.hero-stats {
grid-template-columns: 1fr;
max-width: none;
}
.hero-stage-main {
min-height: auto;
padding: 22px;
}
.hero-stage-main h2 {
font-size: 24px;
} }
} }

View File

@@ -1,76 +1,149 @@
import { Input, Button, Tag } from 'antd'; import { Input, Button, Tag } from 'antd';
import { SearchOutlined, CameraOutlined } from '@ant-design/icons'; import {
import './Hero.css'; SearchOutlined,
CameraOutlined,
const Hero = () => { ArrowRightOutlined,
const recommendTags = ['地产', '医美', '旅游', '汽车', '价值点', '美陈', '电商', '画册', '大寒']; ThunderboltOutlined,
SafetyCertificateOutlined,
return ( ClockCircleOutlined,
<section className="hero"> } from '@ant-design/icons';
<div className="hero-background"> import { useNavigate } from 'react-router-dom';
{/* Winter Scene Illustration */} import './Hero.css';
<div className="hero-scene">
<div className="snowflakes"> const recommendTags = ['活动', '医美', '科技', '包装', '直播', '邀请函'];
{[...Array(20)].map((_, i) => ( const heroStats = [
<div key={i} className="snowflake" style={{ { value: '24h', label: '持续更新' },
left: `${Math.random() * 100}%`, { value: '原图', label: '即时交付' },
animationDelay: `${Math.random() * 5}s`, { value: '在线', label: '支付下载' },
animationDuration: `${3 + Math.random() * 4}s` ];
}}></div> const stageHighlights = [
))} {
</div> icon: <ThunderboltOutlined />,
title: '热门原图',
{/* Left Side - Title */} description: '按场景、风格和行业快速找图,减少反复沟通。',
<div className="hero-title-area"> },
<h1 className="hero-main-title">这个冬天</h1> {
<h2 className="hero-sub-title">相约雪山</h2> icon: <SafetyCertificateOutlined />,
<p className="hero-english">APPOINTMENT AT SNOWMOUNTAIN</p> title: '支付后交付',
</div> description: '下单、支付、下载一条链路走通,用户体验更顺。',
},
{/* Decorative Elements */} {
<div className="hero-decorations"> icon: <ClockCircleOutlined />,
<div className="mountain mountain-1"></div> title: '最新上传',
<div className="mountain mountain-2"></div> description: '设计师持续上新,支持详情页预览和作品编号追踪。',
<div className="tree tree-1">🌲</div> },
<div className="tree tree-2">🌲</div> ];
<div className="tree tree-3">🎄</div>
<div className="snowman"></div> const Hero = () => {
<div className="people people-1">🎿</div> const navigate = useNavigate();
<div className="people people-2">🛷</div>
</div> const scrollToHotWorks = () => {
</div> document.getElementById('home-hot-works')?.scrollIntoView({
</div> behavior: 'smooth',
block: 'start',
<div className="hero-content"> });
<h2 className="hero-slogan">有所想不如有所享</h2> };
<div className="hero-search-box"> return (
<Input <section className="hero">
size="large" <div className="hero-shell">
placeholder="搜索作品或编号" <div className="hero-copy">
className="hero-search-input" <span className="hero-eyebrow">图汇精选素材库</span>
suffix={ <h1 className="hero-title">
<div className="search-actions"> 把灵感整理成
<CameraOutlined className="camera-icon" /> <span>可直接下载的设计资产</span>
<Button type="primary" shape="circle" icon={<SearchOutlined />} className="search-btn" /> </h1>
</div> <p className="hero-description">
} 原图背景电商图活动物料统一归档预览支付下载三步走通
/> 让客户从找图到拿到成品更省心
</div> </p>
<div className="hero-tags"> <div className="hero-search-panel">
<span className="tags-label">推荐搜索</span> <div className="hero-search-box">
{recommendTags.map(tag => ( <Input
<Tag key={tag} className="recommend-tag">{tag}</Tag> size="large"
))} placeholder="搜索作品标题、作品编号或场景关键词"
</div> className="hero-search-input"
prefix={<SearchOutlined className="hero-search-prefix" />}
<div className="hero-designer"> suffix={<CameraOutlined className="hero-search-camera" />}
设计师M.A />
</div> </div>
</div> <div className="hero-action-row">
</section> <Button
); type="primary"
}; size="large"
className="hero-primary-btn"
export default Hero; icon={<ArrowRightOutlined />}
onClick={scrollToHotWorks}
>
浏览热门作品
</Button>
<Button
size="large"
className="hero-secondary-btn"
onClick={() => navigate('/upload-guide')}
>
上传指南
</Button>
</div>
</div>
<div className="hero-tags">
<span className="hero-tags-label">推荐分类</span>
{recommendTags.map((tag) => (
<Tag
key={tag}
className="hero-tag"
onClick={() => navigate(`/category/${tag}`)}
>
{tag}
</Tag>
))}
</div>
<div className="hero-stats">
{heroStats.map((item) => (
<div key={item.label} className="hero-stat">
<strong>{item.value}</strong>
<span>{item.label}</span>
</div>
))}
</div>
</div>
<div className="hero-stage">
<div className="hero-stage-card hero-stage-main">
<div className="hero-stage-topline">
<span className="hero-stage-label">本周趋势</span>
<span className="hero-stage-badge">精选专题</span>
</div>
<h2>更像一个能直接成交的素材站而不是简单图库</h2>
<p>
首页负责承接搜索意图详情页负责转化下载动作让用户一眼知道
这里能找能买能下
</p>
<div className="hero-stage-pills">
<span>高清原图</span>
<span>在线支付</span>
<span>作品详情页</span>
</div>
</div>
<div className="hero-stage-stack">
{stageHighlights.map((item) => (
<div key={item.title} className="hero-stage-card hero-stage-mini">
<div className="hero-stage-icon">{item.icon}</div>
<div>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
</div>
))}
</div>
</div>
</div>
</section>
);
};
export default Hero;

View File

@@ -1,90 +1,119 @@
/* ========== 作品区块整体样式 ========== */
.works-section { .works-section {
padding: 60px 20px;
max-width: 1400px; max-width: 1400px;
margin: 0 auto; margin: 0 auto;
animation: fadeIn 0.8s ease-out; padding: 54px 20px;
}
.works-loading {
display: flex;
justify-content: center;
align-items: center;
min-height: 220px;
border-radius: 28px;
background: rgba(255, 255, 255, 0.72);
border: 1px solid rgba(231, 218, 207, 0.85);
} }
/* ========== 区块标题 ========== */
.section-header { .section-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: flex-end;
margin-bottom: 40px; gap: 24px;
padding: 0 10px; margin-bottom: 28px;
}
.section-copy {
max-width: 760px;
}
.section-kicker {
display: inline-flex;
margin-bottom: 12px;
padding: 7px 12px;
border-radius: 999px;
background: rgba(239, 106, 91, 0.1);
color: #d84e3f;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
} }
.section-title { .section-title {
font-size: 32px; margin: 0;
font-weight: 700; font-size: clamp(28px, 4vw, 38px);
color: #2d3436; line-height: 1.1;
position: relative; color: #221d18;
padding-left: 20px;
animation: slideIn 0.6s ease-out;
} }
.section-title::before { .section-description {
content: ''; margin: 12px 0 0;
position: absolute; font-size: 15px;
left: 0; line-height: 1.85;
top: 50%; color: #756b61;
transform: translateY(-50%);
width: 6px;
height: 32px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 3px;
} }
.view-more { .section-summary {
color: #667eea;
font-size: 16px;
font-weight: 600;
text-decoration: none;
transition: all 0.3s ease;
display: flex; display: flex;
align-items: center; flex-wrap: wrap;
gap: 6px; gap: 10px;
padding: 8px 16px; justify-content: flex-end;
border-radius: 20px;
background: rgba(102, 126, 234, 0.1);
} }
.view-more:hover { .section-pill {
background: rgba(102, 126, 234, 0.2); padding: 10px 14px;
transform: translateX(5px); border-radius: 999px;
background: rgba(255, 255, 255, 0.84);
border: 1px solid rgba(232, 220, 209, 0.9);
color: #564a40;
font-size: 13px;
font-weight: 600;
}
.section-pill-accent {
background: linear-gradient(135deg, #fff0e7 0%, #ffe1d7 100%);
color: #d84e3f;
} }
/* ========== 作品网格布局 ========== */
.works-grid { .works-grid {
animation: scaleIn 0.8s ease-out; animation: fadeIn 0.6s ease-out;
} }
/* ========== 作品卡片 ========== */ .work-card.ant-card {
.work-card {
border-radius: 16px;
overflow: hidden; overflow: hidden;
border: none; border: 1px solid rgba(233, 221, 212, 0.92);
background: white; border-radius: 24px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); background: rgba(255, 255, 255, 0.94);
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); box-shadow: 0 18px 40px rgba(58, 38, 23, 0.08);
cursor: pointer; transition: transform 0.28s ease, box-shadow 0.28s ease, border-color 0.28s ease;
animation: fadeIn 0.6s ease-out; animation: fadeIn 0.6s ease-out;
animation-delay: var(--delay);
animation-fill-mode: both; animation-fill-mode: both;
} }
.work-card:hover { .work-card.ant-card:hover {
transform: translateY(-10px) scale(1.02); transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); border-color: rgba(239, 106, 91, 0.3);
box-shadow: 0 26px 48px rgba(58, 38, 23, 0.14);
}
.work-card .ant-card-body {
padding: 20px;
} }
/* ========== 作品图片容器 ========== */
.work-image { .work-image {
position: relative; position: relative;
width: 100%; height: 290px;
height: 280px;
overflow: hidden; overflow: hidden;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); background: linear-gradient(135deg, #f7ede2 0%, #efe2d4 100%);
}
.work-image::after {
content: '';
position: absolute;
inset: auto 0 0;
height: 40%;
background: linear-gradient(180deg, transparent, rgba(29, 22, 17, 0.28));
pointer-events: none;
} }
.work-image img { .work-image img {
@@ -95,181 +124,185 @@
} }
.work-card:hover .work-image img { .work-card:hover .work-image img {
transform: scale(1.1); transform: scale(1.06);
} }
/* ========== 预览遮罩层 ========== */ .work-image-overlay {
.work-preview {
position: absolute; position: absolute;
top: 0; inset: auto 18px 18px 18px;
left: 0; z-index: 2;
right: 0;
bottom: 0;
background: rgba(102, 126, 234, 0.8);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: space-between;
gap: 10px;
padding: 12px 14px;
border-radius: 16px;
background: rgba(28, 24, 20, 0.58);
color: #fff;
font-size: 14px;
font-weight: 600;
opacity: 0; opacity: 0;
transition: all 0.3s ease; transform: translateY(10px);
transition: opacity 0.25s ease, transform 0.25s ease;
} }
.work-card:hover .work-preview { .work-card:hover .work-image-overlay {
opacity: 1; opacity: 1;
transform: translateY(0);
} }
.preview-icon { .work-price-badge {
font-size: 48px; position: absolute;
transform: scale(0.5); top: 16px;
transition: transform 0.3s ease; right: 16px;
z-index: 2;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 250, 244, 0.92);
border: 1px solid rgba(233, 218, 207, 0.92);
color: #d84e3f;
font-size: 14px;
font-weight: 700;
backdrop-filter: blur(10px);
} }
.work-card:hover .preview-icon {
transform: scale(1);
}
/* ========== 作品信息 ========== */
.work-info { .work-info {
padding: 20px; display: flex;
background: white; flex-direction: column;
gap: 14px;
}
.work-topline {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.work-category.ant-tag {
margin: 0;
padding: 6px 12px;
border-radius: 999px;
border: 1px solid rgba(226, 209, 196, 0.9);
background: #fff9f4;
color: #7b6655;
font-size: 12px;
font-weight: 700;
}
.work-id {
color: #a09083;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.04em;
} }
.work-title { .work-title {
font-size: 16px; margin: 0;
font-weight: 600; color: #241e19;
color: #2d3436; font-size: 18px;
margin-bottom: 12px; line-height: 1.45;
line-height: 1.5; font-weight: 700;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
transition: color 0.3s ease;
} }
.work-card:hover .work-title {
color: #667eea;
}
/* ========== 作品元数据 ========== */
.work-meta { .work-meta {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
margin-bottom: 12px;
flex-wrap: wrap; flex-wrap: wrap;
} }
.work-designer { .work-designer {
font-size: 13px; font-size: 13px;
color: #636e72; color: #72675d;
font-weight: 500; font-weight: 600;
} }
.work-level { .work-level.ant-tag {
margin: 0;
padding: 4px 10px;
border-radius: 999px;
background: transparent;
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 700;
padding: 3px 10px;
border-radius: 12px;
border: 1px solid;
} }
.work-level-text { .work-level-text {
font-size: 12px; font-size: 12px;
color: #b2bec3; color: #a29488;
} }
/* ========== 作品价格 ========== */ .work-bottom {
.work-price {
font-size: 20px;
font-weight: 700;
color: #ff5a5a;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; justify-content: space-between;
gap: 14px;
padding-top: 6px;
border-top: 1px solid rgba(239, 230, 222, 0.95);
} }
.work-price::before { .work-price {
content: '¥'; color: #db5748;
font-size: 14px; font-size: 24px;
font-weight: 800;
line-height: 1;
} }
/* ========== 卡片延迟动画 ========== */ .work-cta {
.work-card:nth-child(1) { animation-delay: 0.05s; } display: inline-flex;
.work-card:nth-child(2) { animation-delay: 0.1s; } align-items: center;
.work-card:nth-child(3) { animation-delay: 0.15s; } gap: 6px;
.work-card:nth-child(4) { animation-delay: 0.2s; } color: #6d6258;
.work-card:nth-child(5) { animation-delay: 0.25s; } font-size: 13px;
.work-card:nth-child(6) { animation-delay: 0.3s; } font-weight: 700;
.work-card:nth-child(7) { animation-delay: 0.35s; } }
.work-card:nth-child(8) { animation-delay: 0.4s; }
.work-card:nth-child(9) { animation-delay: 0.45s; } .work-card:hover .work-cta {
color: #ef6a5b;
}
/* ========== 加载状态 ========== */
.works-section .ant-spin { .works-section .ant-spin {
color: #667eea; color: #ef6a5b;
} }
.works-section .ant-spin-text { .works-section .ant-spin-text {
color: #636e72; color: #756b61;
font-size: 14px;
margin-top: 10px; margin-top: 10px;
} }
/* ========== 空状态 ========== */ @media (max-width: 900px) {
.works-empty { .section-header {
text-align: center; flex-direction: column;
padding: 60px 20px; align-items: flex-start;
color: #b2bec3; }
.section-summary {
justify-content: flex-start;
}
} }
.works-empty-icon {
font-size: 64px;
margin-bottom: 20px;
opacity: 0.5;
}
.works-empty-text {
font-size: 16px;
}
/* ========== 响应式设计 ========== */
@media (max-width: 768px) { @media (max-width: 768px) {
.works-section { .works-section {
padding: 40px 15px; padding: 42px 12px;
} }
.section-title { .section-title {
font-size: 24px; font-size: 28px;
} }
.work-image { .section-description {
height: 200px;
}
.work-title {
font-size: 14px; font-size: 14px;
} }
.work-price {
font-size: 18px;
}
}
/* ========== 暗黑模式 ========== */ .work-image {
@media (prefers-color-scheme: dark) { height: 250px;
.work-card {
background: #2d2d44;
} }
.work-title { .work-card .ant-card-body {
color: #e0e0e0; padding: 18px;
}
.work-designer {
color: #a0a0a0;
}
.work-card:hover .work-title {
color: #667eea;
} }
} }

View File

@@ -1,120 +1,156 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Card, Tag, Row, Col, Spin } from 'antd'; import { Card, Tag, Row, Col, Spin } from 'antd';
import { RightOutlined } from '@ant-design/icons'; import { RightOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { getWorksList } from '../api/works'; import { getWorksList } from '../api/works';
import { API_CONFIG } from '../utils/config'; import { API_CONFIG } from '../utils/config';
import './Works.css'; import './Works.css';
const Works = ({ title, type, categoryFilter }) => { const sectionPresets = {
const navigate = useNavigate(); hot: {
const [works, setWorks] = useState([]); kicker: '精选推荐',
const [loading, setLoading] = useState(true); description: '优先展示更适合首页承接咨询和转化的热门素材,适合直接跳详情页成交。',
badge: '本周热度',
useEffect(() => { },
loadWorks(); new: {
}, [type, categoryFilter]); kicker: '最新上架',
description: '设计师最新上传内容集中展示,适合让站点保持“持续更新”的活跃感。',
const loadWorks = async () => { badge: '持续更新',
setLoading(true); },
const result = await getWorksList(1, 9, categoryFilter || ''); };
setLoading(false);
const Works = ({ title, type, categoryFilter, sectionId }) => {
if (result.success) { const navigate = useNavigate();
setWorks(result.data.items || []); const [works, setWorks] = useState([]);
} const [loading, setLoading] = useState(true);
};
useEffect(() => {
// 使用 Picsum 随机图片(更稳定) loadWorks();
const getImageUrl = (id, width = 400, height = 300) => { }, [type, categoryFilter]);
return `https://picsum.photos/seed/${id}/${width}/${height}`;
}; const loadWorks = async () => {
setLoading(true);
const getLevelColor = (level) => { const result = await getWorksList(1, 9, categoryFilter || '');
const colors = { setLoading(false);
1: '#999',
2: '#74b9ff', if (result.success) {
3: '#00b894', setWorks(result.data.items || []);
4: '#6c5ce7', } else {
5: '#e17055', setWorks([]);
6: '#ff5a5a', }
}; };
return colors[level] || '#999';
}; const getImageUrl = (id, width = 480, height = 340) => {
return `https://picsum.photos/seed/${id}/${width}/${height}`;
// 点击卡片,跳转到详情页 };
const handleCardClick = (work) => {
navigate(`/detail/${work.id}`); const getLevelColor = (level) => {
}; const colors = {
1: '#8b847d',
if (loading) { 2: '#5f9fd2',
return ( 3: '#2f8f73',
<section className="works-section"> 4: '#9a5be0',
<div style={{ textAlign: 'center', padding: '40px 0' }}> 5: '#d46b40',
<Spin size="large" tip="加载中..." /> 6: '#e0564a',
</div> };
</section> return colors[level] || '#8b847d';
); };
}
const handleCardClick = (work) => {
return ( navigate(`/detail/${work.id}`);
<section className="works-section"> };
<div className="section-header">
<h2 className="section-title">{title}</h2> const sectionMeta = categoryFilter
<a href="#" className="view-more"> ? {
查看更多 <RightOutlined /> kicker: '分类浏览',
</a> description: `当前正在浏览「${categoryFilter}」分类下的作品,卡片会优先承接下载和详情页转化。`,
</div> badge: `${categoryFilter} 分类`,
}
<Row gutter={[16, 16]} className="works-grid"> : sectionPresets[type] || sectionPresets.hot;
{works.map((work, index) => (
<Col xs={12} sm={8} md={8} lg={8} xl={8} key={work.id}> if (loading) {
<Card return (
className="work-card" <section className="works-section" id={sectionId}>
hoverable <div className="works-loading">
style={{ '--delay': `${index * 0.05}s` }} <Spin size="large" tip="加载作品中..." />
onClick={() => handleCardClick(work)} </div>
cover={ </section>
<div className="work-image"> );
<img }
src={work.thumbnail_image ? `${API_CONFIG.baseURL}${work.thumbnail_image}` : getImageUrl(work.id)}
alt={work.title} return (
loading="lazy" <section className="works-section" id={sectionId}>
onError={(e) => { <div className="section-header">
e.target.src = getImageUrl(work.id); <div className="section-copy">
}} <span className="section-kicker">{sectionMeta.kicker}</span>
/> <h2 className="section-title">{title}</h2>
<div className="work-preview"> <p className="section-description">{sectionMeta.description}</p>
<span className="preview-icon">🔍</span> </div>
</div> <div className="section-summary">
</div> <span className="section-pill">{works.length} 张展示</span>
} <span className="section-pill section-pill-accent">{sectionMeta.badge}</span>
> </div>
<div className="work-info"> </div>
<h3 className="work-title">{work.title}</h3>
<div className="work-meta"> <Row gutter={[20, 20]} className="works-grid">
<span className="work-designer">{work.designer}</span> {works.map((work, index) => (
<Tag <Col xs={24} sm={12} lg={8} key={work.id}>
className="work-level" <Card
style={{ className="work-card"
color: getLevelColor(work.level), hoverable
borderColor: getLevelColor(work.level) style={{ '--delay': `${index * 0.05}s` }}
}} onClick={() => handleCardClick(work)}
> cover={
Lv.{work.level} <div className="work-image">
</Tag> <img
<span className="work-level-text">{work.level_text}</span> src={work.thumbnail_image ? `${API_CONFIG.baseURL}${work.thumbnail_image}` : getImageUrl(work.id)}
</div> alt={work.title}
<div className="work-price" style={{ marginTop: 8, color: '#ff5a5a', fontWeight: 'bold', fontSize: 16 }}> loading="lazy"
¥{work.price} onError={(e) => {
</div> e.target.src = getImageUrl(work.id);
</div> }}
</Card> />
</Col> <div className="work-image-overlay">
))} <span>查看详情</span>
</Row> <RightOutlined />
</section> </div>
); <div className="work-price-badge">¥{work.price}</div>
}; </div>
}
export default Works; >
<div className="work-info">
<div className="work-topline">
<Tag className="work-category">{work.category || '设计素材'}</Tag>
<span className="work-id">#{work.id}</span>
</div>
<h3 className="work-title">{work.title}</h3>
<div className="work-meta">
<span className="work-designer">{work.designer}</span>
<Tag
className="work-level"
style={{
color: getLevelColor(work.level),
borderColor: getLevelColor(work.level),
}}
>
Lv.{work.level}
</Tag>
<span className="work-level-text">{work.level_text}</span>
</div>
<div className="work-bottom">
<div className="work-price">¥{work.price}</div>
<span className="work-cta">
进入详情
<RightOutlined />
</span>
</div>
</div>
</Card>
</Col>
))}
</Row>
</section>
);
};
export default Works;

View File

@@ -1,44 +1,65 @@
/* ========== 全局样式重置 ========== */ :root {
--brand: #ef6a5b;
--brand-strong: #d84e3f;
--brand-soft: #fff0e7;
--ink: #241d17;
--text: #5f5449;
--muted: #8c8073;
--surface: rgba(255, 255, 255, 0.94);
--surface-soft: #fffaf5;
--line: rgba(233, 221, 212, 0.95);
--shadow: 0 22px 48px rgba(66, 42, 26, 0.08);
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
} }
html {
scroll-behavior: smooth;
}
body { body {
font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Arial, sans-serif; font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Arial, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 1.6; line-height: 1.6;
color: #2d3436; color: var(--ink);
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); background:
radial-gradient(circle at top left, rgba(239, 106, 91, 0.08), transparent 22%),
radial-gradient(circle at top right, rgba(244, 178, 84, 0.1), transparent 20%),
linear-gradient(180deg, #fffdf9 0%, #f8f2eb 100%);
min-height: 100vh; min-height: 100vh;
} }
/* ========== 滚动条美化 ========== */ a {
color: inherit;
}
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 8px;
height: 8px; height: 8px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: #f1f1f1; background: #f4ede6;
border-radius: 4px; border-radius: 999px;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(180deg, #ef6a5b 0%, #d84e3f 100%);
border-radius: 4px; border-radius: 999px;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); background: linear-gradient(180deg, #e06152 0%, #cb4638 100%);
} }
/* ========== 通用动画 ========== */
@keyframes fadeIn { @keyframes fadeIn {
from { from {
opacity: 0; opacity: 0;
transform: translateY(20px); transform: translateY(16px);
} }
to { to {
opacity: 1; opacity: 1;
@@ -49,7 +70,7 @@ body {
@keyframes slideIn { @keyframes slideIn {
from { from {
opacity: 0; opacity: 0;
transform: translateX(-30px); transform: translateX(-24px);
} }
to { to {
opacity: 1; opacity: 1;
@@ -60,187 +81,10 @@ body {
@keyframes scaleIn { @keyframes scaleIn {
from { from {
opacity: 0; opacity: 0;
transform: scale(0.9); transform: scale(0.96);
} }
to { to {
opacity: 1; opacity: 1;
transform: scale(1); transform: scale(1);
} }
} }
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
@keyframes shimmer {
0% {
background-position: -1000px 0;
}
100% {
background-position: 1000px 0;
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
/* ========== 通用类 ========== */
.fade-in {
animation: fadeIn 0.6s ease-out;
}
.slide-in {
animation: slideIn 0.6s ease-out;
}
.scale-in {
animation: scaleIn 0.6s ease-out;
}
.float {
animation: float 3s ease-in-out infinite;
}
/* ========== 按钮样式 ========== */
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
.btn-primary:active {
transform: translateY(0);
}
/* ========== 卡片阴影 ========== */
.card-shadow {
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.card-shadow:hover {
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
transform: translateY(-5px);
}
/* ========== 渐变背景 ========== */
.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.gradient-text {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* ========== 玻璃拟态 ========== */
.glass {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* ========== 加载动画 ========== */
.loading-shimmer {
background: linear-gradient(
90deg,
#f0f0f0 0%,
#e0e0e0 20%,
#f0f0f0 40%,
#f0f0f0 100%
);
background-size: 1000px 100%;
animation: shimmer 2s infinite;
}
/* ========== 标签样式 ========== */
.tag-gradient {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
/* ========== 输入框美化 ========== */
.input-modern {
width: 100%;
padding: 12px 20px;
border: 2px solid #e0e0e0;
border-radius: 12px;
font-size: 16px;
transition: all 0.3s ease;
background: white;
}
.input-modern:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
/* ========== 分割线 ========== */
.divider-gradient {
height: 2px;
border: none;
background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #667eea 100%);
background-size: 200% 100%;
animation: shimmer 3s infinite;
}
/* ========== 响应式断点 ========== */
@media (max-width: 768px) {
body {
font-size: 14px;
}
.btn-primary {
padding: 10px 20px;
font-size: 14px;
}
}
/* ========== 暗黑模式支持 ========== */
@media (prefers-color-scheme: dark) {
body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #e0e0e0;
}
.input-modern {
background: #2d2d44;
border-color: #404060;
color: #e0e0e0;
}
.input-modern:focus {
border-color: #667eea;
}
}

View File

@@ -6,19 +6,19 @@ import Works from '../components/Works';
import Designers from '../components/Designers'; import Designers from '../components/Designers';
import Footer from '../components/Footer'; import Footer from '../components/Footer';
function Home() { function Home() {
return ( return (
<div className="home-page" style={{ paddingTop: '70px' }}> <div className="home-page" style={{ paddingTop: '70px' }}>
<Header /> <Header />
<Hero /> <Hero />
<Categories /> <Categories />
<Festival /> <Festival />
<Works title="热门推荐" type="hot" /> <Works title="热门推荐" type="hot" sectionId="home-hot-works" />
<Works title="最新上传" type="new" /> <Works title="最新上传" type="new" sectionId="home-new-works" />
<Designers /> <Designers />
<Footer /> <Footer />
</div> </div>
); );
} }
export default Home; export default Home;

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,23 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { Button, Tag, message, Input, Modal, Spin } from 'antd'; import { Button, Tag, message, Input, Modal, Spin } from 'antd';
import { HeartOutlined, HeartFilled, DownloadOutlined, ShareAltOutlined, EyeOutlined, PlusOutlined, SearchOutlined } from '@ant-design/icons'; import {
import { QRCodeSVG } from 'qrcode.react'; HeartOutlined,
import { getWorkDetail } from '../api/works'; HeartFilled,
import { createOrder } from '../api/orders'; DownloadOutlined,
import { createPayment, queryPaymentStatus } from '../api/payment'; ShareAltOutlined,
import { downloadWork } from '../api/download'; EyeOutlined,
import { isLoggedIn } from '../api/auth'; PlusOutlined,
import { API_CONFIG } from '../utils/config'; SearchOutlined,
CheckCircleFilled,
} from '@ant-design/icons';
import { QRCodeSVG } from 'qrcode.react';
import { getWorkDetail } from '../api/works';
import { createOrder } from '../api/orders';
import { createPayment, queryPaymentStatus } from '../api/payment';
import { downloadWork } from '../api/download';
import { isLoggedIn } from '../api/auth';
import { API_CONFIG } from '../utils/config';
import Header from '../components/Header'; import Header from '../components/Header';
import Footer from '../components/Footer'; import Footer from '../components/Footer';
import './WorkDetail.css'; import './WorkDetail.css';
@@ -34,7 +43,7 @@ const parseTags = (rawTags) => {
const parsed = JSON.parse(trimmed); const parsed = JSON.parse(trimmed);
return Array.isArray(parsed) ? parsed.filter(Boolean) : []; return Array.isArray(parsed) ? parsed.filter(Boolean) : [];
} catch { } catch {
// 回退到逗号分隔解析,避免详情页白屏 // 兼容历史逗号分隔格式,避免详情页白屏
} }
} }
@@ -50,452 +59,507 @@ const getDownloadFilename = (work) => {
const ext = extMatch ? extMatch[0] : '.jpg'; const ext = extMatch ? extMatch[0] : '.jpg';
return `${work?.title || 'work'}${ext}`; return `${work?.title || 'work'}${ext}`;
}; };
const WorkDetail = () => { const WorkDetail = () => {
const { id } = useParams(); const { id } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const [collected, setCollected] = useState(false); const [collected, setCollected] = useState(false);
const [followed, setFollowed] = useState(false); const [followed, setFollowed] = useState(false);
const [downloadModalOpen, setDownloadModalOpen] = useState(false); const [downloadModalOpen, setDownloadModalOpen] = useState(false);
const [workData, setWorkData] = useState(null); const [workData, setWorkData] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [purchasing, setPurchasing] = useState(false); const [purchasing, setPurchasing] = useState(false);
const [paymentUrl, setPaymentUrl] = useState(''); const [paymentUrl, setPaymentUrl] = useState('');
const [orderNumber, setOrderNumber] = useState(''); const [orderNumber, setOrderNumber] = useState('');
const [checkingPayment, setCheckingPayment] = useState(false); const [checkingPayment, setCheckingPayment] = useState(false);
// 加载作品详情 useEffect(() => {
useEffect(() => { loadWorkDetail();
loadWorkDetail(); }, [id]);
}, [id]);
const loadWorkDetail = async () => {
const loadWorkDetail = async () => { setLoading(true);
setLoading(true); const result = await getWorkDetail(id);
const result = await getWorkDetail(id); setLoading(false);
setLoading(false);
if (result.success) {
if (result.success) { setWorkData(result.data);
setWorkData(result.data); } else {
} else { message.error('加载作品详情失败');
message.error('加载作品详情失败'); }
} };
};
const getImageUrl = (workId) => {
// 使用 Picsum 图片作为后备 return `https://picsum.photos/seed/${workId}/800/1200`;
const getImageUrl = (workId) => { };
return `https://picsum.photos/seed/${workId}/800/1200`;
}; const getRelatedImageUrl = (workId) => {
return `https://picsum.photos/seed/related${workId}/400/600`;
const getRelatedImageUrl = (workId) => { };
return `https://picsum.photos/seed/related${workId}/400/600`;
}; const relatedWorks = [
{
const relatedWorks = [ id: '20',
{ title: '相关设计作品 1',
id: '20', image: getRelatedImageUrl(20),
title: '相关设计作品1', designer: 'cestbon',
image: getRelatedImageUrl(20), level: 1,
designer: 'cestbon', levelName: '设计爱好者',
level: 1, },
levelName: '设计爱好者' {
}, id: '21',
{ title: '相关设计作品 2',
id: '21', image: getRelatedImageUrl(21),
title: '相关设计作品2', designer: '六十六号屯',
image: getRelatedImageUrl(21), level: 4,
designer: '六十六号屯', levelName: '资深设计师',
level: 4, },
levelName: '资深设计师' {
}, id: '22',
{ title: '相关设计作品 3',
id: '22', image: getRelatedImageUrl(22),
title: '相关设计作品3', designer: '扶摇',
image: getRelatedImageUrl(22), level: 3,
designer: '扶摇', levelName: '设计师',
level: 3, },
levelName: '设计师' ];
}
]; const handleCollect = () => {
setCollected(!collected);
const handleCollect = () => { message.success(collected ? '已取消收藏' : '收藏成功');
setCollected(!collected); };
message.success(collected ? '已取消收藏' : '收藏成功');
}; const handleFollow = () => {
setFollowed(!followed);
const handleFollow = () => { message.success(followed ? '已取消关注' : '关注成功');
setFollowed(!followed); };
message.success(followed ? '已取消关注' : '关注成功');
}; const handleDownload = async () => {
if (!isLoggedIn()) {
const handleDownload = async () => { message.warning('请先登录');
// 检查是否登录 return;
if (!isLoggedIn()) { }
message.warning('请先登录');
return;
}
// 尝试直接下载
const result = await downloadWork(id, getDownloadFilename(workData)); const result = await downloadWork(id, getDownloadFilename(workData));
// 如果需要购买,显示购买确认弹窗 if (!result.success && result.needPurchase) {
if (!result.success && result.needPurchase) { setDownloadModalOpen(true);
setDownloadModalOpen(true); }
} };
};
const confirmDownload = async () => {
const confirmDownload = async () => { setPurchasing(true);
setPurchasing(true);
const orderResult = await createOrder(id);
// 1. 创建订单 if (!orderResult.success) {
const orderResult = await createOrder(id); setPurchasing(false);
if (!orderResult.success) { message.error(orderResult.message);
setPurchasing(false); return;
message.error(orderResult.message); }
return;
} const order = orderResult.data;
setOrderNumber(order.order_no);
const order = orderResult.data;
setOrderNumber(order.order_no); const paymentResult = await createPayment(order.id);
setPurchasing(false);
// 2. 创建支付链接
const paymentResult = await createPayment(order.id); if (paymentResult.success) {
setPurchasing(false); setPaymentUrl(paymentResult.data.pay_url);
message.success('请扫描二维码完成支付');
if (paymentResult.success) { startPaymentStatusCheck(order.order_no);
setPaymentUrl(paymentResult.data.pay_url); } else {
message.success('请扫描二维码完成支付'); message.error(paymentResult.message);
// 开始轮询支付状态 setDownloadModalOpen(false);
startPaymentStatusCheck(order.order_no); }
} else { };
message.error(paymentResult.message);
setDownloadModalOpen(false); const startPaymentStatusCheck = (orderNum) => {
} setCheckingPayment(true);
}; const checkInterval = setInterval(async () => {
const result = await queryPaymentStatus(orderNum);
// 轮询检查支付状态 if (result.success) {
const startPaymentStatusCheck = (orderNum) => { if (result.data.local_status === 'paid' || result.data.remote_status === 'SUCCESS') {
setCheckingPayment(true); clearInterval(checkInterval);
const checkInterval = setInterval(async () => { setCheckingPayment(false);
const result = await queryPaymentStatus(orderNum); setDownloadModalOpen(false);
if (result.success) { setPaymentUrl('');
// 检查本地订单状态是否为已支付 message.success('支付成功,开始下载');
if (result.data.local_status === 'paid' || result.data.remote_status === 'SUCCESS') {
clearInterval(checkInterval);
setCheckingPayment(false);
setDownloadModalOpen(false);
setPaymentUrl('');
message.success('支付成功!开始下载...');
// 重新尝试下载
await downloadWork(id, getDownloadFilename(workData)); await downloadWork(id, getDownloadFilename(workData));
} }
} }
}, 3000); // 每3秒检查一次 }, 3000);
// 5分钟后停止检查 setTimeout(() => {
setTimeout(() => { clearInterval(checkInterval);
clearInterval(checkInterval); setCheckingPayment(false);
setCheckingPayment(false); }, 300000);
}, 300000); };
};
const handleModalClose = () => {
const handleModalClose = () => { setDownloadModalOpen(false);
setDownloadModalOpen(false); setPaymentUrl('');
setPaymentUrl(''); setOrderNumber('');
setOrderNumber(''); setCheckingPayment(false);
setCheckingPayment(false); };
};
const generateWorkNumber = (workId) => {
// 生成作品编号:半年前日期 + 当前时分秒 + 作品ID const now = new Date();
const generateWorkNumber = (workId) => { const sixMonthsAgo = new Date(now.getTime() - 180 * 24 * 60 * 60 * 1000);
const now = new Date(); const datePart = sixMonthsAgo.toISOString().slice(0, 10).replace(/-/g, '');
const sixMonthsAgo = new Date(now.getTime() - 180 * 24 * 60 * 60 * 1000); // 半年前 const timePart = now.toTimeString().slice(0, 8).replace(/:/g, '');
const datePart = sixMonthsAgo.toISOString().slice(0, 10).replace(/-/g, ''); // YYYYMMDD return `${datePart}${timePart}${workId}`;
const timePart = now.toTimeString().slice(0, 8).replace(/:/g, ''); // HHMMSS };
return `${datePart}${timePart}${workId}`;
}; const handleShare = () => {
navigator.clipboard.writeText(window.location.href);
const handleShare = () => { message.success('链接已复制到剪贴板');
navigator.clipboard.writeText(window.location.href); };
message.success('链接已复制到剪贴板');
}; const copyWorkId = () => {
const fullWorkNumber = generateWorkNumber(workData?.id || 0);
const copyWorkId = () => { navigator.clipboard.writeText(fullWorkNumber);
const fullWorkNumber = generateWorkNumber(workData?.id || 0); message.success('编号已复制');
navigator.clipboard.writeText(fullWorkNumber); };
message.success('编号已复制');
}; if (loading) {
return (
if (loading) { <div className="work-detail-page">
return ( <Header />
<div className="work-detail-page"> <div className="work-loading">
<Header /> <Spin size="large" tip="加载中..." />
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '60vh' }}> </div>
<Spin size="large" tip="加载中..." /> <Footer />
</div> </div>
<Footer /> );
</div> }
);
} if (!workData) {
return (
if (!workData) { <div className="work-detail-page">
return ( <Header />
<div className="work-detail-page"> <div className="work-loading">
<Header /> <p>作品不存在</p>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '60vh' }}> </div>
<p>作品不存在</p> <Footer />
</div> </div>
<Footer /> );
</div> }
);
}
// 解析标签
const tags = parseTags(workData.tags); const tags = parseTags(workData.tags);
const detailFacts = [
return ( { label: '分类', value: workData.category || '设计素材' },
<div className="work-detail-page"> { label: '设计师', value: workData.designer || '-' },
<Header /> { label: '等级', value: `Lv.${workData.level} ${workData.level_text}` },
{ label: '价格', value: `¥${workData.price}` },
<div className="work-detail-container"> ];
{/* 面包屑导航 */} const servicePromises = [
<div className="breadcrumb"> '支付成功后自动下载原图',
<a onClick={() => navigate('/')}>首页</a> '详情页支持预览与编号复制',
<span> / </span> '订单状态与支付状态可追踪',
<span className="current">{workData.title}</span> ];
</div>
return (
<div className="work-detail-content"> <div className="work-detail-page">
{/* 左侧:作品展示 */} <Header />
<div className="work-main">
<div className="work-image-wrapper"> <div className="work-detail-container">
<img <div className="breadcrumb">
src={workData.watermarked_image ? `${API_CONFIG.baseURL}${workData.watermarked_image}` : getImageUrl(id)} <a onClick={() => navigate('/')}>首页</a>
alt={workData.title} <span> / </span>
className="work-image" {workData.category ? (
/> <>
<a onClick={() => navigate(`/category/${workData.category}`)}>{workData.category}</a>
{/* 相关搜索标签 */} <span> / </span>
<div className="related-tags"> </>
<h4>相关搜索</h4> ) : null}
<div className="tags-list"> <span className="current">{workData.title}</span>
{tags.map((tag, index) => ( </div>
<Tag key={index} className="work-tag">{tag}</Tag>
))} <div className="work-detail-heading">
</div> <div className="detail-heading-copy">
</div> <span className="detail-kicker">
{workData.category || '设计素材'} · 支付后即时交付
{/* 猜你喜欢 */} </span>
<div className="related-works"> <h1 className="work-title">{workData.title}</h1>
<h3>猜你喜欢</h3> <p className="work-subtitle">
<div className="related-works-grid"> 适合活动海报电商主图背景素材等快速落地场景先看预览再决定是否下载原图
{relatedWorks.map(work => ( </p>
<div key={work.id} className="related-work-card" onClick={() => navigate(`/detail/${work.id}`)}> </div>
<div className="related-work-image"> <div className="detail-heading-stats">
<img src={work.image} alt={work.title} /> <div className="heading-stat">
<div className="related-work-overlay"> <strong>{workData.views}</strong>
<Button type="primary" size="small" icon={<DownloadOutlined />}> <span>浏览量</span>
源文件下载 </div>
</Button> <div className="heading-stat">
</div> <strong>{workData.collects}</strong>
</div> <span>收藏量</span>
<div className="related-work-info"> </div>
<h4>{work.title}</h4> <div className="heading-stat">
<div className="related-work-designer"> <strong>Lv.{workData.level}</strong>
<span className="designer-level">Lv.{work.level}</span> <span>{workData.level_text}</span>
<span className="designer-level-name">{work.levelName}</span> </div>
<p className="designer-name">{work.designer}</p> </div>
</div> </div>
</div>
</div> <div className="work-detail-content">
))} <div className="work-main">
</div> <div className="work-preview-card">
</div> <div className="work-preview-topline">
</div> <span className="preview-badge">水印预览</span>
</div> <span className="preview-badge subtle">可购买原图下载</span>
</div>
{/* 右侧:作品信息 */}
<div className="work-sidebar"> <div className="work-image-shell">
{/* 编号和复制 */} <img
<div className="work-id-section"> src={workData.watermarked_image ? `${API_CONFIG.baseURL}${workData.watermarked_image}` : getImageUrl(id)}
<span className="work-id">{generateWorkNumber(workData.id)}</span> alt={workData.title}
<Button type="link" size="small" onClick={copyWorkId}>复制</Button> className="work-image"
</div> />
</div>
{/* 作品标题 */}
<h1 className="work-title">{workData.title}</h1> <div className="preview-benefits">
{servicePromises.map((item) => (
{/* 作品信息 */} <div key={item} className="preview-benefit">
<div className="work-info-list"> <CheckCircleFilled />
<div className="work-info-item"> <span>{item}</span>
<span className="info-label">分类</span> </div>
<span className="info-value">{workData.category}</span> ))}
</div> </div>
<div className="work-info-item"> </div>
<span className="info-label">设计师</span>
<span className="info-value">{workData.designer}</span> <div className="detail-panel related-tags">
</div> <div className="panel-header">
<div className="work-info-item"> <h3>相关搜索</h3>
<span className="info-label">等级</span> <span>帮助用户继续筛选相似素材</span>
<span className="info-value">Lv.{workData.level} {workData.level_text}</span> </div>
</div> <div className="tags-list">
<div className="work-info-item"> {tags.length ? (
<span className="info-label">价格</span> tags.map((tag, index) => (
<span className="info-value" style={{ color: '#ff5a5a', fontWeight: 'bold' }}>¥{workData.price}</span> <Tag key={index} className="work-tag">
</div> {tag}
</div> </Tag>
))
{/* 操作按钮 */} ) : (
<div className="work-actions"> <span className="empty-hint">暂无标签后续上传时可补充关键词</span>
<Button )}
type="primary" </div>
size="large" </div>
block
icon={<DownloadOutlined />} <div className="detail-panel related-works">
onClick={handleDownload} <div className="panel-header">
className="download-btn" <h3>猜你喜欢</h3>
> <span>保持详情页浏览节奏减少单页跳出</span>
我要下载 </div>
</Button> <div className="related-works-grid">
<Button {relatedWorks.map((work) => (
size="large" <div key={work.id} className="related-work-card" onClick={() => navigate(`/detail/${work.id}`)}>
icon={collected ? <HeartFilled /> : <HeartOutlined />} <div className="related-work-image">
onClick={handleCollect} <img src={work.image} alt={work.title} />
className={`collect-btn ${collected ? 'collected' : ''}`} <div className="related-work-overlay">
/> <Button type="primary" size="small" icon={<DownloadOutlined />}>
<Button 查看详情
size="large" </Button>
icon={<ShareAltOutlined />} </div>
onClick={handleShare} </div>
className="share-btn" <div className="related-work-info">
/> <h4>{work.title}</h4>
</div> <div className="related-work-designer">
<span className="designer-level">Lv.{work.level}</span>
{/* 设计师信息 */} <span className="designer-level-name">{work.levelName}</span>
<div className="designer-card"> <p className="designer-name">{work.designer}</p>
<div className="designer-header"> </div>
<div className="designer-avatar"> </div>
<img src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${workData.designer}`} alt={workData.designer} /> </div>
</div> ))}
<div className="designer-info"> </div>
<div className="designer-name-level"> </div>
<span className="designer-level-badge">Lv.{workData.level}</span> </div>
<span className="designer-level-text">{workData.level_text}</span>
</div> <div className="work-sidebar">
<h3 className="designer-name">{workData.designer}</h3> <div className="price-card">
</div> <div className="work-id-section">
</div> <span className="work-id">{generateWorkNumber(workData.id)}</span>
<Button type="link" size="small" onClick={copyWorkId}>
<div className="designer-stats"> 复制编号
<div className="stat-item"> </Button>
<span className="stat-value">-</span> </div>
<span className="stat-label">作品</span>
</div> <div className="price-block">
<div className="stat-item"> <span className="price-label">当前下载价</span>
<span className="stat-value">-</span> <div className="price-value">¥{workData.price}</div>
<span className="stat-label">粉丝</span> </div>
</div>
</div> <div className="work-info-list">
{detailFacts.map((item) => (
<Button <div key={item.label} className="work-info-item">
type="primary" <span className="info-label">{item.label}</span>
block <span className={`info-value ${item.label === '价格' ? 'highlight' : ''}`}>
icon={<PlusOutlined />} {item.value}
onClick={handleFollow} </span>
className={`follow-btn ${followed ? 'followed' : ''}`} </div>
> ))}
{followed ? '已关注' : '关注'} </div>
</Button>
</div> <div className="work-actions">
<Button
{/* 搜索画板 */} type="primary"
<div className="board-search"> size="large"
<h4>搜索画板</h4> block
<Input icon={<DownloadOutlined />}
placeholder="搜索画板" onClick={handleDownload}
prefix={<SearchOutlined />} className="download-btn"
className="board-search-input" >
/> 立即下载
</div> </Button>
<Button
{/* 浏览统计 */} size="large"
<div className="work-stats"> icon={collected ? <HeartFilled /> : <HeartOutlined />}
<div className="stat-item"> onClick={handleCollect}
<EyeOutlined /> className={`icon-btn ${collected ? 'collected' : ''}`}
<span>{workData.views} 浏览</span> />
</div> <Button
<div className="stat-item"> size="large"
<HeartOutlined /> icon={<ShareAltOutlined />}
<span>{workData.collects} 收藏</span> onClick={handleShare}
</div> className="icon-btn"
</div> />
</div> </div>
</div> </div>
</div>
<div className="detail-panel designer-card">
{/* 购买确认弹窗 */} <div className="designer-header">
<Modal <div className="designer-avatar">
title={paymentUrl ? "扫码支付" : "购买作品"} <img
open={downloadModalOpen} src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${workData.designer}`}
onOk={paymentUrl ? null : confirmDownload} alt={workData.designer}
onCancel={handleModalClose} />
okText="确定购买" </div>
cancelText={paymentUrl ? "关闭" : "取消"} <div className="designer-info">
centered <div className="designer-name-level">
confirmLoading={purchasing} <span className="designer-level-badge">Lv.{workData.level}</span>
footer={paymentUrl ? [ <span className="designer-level-text">{workData.level_text}</span>
<Button key="close" onClick={handleModalClose}> </div>
关闭 <h3 className="designer-name">{workData.designer}</h3>
</Button> </div>
] : undefined} </div>
width={paymentUrl ? 450 : 416}
> <div className="designer-stats">
{!paymentUrl ? ( <div className="stat-item">
<div> <span className="stat-value">-</span>
<p>作品{workData.title}</p> <span className="stat-label">作品</span>
<p>价格<span style={{ color: '#ff5a5a', fontSize: '18px', fontWeight: 'bold' }}>¥{workData.price}</span></p> </div>
<p style={{ color: '#999', fontSize: '12px' }}>点击确定后将生成支付二维码</p> <div className="stat-item">
</div> <span className="stat-value">-</span>
) : ( <span className="stat-label">粉丝</span>
<div style={{ textAlign: 'center', padding: '20px 0' }}> </div>
<div style={{ </div>
background: '#fff',
padding: '20px', <Button
borderRadius: '8px', type="primary"
boxShadow: '0 2px 8px rgba(0,0,0,0.1)', block
display: 'inline-block' icon={<PlusOutlined />}
}}> onClick={handleFollow}
<QRCodeSVG className={`follow-btn ${followed ? 'followed' : ''}`}
value={paymentUrl} >
size={200} {followed ? '已关注' : '关注设计师'}
level="H" </Button>
includeMargin={true} </div>
/>
</div> <div className="detail-panel board-search">
<p style={{ marginTop: '20px', fontSize: '16px', fontWeight: 'bold' }}> <div className="panel-header compact">
订单金额<span style={{ color: '#ff5a5a' }}>¥{workData.price}</span> <h3>搜索画板</h3>
</p> <span>继续搜同类风格</span>
<p style={{ color: '#666', fontSize: '14px' }}> </div>
请使用微信或支付宝扫码支付 <Input
</p> placeholder="搜索画板关键词"
{checkingPayment && ( prefix={<SearchOutlined />}
<div style={{ marginTop: '15px' }}> className="board-search-input"
<Spin size="small" /> />
<span style={{ marginLeft: '10px', color: '#1890ff' }}>等待支付中...</span> </div>
</div>
)} <div className="detail-panel work-stats">
<p style={{ color: '#999', fontSize: '12px', marginTop: '15px' }}> <div className="panel-header compact">
订单号{orderNumber} <h3>作品动态</h3>
</p> <span>帮助用户判断热度</span>
<p style={{ color: '#999', fontSize: '12px' }}> </div>
支付完成后将自动开始下载 <div className="stats-grid">
</p> <div className="stat-row">
</div> <EyeOutlined />
)} <span>{workData.views} 次浏览</span>
</Modal> </div>
<div className="stat-row">
<Footer /> <HeartOutlined />
</div> <span>{workData.collects} 次收藏</span>
); </div>
}; </div>
</div>
export default WorkDetail; </div>
</div>
</div>
<Modal
title={paymentUrl ? '扫码支付' : '购买作品'}
open={downloadModalOpen}
onOk={paymentUrl ? null : confirmDownload}
onCancel={handleModalClose}
okText="确定购买"
cancelText={paymentUrl ? '关闭' : '取消'}
centered
confirmLoading={purchasing}
footer={
paymentUrl
? [
<Button key="close" onClick={handleModalClose}>
关闭
</Button>,
]
: undefined
}
width={paymentUrl ? 450 : 416}
>
{!paymentUrl ? (
<div>
<p>作品{workData.title}</p>
<p>
价格
<span style={{ color: '#ff5a5a', fontSize: '18px', fontWeight: 'bold' }}>
¥{workData.price}
</span>
</p>
<p style={{ color: '#999', fontSize: '12px' }}>点击确定后将生成支付二维码</p>
</div>
) : (
<div style={{ textAlign: 'center', padding: '20px 0' }}>
<div
style={{
background: '#fff',
padding: '20px',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
display: 'inline-block',
}}
>
<QRCodeSVG value={paymentUrl} size={200} level="H" includeMargin />
</div>
<p style={{ marginTop: '20px', fontSize: '16px', fontWeight: 'bold' }}>
订单金额<span style={{ color: '#ff5a5a' }}>¥{workData.price}</span>
</p>
<p style={{ color: '#666', fontSize: '14px' }}>请使用微信或支付宝扫码支付</p>
{checkingPayment && (
<div style={{ marginTop: '15px' }}>
<Spin size="small" />
<span style={{ marginLeft: '10px', color: '#1890ff' }}>等待支付中...</span>
</div>
)}
<p style={{ color: '#999', fontSize: '12px', marginTop: '15px' }}>订单号{orderNumber}</p>
<p style={{ color: '#999', fontSize: '12px' }}>支付完成后将自动开始下载</p>
</div>
)}
</Modal>
<Footer />
</div>
);
};
export default WorkDetail;