feat(character): 更新角色详情页面- 修改角色详情页面布局和样式
- 添加角色属性、职业等信息展示 - 更新技能和配装推荐模块 - 优化页面加载和错误处理逻辑
This commit is contained in:
@@ -128,7 +128,7 @@ const App: React.FC = () => {
|
|||||||
<Route path="/home" element={<Home />} />
|
<Route path="/home" element={<Home />} />
|
||||||
<Route path="/characters" element={<Characters />} />
|
<Route path="/characters" element={<Characters />} />
|
||||||
<Route
|
<Route
|
||||||
path="/character/:id"
|
path="/character/:heroCode"
|
||||||
element={<CharacterDetail character={mockCharacterData} />}
|
element={<CharacterDetail character={mockCharacterData} />}
|
||||||
/>
|
/>
|
||||||
<Route path="/news" element={<News />} />
|
<Route path="/news" element={<News />} />
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {Api} from "../utils/axios/config";
|
import {Api} from "../utils/axios/config";
|
||||||
import axios, {AxiosRequestConfig} from "axios";
|
|
||||||
|
|
||||||
// 查询参数接口
|
// 查询参数接口
|
||||||
export interface GvgTeamQueryParams {
|
export interface GvgTeamQueryParams {
|
||||||
@@ -54,6 +53,37 @@ export interface Hero {
|
|||||||
headImgUrl: string;
|
headImgUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 角色详情接口类型
|
||||||
|
export interface HeroDetailResp {
|
||||||
|
heroRespSimpleVO: {
|
||||||
|
id: string;
|
||||||
|
heroCode: string;
|
||||||
|
heroName: string;
|
||||||
|
nickName: string | null;
|
||||||
|
headImgUrl: string;
|
||||||
|
stars: number;
|
||||||
|
role: string;
|
||||||
|
attribute: string;
|
||||||
|
};
|
||||||
|
hero60AttributeVO: {
|
||||||
|
cp: number;
|
||||||
|
atk: number;
|
||||||
|
hp: number;
|
||||||
|
spd: number;
|
||||||
|
def: number;
|
||||||
|
chc: number;
|
||||||
|
chd: number;
|
||||||
|
dac: number;
|
||||||
|
eff: number;
|
||||||
|
efr: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询角色详情
|
||||||
|
export const getHeroDetail = async (heroCode: string): Promise<Response<HeroDetailResp>> => {
|
||||||
|
return await Api.get<Response<HeroDetailResp>>(`/epic/hero/hero-detail?heroCode=${heroCode}`)
|
||||||
|
};
|
||||||
|
|
||||||
// 查询 GVG 阵容列表
|
// 查询 GVG 阵容列表
|
||||||
export const getGvgTeamList = async (heroCodes?: string[]) => {
|
export const getGvgTeamList = async (heroCodes?: string[]) => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
@@ -89,7 +119,7 @@ export const recognizeHeroesFromImage = async (file: File): Promise<ImageRecogni
|
|||||||
const response = await Api.upload<string[]>('/epic/hero/recognize', file);
|
const response = await Api.upload<string[]>('/epic/hero/recognize', file);
|
||||||
console.log(response)
|
console.log(response)
|
||||||
if (response && Array.isArray(response)) {
|
if (response && Array.isArray(response)) {
|
||||||
return { heroNames: response };
|
return {heroNames: response};
|
||||||
}
|
}
|
||||||
throw new Error("图片识别失败");
|
throw new Error("图片识别失败");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import React, {useEffect} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {useParams} from 'react-router-dom';
|
||||||
|
import * as EpicApi from '@/api/index';
|
||||||
|
import {getHeroDetail} from "@/api/index";
|
||||||
|
|
||||||
export interface Skill {
|
export interface Skill {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -145,19 +148,82 @@ const artifactImgMap: Record<string, string> = {
|
|||||||
'伊赛丽亚的誓约': 'https://epic7db.com/images/artifacts/elbris-ritual-sword.webp',
|
'伊赛丽亚的誓约': 'https://epic7db.com/images/artifacts/elbris-ritual-sword.webp',
|
||||||
};
|
};
|
||||||
|
|
||||||
const CharacterDetail: React.FC<CharacterDetailProps> = ({character}) => {
|
// 角色职业中文映射
|
||||||
useEffect(() => {
|
const ROLE_LABELS: Record<string, string> = {
|
||||||
window.scrollTo({top: 0, behavior: 'auto'});
|
knight: '骑士',
|
||||||
}, []);
|
warrior: '战士',
|
||||||
|
assassin: '盗贼',
|
||||||
|
ranger: '射手',
|
||||||
|
mage: '魔导师',
|
||||||
|
manauser: '精灵师',
|
||||||
|
all: '全部',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 属性图标映射
|
||||||
|
const ATTR_ICON_MAP: Record<string, string> = {
|
||||||
|
attack: '/pic/item/attr/cm_icon_stat_attack.png',
|
||||||
|
defense: '/pic/item/attr/cm_icon_stat_defense.png',
|
||||||
|
health: '/pic/item/attr/cm_icon_stat_health.png',
|
||||||
|
speed: '/pic/item/attr/cm_icon_stat_speed.png',
|
||||||
|
critChance: '/pic/item/attr/cm_icon_stat_crit_chance.png',
|
||||||
|
critDamage: '/pic/item/attr/cm_icon_stat_crit_damage.png',
|
||||||
|
effectiveness: '/pic/item/attr/cm_icon_stat_effectiveness.png',
|
||||||
|
effectResistance: '/pic/item/attr/cm_icon_stat_effect_resistance.png',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 属性中文映射
|
||||||
|
const ATTR_LABEL_MAP: Record<string, string> = {
|
||||||
|
attack: '攻击',
|
||||||
|
defense: '防御',
|
||||||
|
health: '生命',
|
||||||
|
speed: '速度',
|
||||||
|
critChance: '暴击率',
|
||||||
|
critDamage: '暴击伤害',
|
||||||
|
effectiveness: '效果命中',
|
||||||
|
effectResistance: '效果抗性',
|
||||||
|
};
|
||||||
|
|
||||||
|
const CharacterDetail: React.FC = () => {
|
||||||
|
const {heroCode} = useParams();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [heroDetail, setHeroDetail] = useState<EpicApi.HeroDetailResp['data'] | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!heroCode) return;
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
EpicApi.getHeroDetail(heroCode)
|
||||||
|
.then(res => {
|
||||||
|
setHeroDetail(res);
|
||||||
|
setError(null);
|
||||||
|
})
|
||||||
|
.catch(() => setError('获取角色详情失败'))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [heroCode]);
|
||||||
|
|
||||||
|
// 星级渲染(详情页专用星星)
|
||||||
const renderStars = (count: number) => {
|
const renderStars = (count: number) => {
|
||||||
return Array(count)
|
return (
|
||||||
.fill(0)
|
<div className="flex items-center gap-1">
|
||||||
.map((_, index) => (
|
{Array(count).fill(0).map((_, i) => (
|
||||||
<i key={index} className="text-yellow-400 text-lg">★</i>
|
<img key={i} src="/pic/star.png" alt="star" className="w-6 h-6 inline-block"/>
|
||||||
));
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 属性图标
|
||||||
|
const getElementIcon = (attribute: string) => {
|
||||||
|
if (!attribute) return null;
|
||||||
|
return <img src={`/pic/element/${attribute}.png`} alt={attribute} className="w-8 h-8"/>;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) return <div className="text-center py-24 text-[#E6B17E]">加载中...</div>;
|
||||||
|
if (error || !heroDetail) return <div className="text-center py-24 text-red-400">{error || '无数据'}</div>;
|
||||||
|
|
||||||
|
const {heroRespSimpleVO, hero60AttributeVO} = heroDetail;
|
||||||
|
|
||||||
const renderSkillType = (type: string) => {
|
const renderSkillType = (type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'None':
|
case 'None':
|
||||||
@@ -180,18 +246,6 @@ const CharacterDetail: React.FC<CharacterDetailProps> = ({character}) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 属性图标占位
|
|
||||||
const statIcons: Record<string, string> = {
|
|
||||||
attack: '⚔️',
|
|
||||||
defense: '🛡️',
|
|
||||||
health: '❤️',
|
|
||||||
critChance: '🎯',
|
|
||||||
critDamage: '💥',
|
|
||||||
effectiveness: '🎲',
|
|
||||||
effectResistance: '🚫',
|
|
||||||
speed: '💨',
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#1A1412] text-white font-sans">
|
<div className="min-h-screen bg-[#1A1412] text-white font-sans">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex">
|
||||||
@@ -199,33 +253,50 @@ const CharacterDetail: React.FC<CharacterDetailProps> = ({character}) => {
|
|||||||
<aside className="w-80 flex-shrink-0 pr-6 flex flex-col pt-12">
|
<aside className="w-80 flex-shrink-0 pr-6 flex flex-col pt-12">
|
||||||
{/* 头像卡片 */}
|
{/* 头像卡片 */}
|
||||||
<div
|
<div
|
||||||
className="bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg p-6 mb-12 border border-[#C17F59]/30 flex flex-col items-center">
|
className="bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg p-6 mb-12 border border-[#C17F59]/30 flex flex-col items-center relative">
|
||||||
<div className="w-28 h-28 rounded-full overflow-hidden border-4 border-[#C17F59] mb-3">
|
<div className="w-28 h-28 rounded-full border-4 border-[#C17F59] mb-3 relative">
|
||||||
<img src={character.imageUrl} alt={character.name} className="w-full h-full object-cover"/>
|
<img src={heroRespSimpleVO.headImgUrl} alt={heroRespSimpleVO.heroName} className="w-full h-full object-cover rounded-full" />
|
||||||
|
{/* 属性图标 */}
|
||||||
|
{heroRespSimpleVO.attribute && (
|
||||||
|
<img
|
||||||
|
src={`/pic/element/${heroRespSimpleVO.attribute}.png`}
|
||||||
|
alt={heroRespSimpleVO.attribute}
|
||||||
|
className="absolute -top-3 -right-3 w-10 h-10 z-10"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold text-[#E6B17E] mb-1">{heroRespSimpleVO.heroName}</h1>
|
||||||
|
<div className="flex mb-1">{renderStars(heroRespSimpleVO.stars)}</div>
|
||||||
|
{/* 职业展示 */}
|
||||||
|
<div className="flex items-center text-[#C17F59] text-base font-medium">
|
||||||
|
{heroRespSimpleVO.role && (
|
||||||
|
<>
|
||||||
|
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-[#E6B17E] mr-2">
|
||||||
|
<img src={`/pic/role/${heroRespSimpleVO.role}.png`} alt={heroRespSimpleVO.role} className="w-6 h-6" />
|
||||||
|
</span>
|
||||||
|
<span className="align-middle">{ROLE_LABELS[heroRespSimpleVO.role] || heroRespSimpleVO.role}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-bold text-[#E6B17E] mb-1">{character.name}</h1>
|
|
||||||
<div className="flex mb-1">{renderStars(character.stars)}</div>
|
|
||||||
<span className="text-[#9B8579] mb-2">{character.class}</span>
|
|
||||||
</div>
|
</div>
|
||||||
{/* 导航卡片 */}
|
{/* 导航卡片 */}
|
||||||
<nav className="sticky top-12 z-30">
|
<nav className="sticky top-12 z-30">
|
||||||
<div
|
<div
|
||||||
className="bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg p-6 border border-[#C17F59]/30">
|
className="bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg p-6 border border-[#C17F59]/30">
|
||||||
<h2 className="text-lg font-bold mb-3 text-white">Table of Contents</h2>
|
<h2 className="text-lg font-bold mb-3 text-white">目 录</h2>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
<li>
|
<li>
|
||||||
<a href="#base-stats" className="hover:text-[#E6B17E] transition">Base Stats</a>
|
<a href="#base-stats" className="hover:text-[#E6B17E] transition">基础属性</a>
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#skills" className="hover:text-[#E6B17E] transition">Skills</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#imprint" className="hover:text-[#E6B17E] transition">Imprint
|
|
||||||
Concentration</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#builds" className="hover:text-[#E6B17E] transition">配装推荐</a>
|
<a href="#builds" className="hover:text-[#E6B17E] transition">配装推荐</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#skills" className="hover:text-[#E6B17E] transition">技能</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#imprint" className="hover:text-[#E6B17E] transition">献身技能</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -237,24 +308,44 @@ const CharacterDetail: React.FC<CharacterDetailProps> = ({character}) => {
|
|||||||
<section id="base-stats"
|
<section id="base-stats"
|
||||||
className="scroll-mt-24 bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg p-6 border border-[#C17F59]/30 mt-12">
|
className="scroll-mt-24 bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg p-6 border border-[#C17F59]/30 mt-12">
|
||||||
<h2 className="text-xl font-bold text-[#E6B17E] mb-4">六星满觉属性</h2>
|
<h2 className="text-xl font-bold text-[#E6B17E] mb-4">六星满觉属性</h2>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
{hero60AttributeVO ? (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
<i className="fas fa-sword text-[#C17F59]"></i>
|
<div className="flex items-center space-x-2">
|
||||||
<span>Attack: {character.baseStats.attack}</span>
|
<img src={ATTR_ICON_MAP.attack} alt="攻击" className="w-6 h-6" />
|
||||||
|
<span>攻击: {hero60AttributeVO.atk}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<img src={ATTR_ICON_MAP.health} alt="生命" className="w-6 h-6" />
|
||||||
|
<span>生命: {hero60AttributeVO.hp}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<img src={ATTR_ICON_MAP.defense} alt="防御" className="w-6 h-6" />
|
||||||
|
<span>防御: {hero60AttributeVO.def}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<img src={ATTR_ICON_MAP.speed} alt="速度" className="w-6 h-6" />
|
||||||
|
<span>速度: {hero60AttributeVO.spd}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<img src={ATTR_ICON_MAP.critChance} alt="暴击率" className="w-6 h-6" />
|
||||||
|
<span>暴击率: {Math.round(hero60AttributeVO.chc * 100)}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<img src={ATTR_ICON_MAP.critDamage} alt="暴击伤害" className="w-6 h-6" />
|
||||||
|
<span>暴击伤害: {Math.round(hero60AttributeVO.chd * 100)}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<img src={ATTR_ICON_MAP.effectiveness} alt="效果命中" className="w-6 h-6" />
|
||||||
|
<span>效果命中: {Math.round(hero60AttributeVO.eff * 100)}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<img src={ATTR_ICON_MAP.effectResistance} alt="效果抗性" className="w-6 h-6" />
|
||||||
|
<span>效果抗性: {Math.round(hero60AttributeVO.efr * 100)}%</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
) : (
|
||||||
<i className="fas fa-heart text-[#C17F59]"></i>
|
<h3 className="text-lg font-semibold text-[#C17F59] mb-2 text-center">无数据</h3>
|
||||||
<span>Health: {character.baseStats.health}</span>
|
)}
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<i className="fas fa-shield-alt text-[#C17F59]"></i>
|
|
||||||
<span>Defense: {character.baseStats.defense}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<i className="fas fa-wind text-[#C17F59]"></i>
|
|
||||||
<span>Speed: {character.baseStats.speed}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 配装推荐模块 */}
|
{/* 配装推荐模块 */}
|
||||||
@@ -269,8 +360,8 @@ const CharacterDetail: React.FC<CharacterDetailProps> = ({character}) => {
|
|||||||
{Object.entries(mockAverageStats).map(([key, value]) => (
|
{Object.entries(mockAverageStats).map(([key, value]) => (
|
||||||
<div key={key} className="flex items-center justify-between py-2">
|
<div key={key} className="flex items-center justify-between py-2">
|
||||||
<div className="flex items-center gap-2 min-w-[110px]">
|
<div className="flex items-center gap-2 min-w-[110px]">
|
||||||
<span>{statIcons[key] || '?'}</span>
|
<img src={ATTR_ICON_MAP[key] || ''} alt={key} className="w-6 h-6" />
|
||||||
<span className="capitalize">{key.replace(/([A-Z])/g, ' $1')}</span>
|
<span>{ATTR_LABEL_MAP[key] || key}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-bold text-[#E6B17E] ml-2">
|
<span className="font-bold text-[#E6B17E] ml-2">
|
||||||
{value}{['critChance', 'critDamage', 'effectiveness', 'effectResistance'].includes(key) ? '%' : ''}
|
{value}{['critChance', 'critDamage', 'effectiveness', 'effectResistance'].includes(key) ? '%' : ''}
|
||||||
@@ -331,7 +422,7 @@ const CharacterDetail: React.FC<CharacterDetailProps> = ({character}) => {
|
|||||||
speed: 175,
|
speed: 175,
|
||||||
},
|
},
|
||||||
gearSets: ['速度', '命中'],
|
gearSets: ['速度', '命中'],
|
||||||
artifact: { name: 'Elbris Ritual Sword', img: '' },
|
artifact: {name: 'Elbris Ritual Sword', img: ''},
|
||||||
}).map((build, idx) => (
|
}).map((build, idx) => (
|
||||||
<div key={idx}
|
<div key={idx}
|
||||||
className="bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg p-6 border border-[#C17F59]/30 flex flex-col gap-2">
|
className="bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg p-6 border border-[#C17F59]/30 flex flex-col gap-2">
|
||||||
@@ -343,8 +434,8 @@ const CharacterDetail: React.FC<CharacterDetailProps> = ({character}) => {
|
|||||||
{Object.entries(build.stats).map(([key, value]) => (
|
{Object.entries(build.stats).map(([key, value]) => (
|
||||||
<div key={key} className="flex items-center justify-between py-2">
|
<div key={key} className="flex items-center justify-between py-2">
|
||||||
<div className="flex items-center gap-2 min-w-[110px]">
|
<div className="flex items-center gap-2 min-w-[110px]">
|
||||||
<span>{statIcons[key] || '?'}</span>
|
<img src={ATTR_ICON_MAP[key] || ''} alt={key} className="w-6 h-6" />
|
||||||
<span className="capitalize">{key.replace(/([A-Z])/g, ' $1')}</span>
|
<span>{ATTR_LABEL_MAP[key] || key}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-bold text-[#E6B17E] ml-2">
|
<span className="font-bold text-[#E6B17E] ml-2">
|
||||||
{value}{['critChance', 'critDamage', 'effectiveness', 'effectResistance'].includes(key) ? '%' : ''}
|
{value}{['critChance', 'critDamage', 'effectiveness', 'effectResistance'].includes(key) ? '%' : ''}
|
||||||
@@ -360,102 +451,37 @@ const CharacterDetail: React.FC<CharacterDetailProps> = ({character}) => {
|
|||||||
<div className="flex gap-3 mb-4">
|
<div className="flex gap-3 mb-4">
|
||||||
{build.gearSets.map((set, i) => (
|
{build.gearSets.map((set, i) => (
|
||||||
setIconMap[set] ? (
|
setIconMap[set] ? (
|
||||||
<img key={i} src={setIconMap[set]} alt={set} className="w-10 h-10" />
|
<img key={i} src={setIconMap[set]} alt={set}
|
||||||
|
className="w-10 h-10"/>
|
||||||
) : (
|
) : (
|
||||||
<span key={i} className="w-10 h-10 inline-flex items-center justify-center text-[#C17F59] text-2xl bg-[#23201c] rounded-full">?</span>
|
<span key={i}
|
||||||
|
className="w-10 h-10 inline-flex items-center justify-center text-[#C17F59] text-2xl bg-[#23201c] rounded-full">?</span>
|
||||||
)
|
)
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{/* 神器图片 */}
|
{/* 神器图片 */}
|
||||||
<div className="flex-1 flex flex-col items-center justify-center">
|
<div className="flex-1 flex flex-col items-center justify-center">
|
||||||
{artifactImgMap[build.artifact.name] ? (
|
{artifactImgMap[build.artifact.name] ? (
|
||||||
<img src={artifactImgMap[build.artifact.name]} alt={build.artifact.name} className="w-48 h48 rounded mb-2 object-contain" />
|
<img src={artifactImgMap[build.artifact.name]}
|
||||||
|
alt={build.artifact.name}
|
||||||
|
className="w-48 h48 rounded mb-2 object-contain"/>
|
||||||
) : build.artifact.img ? (
|
) : build.artifact.img ? (
|
||||||
<img src={build.artifact.img} alt={build.artifact.name} className="w-24 h-24 rounded mb-2 object-contain" />
|
<img src={build.artifact.img} alt={build.artifact.name}
|
||||||
|
className="w-24 h-24 rounded mb-2 object-contain"/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-24 h-24 rounded bg-[#2A211E] flex items-center justify-center text-3xl text-[#C17F59] mb-2">?</div>
|
<div
|
||||||
|
className="w-24 h-24 rounded bg-[#2A211E] flex items-center justify-center text-3xl text-[#C17F59] mb-2">?</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* 神器名称 */}
|
{/* 神器名称 */}
|
||||||
<span className="text-[#E6B17E] font-bold text-base text-center mt-2">{build.artifact.name}</span>
|
<span
|
||||||
|
className="text-[#E6B17E] font-bold text-base text-center mt-2">{build.artifact.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 技能列表 */}
|
|
||||||
<section id="skills" className="scroll-mt-24 space-y-6">
|
|
||||||
{character.skills.map((skill, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg p-6 border border-[#C17F59]/30"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<h3 className="text-xl font-bold text-[#E6B17E]">{skill.name}</h3>
|
|
||||||
<div className="flex items-center">
|
|
||||||
{renderSkillType(skill.type)}
|
|
||||||
{renderSoulBurn(skill.soulBurn)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-[#9B8579] mb-4">{skill.description}</p>
|
|
||||||
{skill.enhancements && skill.enhancements.length > 0 && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<h4 className="text-lg font-semibold text-[#E6B17E] mb-2">Skill
|
|
||||||
Enhancements</h4>
|
|
||||||
<div className="space-y-2">
|
|
||||||
{skill.enhancements.map((enhancement, idx) => (
|
|
||||||
<div key={idx} className="flex items-center space-x-4 text-sm">
|
|
||||||
<div className="flex">{renderStars(enhancement.stars)}</div>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{enhancement.attack && (
|
|
||||||
<span
|
|
||||||
className="text-[#9B8579]">Attack {enhancement.attack}</span>
|
|
||||||
)}
|
|
||||||
{enhancement.health && (
|
|
||||||
<span
|
|
||||||
className="text-[#9B8579]">Health {enhancement.health}</span>
|
|
||||||
)}
|
|
||||||
{enhancement.defense && (
|
|
||||||
<span
|
|
||||||
className="text-[#9B8579]">Defense {enhancement.defense}</span>
|
|
||||||
)}
|
|
||||||
{enhancement.effectiveness && (
|
|
||||||
<span
|
|
||||||
className="text-[#9B8579]">Effectiveness {enhancement.effectiveness}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* 印记集中 */}
|
|
||||||
<section id="imprint"
|
|
||||||
className="scroll-mt-24 bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg p-6 border border-[#C17F59]/30">
|
|
||||||
<h2 className="text-xl font-bold text-[#E6B17E] mb-4">Imprint Concentration</h2>
|
|
||||||
{character.imprintConcentration.map((imprint, index) => (
|
|
||||||
<div key={index} className="mb-4">
|
|
||||||
<h3 className="text-lg font-semibold text-[#C17F59] mb-2">{imprint.type}</h3>
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
||||||
{imprint.values.map((value, idx) => (
|
|
||||||
<div
|
|
||||||
key={idx}
|
|
||||||
className="flex items-center justify-between bg-[#1A1412] p-2 rounded border border-[#C17F59]/30"
|
|
||||||
>
|
|
||||||
<span className="text-[#E6B17E]">{value.rank}</span>
|
|
||||||
<span className="text-[#9B8579]">{value.value}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</section>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ const Characters: React.FC = () => {
|
|||||||
<div
|
<div
|
||||||
key={hero.id}
|
key={hero.id}
|
||||||
className="flex items-center bg-[#23201B] rounded-xl border border-[#C17F59]/30 hover:border-[#C17F59] transition-all duration-300 shadow-md px-4 py-3 min-h-[96px] max-h-[96px] cursor-pointer backdrop-blur-sm"
|
className="flex items-center bg-[#23201B] rounded-xl border border-[#C17F59]/30 hover:border-[#C17F59] transition-all duration-300 shadow-md px-4 py-3 min-h-[96px] max-h-[96px] cursor-pointer backdrop-blur-sm"
|
||||||
onClick={() => navigate(`/character/${hero.id}`)}
|
onClick={() => navigate(`/character/${hero.heroCode}`)}
|
||||||
>
|
>
|
||||||
{/* 头像+attr */}
|
{/* 头像+attr */}
|
||||||
<div className="relative mr-4 flex-shrink-0">
|
<div className="relative mr-4 flex-shrink-0">
|
||||||
|
|||||||
Reference in New Issue
Block a user