解决冲突
This commit is contained in:
@@ -25,7 +25,29 @@ async def public_list_menu_configs(
|
||||
)
|
||||
.order_by(MenuConfig.sort_order)
|
||||
)
|
||||
menus = result.scalars().all()
|
||||
all_menus = result.scalars().all()
|
||||
|
||||
# Filter menus based on user permissions
|
||||
# Show default menus + user-specific allowed menus (merged)
|
||||
user_allowed_paths = set(current_user.allowed_menus or [])
|
||||
|
||||
# Start with default menus
|
||||
allowed_ids = set()
|
||||
for menu in all_menus:
|
||||
if menu.is_default:
|
||||
allowed_ids.add(menu.id)
|
||||
if menu.parent_id:
|
||||
allowed_ids.add(menu.parent_id)
|
||||
|
||||
# Add user-specific allowed menus (merge with defaults)
|
||||
for menu in all_menus:
|
||||
if menu.path in user_allowed_paths:
|
||||
allowed_ids.add(menu.id)
|
||||
if menu.parent_id:
|
||||
allowed_ids.add(menu.parent_id)
|
||||
|
||||
menus = [m for m in all_menus if m.id in allowed_ids]
|
||||
|
||||
return [
|
||||
{
|
||||
"id": m.id, "label": m.label, "path": m.path, "icon": m.icon,
|
||||
|
||||
+55
-82
@@ -125,7 +125,7 @@ async def _seed_data():
|
||||
from app.models.model_config import ModelConfig
|
||||
from app.services.auth import hash_password
|
||||
from app.utils.id_gen import generate_id
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import select, func
|
||||
|
||||
async with async_session() as db:
|
||||
# Check if admin exists
|
||||
@@ -354,26 +354,19 @@ async def _seed_data():
|
||||
# Seed menu configs
|
||||
from app.models.menu_config import MenuConfig
|
||||
|
||||
frontend_groups = [
|
||||
("AI项目行业生成", "HomeOutlined", 0),
|
||||
("AI对话生成", "HomeOutlined", 1),
|
||||
("AI视频创作", "HomeOutlined", 2),
|
||||
("资产管理", "HomeOutlined", 3),
|
||||
("媒体关联", "HomeOutlined", 4),
|
||||
]
|
||||
frontend_group_ids: dict[str, str] = {}
|
||||
for label, icon, order in frontend_groups:
|
||||
existing = await db.execute(
|
||||
select(MenuConfig).where(
|
||||
MenuConfig.label == label,
|
||||
MenuConfig.menu_type == "group",
|
||||
MenuConfig.menu_target == "frontend",
|
||||
).limit(1)
|
||||
)
|
||||
group = existing.scalar_one_or_none()
|
||||
if group:
|
||||
frontend_group_ids[label] = group.id
|
||||
else:
|
||||
menu_count = await db.execute(select(func.count(MenuConfig.id)))
|
||||
menu_count_result = menu_count.scalar_one()
|
||||
|
||||
if menu_count_result == 0:
|
||||
frontend_groups = [
|
||||
("AI项目行业生成", "HomeOutlined", 0),
|
||||
("AI对话生成", "HomeOutlined", 1),
|
||||
("AI视频创作", "HomeOutlined", 2),
|
||||
("资产管理", "HomeOutlined", 3),
|
||||
("媒体关联", "HomeOutlined", 4),
|
||||
]
|
||||
frontend_group_ids: dict[str, str] = {}
|
||||
for label, icon, order in frontend_groups:
|
||||
gid = generate_id()
|
||||
frontend_group_ids[label] = gid
|
||||
db.add(
|
||||
@@ -391,21 +384,19 @@ async def _seed_data():
|
||||
)
|
||||
)
|
||||
|
||||
frontend_pages = [
|
||||
("/projects", "我的项目", "HomeOutlined", 0, "AI项目行业生成", True),
|
||||
("/conversation", "AI创作", "StarOutlined", 0, "AI对话生成", True),
|
||||
("/initial", "爆款开头复刻", "CodeOutlined", 0, "AI视频创作", False),
|
||||
("/removelens", "拆镜复刻", "CameraOutlined", 1, "AI视频创作", False),
|
||||
("/records", "项目记录", "PlayCircleOutlined", 0, "资产管理", True),
|
||||
("/generated", "素材云", "CloudOutlined", 1, "资产管理", True),
|
||||
("/authorization", "授权管理", "UserOutlined", 0, "媒体关联", False),
|
||||
("/consume", "消耗列表", "FileTextOutlined", 1, "媒体关联", False),
|
||||
]
|
||||
for path, label, icon, order, group_label, is_default in frontend_pages:
|
||||
existing = await db.execute(
|
||||
select(MenuConfig).where(MenuConfig.path == path).limit(1)
|
||||
)
|
||||
if not existing.scalar_one_or_none():
|
||||
frontend_pages = [
|
||||
("/projects", "我的项目", "HomeOutlined", 0, "AI项目行业生成", True),
|
||||
("/conversation", "AI创作", "StarOutlined", 0, "AI对话生成", True),
|
||||
("/initial", "爆款开头复刻", "CodeOutlined", 0, "AI视频创作", False),
|
||||
("/removelens", "拆镜复刻", "CameraOutlined", 1, "AI视频创作", False),
|
||||
("/records", "项目记录", "PlayCircleOutlined", 0, "资产管理", True),
|
||||
("/generated", "素材云", "CloudOutlined", 1, "资产管理", True),
|
||||
("/authorization", "授权管理", "UserOutlined", 0, "媒体关联", False),
|
||||
("/consume", "消耗列表", "FileTextOutlined", 1, "媒体关联", False),
|
||||
("/popular", "爆款榜单", "ThunderboltOutlined", 0, "AI项目行业生成", False),
|
||||
("/creativeplaza", "创意广场", "GiftOutlined", 0, "AI对话生成", False),
|
||||
]
|
||||
for path, label, icon, order, group_label, is_default in frontend_pages:
|
||||
db.add(
|
||||
MenuConfig(
|
||||
id=generate_id(),
|
||||
@@ -421,24 +412,13 @@ async def _seed_data():
|
||||
)
|
||||
)
|
||||
|
||||
admin_groups = [
|
||||
("模型设置", "RobotOutlined", 98),
|
||||
("模型配置", "RobotOutlined", 6),
|
||||
("系统设置", "SettingOutlined", 99),
|
||||
]
|
||||
admin_group_ids: dict[str, str] = {}
|
||||
for label, icon, order in admin_groups:
|
||||
existing = await db.execute(
|
||||
select(MenuConfig).where(
|
||||
MenuConfig.label == label,
|
||||
MenuConfig.menu_type == "group",
|
||||
MenuConfig.menu_target == "admin",
|
||||
).limit(1)
|
||||
)
|
||||
group = existing.scalar_one_or_none()
|
||||
if group:
|
||||
admin_group_ids[label] = group.id
|
||||
else:
|
||||
admin_groups = [
|
||||
("模型设置", "RobotOutlined", 98),
|
||||
("模型配置", "RobotOutlined", 6),
|
||||
("系统设置", "SettingOutlined", 99),
|
||||
]
|
||||
admin_group_ids: dict[str, str] = {}
|
||||
for label, icon, order in admin_groups:
|
||||
gid = generate_id()
|
||||
admin_group_ids[label] = gid
|
||||
db.add(
|
||||
@@ -454,34 +434,27 @@ async def _seed_data():
|
||||
)
|
||||
)
|
||||
|
||||
admin_pages = [
|
||||
("/", "数据概览", "DashboardOutlined", 0, None),
|
||||
("/users", "用户管理", "UserOutlined", 1, None),
|
||||
("/credit-records", "交易流水", "WalletOutlined", 2, None),
|
||||
("/generation-ai", "创作记录", "BulbOutlined", 3, None),
|
||||
("/generation-records", "项目记录", "VideoCameraOutlined", 3, None),
|
||||
("/recharge-packages", "充值套餐", "GiftOutlined", 4, None),
|
||||
("/notifications", "消息推送", "BellOutlined", 5, None),
|
||||
("/payment-stats", "支付统计", "LineChartOutlined", 6, None),
|
||||
("/video-engines", "视频引擎", "PlayCircleOutlined", 0, "模型设置"),
|
||||
("/models", "模型配置", "RobotOutlined", 1, "模型设置"),
|
||||
("/image-engines", "图片模型", "PictureOutlined", 2, "模型设置"),
|
||||
("/credit-ratios", "积分比例", "CalculatorOutlined", 3, "模型设置"),
|
||||
("/payment", "支付配置", "DollarOutlined", 1, "系统设置"),
|
||||
("/industries", "行业配置", "AppstoreOutlined", 2, "系统设置"),
|
||||
("/menu-configs", "菜单配置", "SettingOutlined", 3, "系统设置"),
|
||||
("/settings", "系统设置", "SettingOutlined", 4, "系统设置"),
|
||||
("/operation-logs", "操作日志", "DatabaseOutlined", 5, "系统设置"),
|
||||
("/oauthapp-list", "授权应用", "MenuOutlined", 28, "系统设置"),
|
||||
]
|
||||
for path, label, icon, order, parent_group in admin_pages:
|
||||
existing = await db.execute(
|
||||
select(MenuConfig).where(
|
||||
MenuConfig.path == path,
|
||||
MenuConfig.menu_target == "admin",
|
||||
).limit(1)
|
||||
)
|
||||
if not existing.scalar_one_or_none():
|
||||
admin_pages = [
|
||||
("/", "数据概览", "DashboardOutlined", 0, None),
|
||||
("/users", "用户管理", "UserOutlined", 1, None),
|
||||
("/credit-records", "交易流水", "WalletOutlined", 2, None),
|
||||
("/generation-ai", "创作记录", "BulbOutlined", 3, None),
|
||||
("/generation-records", "项目记录", "VideoCameraOutlined", 3, None),
|
||||
("/recharge-packages", "充值套餐", "GiftOutlined", 4, None),
|
||||
("/notifications", "消息推送", "BellOutlined", 5, None),
|
||||
("/payment-stats", "支付统计", "LineChartOutlined", 6, None),
|
||||
("/video-engines", "视频引擎", "PlayCircleOutlined", 0, "模型设置"),
|
||||
("/models", "模型配置", "RobotOutlined", 1, "模型设置"),
|
||||
("/image-engines", "图片模型", "PictureOutlined", 2, "模型设置"),
|
||||
("/credit-ratios", "积分比例", "CalculatorOutlined", 3, "模型设置"),
|
||||
("/payment", "支付配置", "DollarOutlined", 1, "系统设置"),
|
||||
("/industries", "行业配置", "AppstoreOutlined", 2, "系统设置"),
|
||||
("/menu-configs", "菜单配置", "SettingOutlined", 3, "系统设置"),
|
||||
("/settings", "系统设置", "SettingOutlined", 4, "系统设置"),
|
||||
("/operation-logs", "操作日志", "DatabaseOutlined", 5, "系统设置"),
|
||||
("/oauthapp-list", "授权应用", "MenuOutlined", 28, "系统设置"),
|
||||
]
|
||||
for path, label, icon, order, parent_group in admin_pages:
|
||||
db.add(
|
||||
MenuConfig(
|
||||
id=generate_id(),
|
||||
|
||||
+442
File diff suppressed because one or more lines are too long
Vendored
+4
@@ -28,7 +28,11 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<<<<<<< HEAD
|
||||
<script type="module" crossorigin src="/assets/index-D9QoFfGP.js"></script>
|
||||
=======
|
||||
<script type="module" crossorigin src="/assets/index-WjS6rFiW.js"></script>
|
||||
>>>>>>> 3b9e5fab104c33eca7e35adc1c6a4ff8ed9cbd60
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-eLRg4pQk.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -25,6 +25,8 @@ import RemoveRw from './pages/RemoveRw';
|
||||
// import RemoveFenbu from './pages/RemoveFenbu';
|
||||
import ConsumePage from './pages/ConsumePage';
|
||||
import AuthorizationWaitingPage from './pages/AuthorizationWaitingPage';
|
||||
import PopularPage from './pages/PopularPage';
|
||||
import CreativePlazaPage from './pages/CreativePlazaPage';
|
||||
import { useAuthStore } from './store/useAuthStore';
|
||||
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
||||
const { user, loading, checkAuth } = useAuthStore();
|
||||
@@ -113,6 +115,8 @@ const App = () => {
|
||||
<Route path="authoriza-waiting" element={<AuthorizationWaitingPage />} />
|
||||
<Route path="materials" element={<MaterialListPage />} />
|
||||
<Route path="consume" element={<ConsumePage />} />
|
||||
<Route path="popular" element={<PopularPage />} />
|
||||
<Route path="creativeplaza" element={<CreativePlazaPage />} />
|
||||
</Route>
|
||||
<Route path="*" element={<Navigate to="/projects" replace />} />
|
||||
</Routes>
|
||||
|
||||
@@ -258,21 +258,7 @@ const AppLayout: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
getMenuConfigs().then(data => {
|
||||
let items = data.filter((m: any) => m.is_active !== false && m.isActive !== false);
|
||||
// Filter by user's allowed menus if set
|
||||
if (user?.allowedMenus && user.allowedMenus.length > 0) {
|
||||
const allowed = new Set(user.allowedMenus);
|
||||
const groupIds = new Set<string>();
|
||||
items.forEach((m: any) => {
|
||||
const pid = m.parent_id ?? m.parentId;
|
||||
if (pid && allowed.has(m.path)) groupIds.add(pid);
|
||||
});
|
||||
items = items.filter((m: any) => {
|
||||
const mt = m.menu_type ?? m.menuType;
|
||||
if (mt === 'group' && groupIds.has(m.id)) return true;
|
||||
return allowed.has(m.path);
|
||||
});
|
||||
}
|
||||
const items = data.filter((m: any) => m.is_active !== false && m.isActive !== false);
|
||||
setMenuItems(items);
|
||||
}).catch(() => { });
|
||||
getRechargePackages().then(data => {
|
||||
@@ -448,15 +434,26 @@ const AppLayout: React.FC = () => {
|
||||
</div>
|
||||
)} */}
|
||||
{(() => {
|
||||
const groups = menuItems.filter(m => (m.menu_type ?? m.menuType) === 'group');
|
||||
const pages = menuItems.filter(m => (m.menu_type ?? m.menuType) !== 'group');
|
||||
// Build child map for all menu items
|
||||
const childMap: Record<string, MenuConfig[]> = {};
|
||||
pages.filter(m => m.parent_id ?? m.parentId).forEach(m => {
|
||||
const pid = (m.parent_id ?? m.parentId) as string;
|
||||
if (!childMap[pid]) childMap[pid] = [];
|
||||
childMap[pid].push(m);
|
||||
menuItems.forEach(m => {
|
||||
const pid = m.parent_id ?? m.parentId;
|
||||
if (pid) {
|
||||
if (!childMap[pid]) childMap[pid] = [];
|
||||
childMap[pid].push(m);
|
||||
}
|
||||
});
|
||||
const topLevel = pages.filter(m => !(m.parent_id ?? m.parentId));
|
||||
|
||||
// Get top-level items (parent_id is null/undefined)
|
||||
const topLevelItems = menuItems.filter(m => !(m.parent_id ?? m.parentId));
|
||||
|
||||
// Sort top-level items by sort_order
|
||||
topLevelItems.sort((a, b) => {
|
||||
const orderA = typeof a.sortOrder === 'number' ? a.sortOrder : Infinity;
|
||||
const orderB = typeof b.sortOrder === 'number' ? b.sortOrder : Infinity;
|
||||
return orderA - orderB;
|
||||
});
|
||||
|
||||
const items: React.ReactNode[] = [];
|
||||
|
||||
const renderMenuItem = (item: MenuConfig, depth: number = 0) => {
|
||||
@@ -484,25 +481,32 @@ const AppLayout: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// Render groups with children
|
||||
groups.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)).forEach(g => {
|
||||
const children = (childMap[g.id] || []).sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
||||
items.push(
|
||||
<div key={g.id}>
|
||||
<div style={{
|
||||
color: '#94a3b8', fontSize: 12, fontWeight: 600,
|
||||
padding: '12px 16px 6px', letterSpacing: 0.5, textTransform: 'uppercase',
|
||||
}}>
|
||||
{g.label}
|
||||
// Render top-level items and their children in order
|
||||
topLevelItems.forEach(item => {
|
||||
const menuType = item.menu_type ?? item.menuType;
|
||||
|
||||
if (menuType === 'group') {
|
||||
// Render group with its children
|
||||
const children = (childMap[item.id] || []).sort((a, b) => {
|
||||
const orderA = typeof a.sortOrder === 'number' ? a.sortOrder : Infinity;
|
||||
const orderB = typeof b.sortOrder === 'number' ? b.sortOrder : Infinity;
|
||||
return orderA - orderB;
|
||||
});
|
||||
items.push(
|
||||
<div key={item.id}>
|
||||
<div style={{
|
||||
color: '#94a3b8', fontSize: 12, fontWeight: 600,
|
||||
padding: '12px 16px 6px', letterSpacing: 0.5, textTransform: 'uppercase',
|
||||
}}>
|
||||
{item.label}
|
||||
</div>
|
||||
{children.map(c => renderMenuItem(c, 1))}
|
||||
</div>
|
||||
{children.map(c => renderMenuItem(c, 1))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// Render top-level pages
|
||||
topLevel.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)).forEach(m => {
|
||||
items.push(renderMenuItem(m));
|
||||
);
|
||||
} else {
|
||||
// Render standalone page
|
||||
items.push(renderMenuItem(item));
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
import React from 'react';
|
||||
import { Button, Card, message, Typography } from 'antd';
|
||||
import {
|
||||
StarOutlined,
|
||||
LockOutlined,
|
||||
GiftOutlined,
|
||||
ThunderboltOutlined,
|
||||
PlayCircleOutlined,
|
||||
CheckCircleOutlined,
|
||||
HeartOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const CreativePlazaPage: React.FC = () => {
|
||||
const handleUnlock = () => {
|
||||
message.info('请联系客服开通VIP会员,客服热线:400-888-8888');
|
||||
};
|
||||
|
||||
const caseStudies = [
|
||||
{
|
||||
id: 1,
|
||||
title: '品牌宣传视频',
|
||||
category: '商业广告',
|
||||
description: '为知名品牌打造的创意宣传视频,展现品牌理念与产品特色',
|
||||
tags: ['AI生成', '品牌推广', '创意设计'],
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=professional%20brand%20promotional%20video%20advertisement%20modern&image_size=landscape_16_9',
|
||||
duration: '0:45',
|
||||
quality: '4K',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '产品展示动画',
|
||||
category: '电商展示',
|
||||
description: '3D产品展示动画,让产品细节完美呈现',
|
||||
tags: ['3D渲染', '产品展示', '电商'],
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=3D%20product%20showcase%20animation%20ecommerce&image_size=landscape_16_9',
|
||||
duration: '0:30',
|
||||
quality: '4K',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '教育培训课程',
|
||||
category: '知识分享',
|
||||
description: '生动有趣的教育内容,让学习更加轻松愉快',
|
||||
tags: ['教育', '知识', '在线课程'],
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=educational%20video%20learning%20classroom%20modern&image_size=landscape_16_9',
|
||||
duration: '15:20',
|
||||
quality: '1080P',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '短视频创意',
|
||||
category: '社交媒体',
|
||||
description: '适合各大社交平台的创意短视频内容',
|
||||
tags: ['短视频', '社交', '创意'],
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=creative%20short%20video%20social%20media%20vibrant&image_size=landscape_16_9',
|
||||
duration: '0:15',
|
||||
quality: '1080P',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '企业宣传片',
|
||||
category: '企业形象',
|
||||
description: '全方位展示企业实力与文化的专业宣传片',
|
||||
tags: ['企业', '宣传片', '品牌'],
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=corporate%20video%20company%20profile%20professional&image_size=landscape_16_9',
|
||||
duration: '2:30',
|
||||
quality: '4K',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '动画短片',
|
||||
category: '创意动画',
|
||||
description: '精美的AI生成动画短片,展现无限创意',
|
||||
tags: ['动画', 'AI艺术', '创意'],
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=creative%20animated%20short%20film%20colorful%20artistic&image_size=landscape_16_9',
|
||||
duration: '1:30',
|
||||
quality: '4K',
|
||||
},
|
||||
];
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: <ThunderboltOutlined style={{ fontSize: 28, color: '#8b5cf6' }} />,
|
||||
title: 'AI智能生成',
|
||||
description: '先进的AI技术,一键生成高质量视频内容',
|
||||
},
|
||||
{
|
||||
icon: <PlayCircleOutlined style={{ fontSize: 28, color: '#ec4899' }} />,
|
||||
title: '多格式输出',
|
||||
description: '支持多种视频格式,满足不同场景需求',
|
||||
},
|
||||
{
|
||||
icon: <CheckCircleOutlined style={{ fontSize: 28, color: '#10b981' }} />,
|
||||
title: '品质保证',
|
||||
description: '专业级画质,细节清晰,色彩鲜艳',
|
||||
},
|
||||
{
|
||||
icon: <HeartOutlined style={{ fontSize: 28, color: '#f59e0b' }} />,
|
||||
title: '创意无限',
|
||||
description: '丰富的模板和风格,激发创作灵感',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px 0' }}>
|
||||
<div style={{ marginBottom: 32 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
|
||||
<div style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 14,
|
||||
background: 'linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 4px 16px rgba(139,92,246,0.3)',
|
||||
}}>
|
||||
<GiftOutlined style={{ fontSize: 22, color: '#fff' }} />
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Title level={2} style={{ margin: 0, color: '#1e293b', fontWeight: 700 }}>
|
||||
创意素材案例
|
||||
<span style={{ color: '#6366f1', fontSize: 14, fontWeight: 400, marginLeft: 8 }}>
|
||||
探索AI创作无限可能
|
||||
</span>
|
||||
</Typography.Title>
|
||||
</div>
|
||||
</div>
|
||||
<Typography.Text style={{ color: '#64748b' }}>
|
||||
精选AI生成的优秀素材案例,展示AI创作的无限潜力与创意灵感
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 20, marginBottom: 32 }}>
|
||||
{features.map((feature, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
bordered={false}
|
||||
style={{
|
||||
borderRadius: 16,
|
||||
background: 'linear-gradient(135deg, rgba(139,92,246,0.04) 0%, rgba(99,102,241,0.04) 100%)',
|
||||
border: '1px solid rgba(139,92,246,0.1)',
|
||||
padding: 20,
|
||||
}}
|
||||
>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ marginBottom: 16 }}>{feature.icon}</div>
|
||||
<Typography.Title level={4} style={{ marginBottom: 8, color: '#1e293b' }}>
|
||||
{feature.title}
|
||||
</Typography.Title>
|
||||
<Typography.Text style={{ color: '#64748b', fontSize: 13 }}>
|
||||
{feature.description}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
|
||||
<Typography.Title level={4} style={{ margin: 0, color: '#1e293b' }}>
|
||||
<StarOutlined style={{ marginRight: 8, color: '#f59e0b' }} />
|
||||
精选案例展示
|
||||
</Typography.Title>
|
||||
<Button
|
||||
type="primary"
|
||||
size="middle"
|
||||
onClick={handleUnlock}
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
background: 'linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%)',
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
<LockOutlined style={{ marginRight: 6 }} />
|
||||
解锁全部案例
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 20 }}>
|
||||
{caseStudies.map((caseItem) => (
|
||||
<Card
|
||||
key={caseItem.id}
|
||||
bordered={false}
|
||||
style={{
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 4px 16px rgba(0,0,0,0.06)',
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<img
|
||||
src={caseItem.thumbnail}
|
||||
alt={caseItem.title}
|
||||
style={{ width: '100%', height: 180, objectFit: 'cover' }}
|
||||
/>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
left: 12,
|
||||
padding: '4px 12px',
|
||||
background: 'rgba(139,92,246,0.9)',
|
||||
borderRadius: 8,
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
}}>
|
||||
{caseItem.category}
|
||||
</div>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: 12,
|
||||
right: 12,
|
||||
padding: '4px 10px',
|
||||
background: 'rgba(0,0,0,0.7)',
|
||||
borderRadius: 6,
|
||||
color: '#fff',
|
||||
fontSize: 11,
|
||||
}}>
|
||||
{caseItem.duration} | {caseItem.quality}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: 16 }}>
|
||||
<Typography.Title level={5} style={{ marginBottom: 8, color: '#1e293b' }}>
|
||||
{caseItem.title}
|
||||
</Typography.Title>
|
||||
<Typography.Text style={{ color: '#64748b', fontSize: 13, marginBottom: 12, display: 'block' }}>
|
||||
{caseItem.description}
|
||||
</Typography.Text>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 12 }}>
|
||||
{caseItem.tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
style={{
|
||||
padding: '4px 12px',
|
||||
background: 'rgba(99,102,241,0.08)',
|
||||
borderRadius: 20,
|
||||
color: '#6366f1',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
block
|
||||
size="small"
|
||||
onClick={handleUnlock}
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
background: 'linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%)',
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
<ThunderboltOutlined style={{ marginRight: 4 }} />
|
||||
生成同款
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreativePlazaPage;
|
||||
@@ -0,0 +1,247 @@
|
||||
import React from 'react';
|
||||
import { Button, Card, message, Typography } from 'antd';
|
||||
import {
|
||||
StarOutlined,
|
||||
CrownOutlined,
|
||||
LockOutlined,
|
||||
ThunderboltOutlined,
|
||||
GiftOutlined,
|
||||
BarChartOutlined,
|
||||
FireOutlined,
|
||||
TrophyOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const PopularPage: React.FC = () => {
|
||||
const handleUnlock = () => {
|
||||
message.info('请联系客服开通VIP会员,客服热线:400-888-8888');
|
||||
};
|
||||
|
||||
const industries = [
|
||||
{ name: '美妆护肤', hot: 'HOT', trend: '+125%', color: '#ec4899' },
|
||||
{ name: '美食餐饮', hot: '', trend: '+89%', color: '#f59e0b' },
|
||||
{ name: '服饰穿搭', hot: 'NEW', trend: '+76%', color: '#10b981' },
|
||||
{ name: '数码科技', hot: '', trend: '+64%', color: '#06b6d4' },
|
||||
{ name: '家居生活', hot: '', trend: '+52%', color: '#8b5cf6' },
|
||||
{ name: '运动健身', hot: 'HOT', trend: '+48%', color: '#6366f1' },
|
||||
];
|
||||
|
||||
const hotMaterials = [
|
||||
{
|
||||
id: 1,
|
||||
title: '夏日清爽护肤教程',
|
||||
industry: '美妆护肤',
|
||||
views: '2.3M',
|
||||
likes: '156K',
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=beautiful%20skincare%20product%20advertisement%20with%20fresh%20summer%20vibes&image_size=landscape_16_9',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '网红美食探店vlog',
|
||||
industry: '美食餐饮',
|
||||
views: '1.8M',
|
||||
likes: '128K',
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=delicious%20food%20foodie%20vlog%20style%20restaurant&image_size=landscape_16_9',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '秋季穿搭灵感分享',
|
||||
industry: '服饰穿搭',
|
||||
views: '1.5M',
|
||||
likes: '98K',
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=fashion%20autumn%20outfit%20inspiration%20stylish&image_size=landscape_16_9',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '新品手机开箱评测',
|
||||
industry: '数码科技',
|
||||
views: '1.2M',
|
||||
likes: '87K',
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=smartphone%20unboxing%20review%20tech%20gadget&image_size=landscape_16_9',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '家居改造前后对比',
|
||||
industry: '家居生活',
|
||||
views: '980K',
|
||||
likes: '76K',
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=home%20makeover%20before%20after%20interior%20design&image_size=landscape_16_9',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '健身房训练日常',
|
||||
industry: '运动健身',
|
||||
views: '850K',
|
||||
likes: '65K',
|
||||
thumbnail: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=gym%20workout%20fitness%20training%20motivation&image_size=landscape_16_9',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px 0' }}>
|
||||
<div style={{ marginBottom: 32 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
|
||||
<div style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 14,
|
||||
background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 4px 16px rgba(245,158,11,0.3)',
|
||||
}}>
|
||||
<BarChartOutlined style={{ fontSize: 22, color: '#fff' }} />
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Title level={2} style={{ margin: 0, color: '#1e293b', fontWeight: 700 }}>
|
||||
行业爆款大盘
|
||||
<span style={{ color: '#6366f1', fontSize: 14, fontWeight: 400, marginLeft: 8 }}>
|
||||
发现热门素材趋势
|
||||
</span>
|
||||
</Typography.Title>
|
||||
</div>
|
||||
</div>
|
||||
<Typography.Text style={{ color: '#64748b' }}>
|
||||
实时追踪各行业爆款素材,把握创作风向,打造热门内容
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
<Card
|
||||
bordered={false}
|
||||
style={{
|
||||
borderRadius: 20,
|
||||
background: 'linear-gradient(135deg, rgba(245,158,11,0.04) 0%, rgba(217,119,6,0.04) 100%)',
|
||||
border: '1px solid rgba(245,158,11,0.1)',
|
||||
padding: 24,
|
||||
marginBottom: 32,
|
||||
}}
|
||||
>
|
||||
<Typography.Title level={4} style={{ marginBottom: 20, color: '#1e293b' }}>
|
||||
<FireOutlined style={{ marginRight: 8, color: '#f59e0b' }} />
|
||||
行业热度排行
|
||||
</Typography.Title>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: 16 }}>
|
||||
{industries.map((industry, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
background: '#fff',
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
textAlign: 'center',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4, marginBottom: 8 }}>
|
||||
<span style={{ fontSize: 20, fontWeight: 700, color: industry.color }}>{index + 1}</span>
|
||||
{industry.hot && (
|
||||
<span style={{
|
||||
fontSize: 10,
|
||||
padding: '2px 8px',
|
||||
borderRadius: 10,
|
||||
background: industry.hot === 'HOT' ? 'rgba(239,68,68,0.1)' : 'rgba(16,185,129,0.1)',
|
||||
color: industry.hot === 'HOT' ? '#ef4444' : '#10b981',
|
||||
fontWeight: 600,
|
||||
}}>
|
||||
{industry.hot}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Typography.Text strong style={{ color: '#1e293b', fontSize: 14 }}>
|
||||
{industry.name}
|
||||
</Typography.Text>
|
||||
<div style={{
|
||||
marginTop: 8,
|
||||
fontSize: 12,
|
||||
color: '#10b981',
|
||||
fontWeight: 600,
|
||||
}}>
|
||||
{industry.trend}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
|
||||
<Typography.Title level={4} style={{ margin: 0, color: '#1e293b' }}>
|
||||
<TrophyOutlined style={{ marginRight: 8, color: '#6366f1' }} />
|
||||
爆款素材榜单
|
||||
</Typography.Title>
|
||||
<Button
|
||||
type="primary"
|
||||
size="middle"
|
||||
onClick={handleUnlock}
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
<LockOutlined style={{ marginRight: 6 }} />
|
||||
立即解锁全部
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 20 }}>
|
||||
{hotMaterials.map((material) => (
|
||||
<Card
|
||||
key={material.id}
|
||||
bordered={false}
|
||||
style={{
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 4px 16px rgba(0,0,0,0.06)',
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<img
|
||||
src={material.thumbnail}
|
||||
alt={material.title}
|
||||
style={{ width: '100%', height: 180, objectFit: 'cover' }}
|
||||
/>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
left: 12,
|
||||
padding: '4px 12px',
|
||||
background: 'rgba(0,0,0,0.6)',
|
||||
borderRadius: 8,
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
}}>
|
||||
{material.industry}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: 16 }}>
|
||||
<Typography.Title level={5} style={{ marginBottom: 8, color: '#1e293b' }}>
|
||||
{material.title}
|
||||
</Typography.Title>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16, color: '#64748b', fontSize: 13 }}>
|
||||
<span><StarOutlined style={{ marginRight: 4 }} />{material.views} 播放</span>
|
||||
<span><CrownOutlined style={{ marginRight: 4 }} />{material.likes} 点赞</span>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
block
|
||||
size="small"
|
||||
onClick={handleUnlock}
|
||||
style={{
|
||||
marginTop: 12,
|
||||
borderRadius: 8,
|
||||
background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
<ThunderboltOutlined style={{ marginRight: 4 }} />
|
||||
一键生成同款
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopularPage;
|
||||
Reference in New Issue
Block a user