diff --git a/public/pic/eqset/setcounter.png b/public/pic/eqset/setcounter.png new file mode 100644 index 0000000..82ef9e6 Binary files /dev/null and b/public/pic/eqset/setcounter.png differ diff --git a/public/pic/eqset/setcritical.png b/public/pic/eqset/setcritical.png new file mode 100644 index 0000000..8dbf9cf Binary files /dev/null and b/public/pic/eqset/setcritical.png differ diff --git a/public/pic/eqset/setdefense.png b/public/pic/eqset/setdefense.png new file mode 100644 index 0000000..fa78edc Binary files /dev/null and b/public/pic/eqset/setdefense.png differ diff --git a/public/pic/eqset/setdestruction.png b/public/pic/eqset/setdestruction.png new file mode 100644 index 0000000..1379867 Binary files /dev/null and b/public/pic/eqset/setdestruction.png differ diff --git a/public/pic/eqset/sethealth.png b/public/pic/eqset/sethealth.png new file mode 100644 index 0000000..149da5a Binary files /dev/null and b/public/pic/eqset/sethealth.png differ diff --git a/public/pic/eqset/sethit.png b/public/pic/eqset/sethit.png new file mode 100644 index 0000000..2f966a1 Binary files /dev/null and b/public/pic/eqset/sethit.png differ diff --git a/public/pic/eqset/setimmunity.png b/public/pic/eqset/setimmunity.png new file mode 100644 index 0000000..d975832 Binary files /dev/null and b/public/pic/eqset/setimmunity.png differ diff --git a/public/pic/eqset/setinjury.png b/public/pic/eqset/setinjury.png new file mode 100644 index 0000000..ef11b6b Binary files /dev/null and b/public/pic/eqset/setinjury.png differ diff --git a/public/pic/eqset/setlifesteal.png b/public/pic/eqset/setlifesteal.png new file mode 100644 index 0000000..151a44f Binary files /dev/null and b/public/pic/eqset/setlifesteal.png differ diff --git a/public/pic/eqset/setpenetration.png b/public/pic/eqset/setpenetration.png new file mode 100644 index 0000000..a602ed1 Binary files /dev/null and b/public/pic/eqset/setpenetration.png differ diff --git a/public/pic/eqset/setresist.png b/public/pic/eqset/setresist.png new file mode 100644 index 0000000..3324ca8 Binary files /dev/null and b/public/pic/eqset/setresist.png differ diff --git a/public/pic/eqset/setspeed.png b/public/pic/eqset/setspeed.png new file mode 100644 index 0000000..cb0535c Binary files /dev/null and b/public/pic/eqset/setspeed.png differ diff --git a/public/pic/eqset/settorrent.png b/public/pic/eqset/settorrent.png new file mode 100644 index 0000000..8ab9c71 Binary files /dev/null and b/public/pic/eqset/settorrent.png differ diff --git a/src/api/index.ts b/src/api/index.ts index fb86665..db175f0 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -77,11 +77,40 @@ export interface HeroDetailResp { eff: number; efr: number; }; + heroSetAvgVO: { + atk: number; + hp: number; + spd: number; + def: number; + chc: number; + chd: number; + dac: number; + eff: number; + efr: number; + }; + heroSetPercentVOS: { + setName: string; + percent: number; + }[]; + heroSetShows: { + cp: number; + atk: number; + hp: number; + spd: number; + def: number; + chc: number; + chd: number; + dac: number; + eff: number; + efr: number; + hds: string; + ctr: string; + }[]; } // 查询角色详情 -export const getHeroDetail = async (heroCode: string): Promise> => { - return await Api.get>(`/epic/hero/hero-detail?heroCode=${heroCode}`) +export const getHeroDetail = async (heroCode: string): Promise => { + return await Api.get(`/epic/hero/hero-detail?heroCode=${heroCode}`) }; // 查询 GVG 阵容列表 diff --git a/src/pages/CharacterDetail.tsx b/src/pages/CharacterDetail.tsx index 71c002e..20bb07e 100644 --- a/src/pages/CharacterDetail.tsx +++ b/src/pages/CharacterDetail.tsx @@ -136,10 +136,19 @@ const mockBuilds: BuildInfo[] = [ // 套装图标映射 const setIconMap: Record = { - 'Health': '/pic/item/set/Health.png', - 'Immunity': '/pic/item/set/Immunity.png', - '生命': '/pic/item/set/Health.png', - '免疫': '/pic/item/set/Immunity.png', + '生命': '/pic/eqset/sethealth.png', + '免疫': '/pic/eqset/setimmunity.png', + '速度': '/pic/eqset/setspeed.png', + '穿透': '/pic/eqset/setpenetration.png', + '反击': '/pic/eqset/setcounter.png', + '命中': '/pic/eqset/sethit.png', + '激流': '/pic/eqset/settorrent.png', + '吸血': '/pic/eqset/setlifesteal.png', + '抵抗': '/pic/eqset/setresist.png', + '防御': '/pic/eqset/setdefense.png', + '破灭': '/pic/eqset/setdestruction.png', + '暴击': '/pic/eqset/setcritical.png', + '伤口': '/pic/eqset/setinjury.png', }; // 神器图片映射 @@ -183,22 +192,34 @@ const ATTR_LABEL_MAP: Record = { effectResistance: '效果抗性', }; +// 属性映射(API字段到显示字段的映射) +const ATTR_KEY_MAP: Record = { + atk: 'attack', + def: 'defense', + hp: 'health', + spd: 'speed', + chc: 'critChance', + chd: 'critDamage', + eff: 'effectiveness', + efr: 'effectResistance', +}; + const CharacterDetail: React.FC = () => { const {heroCode} = useParams(); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [heroDetail, setHeroDetail] = useState(null); + const [heroDetail, setHeroDetail] = useState(null); useEffect(() => { if (!heroCode) return; setLoading(true); EpicApi.getHeroDetail(heroCode) - .then(res => { - setHeroDetail(res); + .then(data => { + setHeroDetail(data); setError(null); }) - .catch(() => setError('获取角色详情失败')) + .catch(err => setError(err.message || '获取角色详情失败')) .finally(() => setLoading(false)); }, [heroCode]); @@ -222,7 +243,19 @@ const CharacterDetail: React.FC = () => { if (loading) return
加载中...
; if (error || !heroDetail) return
{error || '无数据'}
; - const {heroRespSimpleVO, hero60AttributeVO} = heroDetail; + const {heroRespSimpleVO, hero60AttributeVO, heroSetAvgVO, heroSetPercentVOS, heroSetShows} = heroDetail; + + // 格式化数字,保留一位小数,如果是整数则只显示整数 + const formatNumber = (num: number | string) => { + const numValue = typeof num === 'string' ? parseFloat(num) : num; + if (Number.isInteger(numValue)) return numValue.toString(); + return numValue.toFixed(1).replace(/\.0$/, ''); + }; + + // 格式化百分比,保留一位小数 + const formatPercent = (num: number) => { + return (num * 100).toFixed(1); + }; const renderSkillType = (type: string) => { switch (type) { @@ -255,7 +288,8 @@ const CharacterDetail: React.FC = () => {
- {heroRespSimpleVO.heroName} + {heroRespSimpleVO.heroName} {/* 属性图标 */} {heroRespSimpleVO.attribute && ( {
{heroRespSimpleVO.role && ( <> - - {heroRespSimpleVO.role} + + {heroRespSimpleVO.role} - {ROLE_LABELS[heroRespSimpleVO.role] || heroRespSimpleVO.role} + {ROLE_LABELS[heroRespSimpleVO.role] || heroRespSimpleVO.role} )}
@@ -311,35 +348,35 @@ const CharacterDetail: React.FC = () => { {hero60AttributeVO ? (
- 攻击 + 攻击 攻击: {hero60AttributeVO.atk}
- 生命 + 生命 生命: {hero60AttributeVO.hp}
- 防御 + 防御 防御: {hero60AttributeVO.def}
- 速度 + 速度 速度: {hero60AttributeVO.spd}
- 暴击率 + 暴击率 暴击率: {Math.round(hero60AttributeVO.chc * 100)}%
- 暴击伤害 + 暴击伤害 暴击伤害: {Math.round(hero60AttributeVO.chd * 100)}%
- 效果命中 + 效果命中 效果命中: {Math.round(hero60AttributeVO.eff * 100)}%
- 效果抗性 + 效果抗性 效果抗性: {Math.round(hero60AttributeVO.efr * 100)}%
@@ -353,37 +390,38 @@ const CharacterDetail: React.FC = () => {

角色配装推荐

{/* 平均属性 */} -
+

平均属性

- {Object.entries(mockAverageStats).map(([key, value]) => ( -
-
- {key} - {ATTR_LABEL_MAP[key] || key} + {Object.entries(heroSetAvgVO).map(([key, value]) => { + const displayKey = ATTR_KEY_MAP[key]; + if (!displayKey) return null; + return ( +
+
+ {displayKey} + {ATTR_LABEL_MAP[displayKey]} +
+ + {formatNumber(value)}{['critChance', 'critDamage', 'effectiveness', 'effectResistance'].includes(displayKey) ? '%' : ''} +
- - {value}{['critChance', 'critDamage', 'effectiveness', 'effectResistance'].includes(key) ? '%' : ''} - -
- ))} + ); + })}
{/* 主流套装占比 */} -
+

主流套装占比

- {mockGearSetRates.slice(0, 3).map((set, idx) => ( + {heroSetPercentVOS.map((set, idx) => (
-
- Set {idx + 1} ({set.percent}% Use Rate) + 套装 {idx + 1} ({formatPercent(set.percent)}% 使用比例)
- {set.setNames.map((name, i) => ( + {set.setName.split(',').map((name, i) => ( { {name} ) : ( - ? + ? )} {name} @@ -410,38 +447,32 @@ const CharacterDetail: React.FC = () => {
{/* 下方builds,两列布局 */}
- {mockBuilds.concat({ - stats: { - attack: 1800, - defense: 2100, - health: 18500, - critChance: 101, - critDamage: 270, - effectiveness: 5, - effectResistance: 1, - speed: 175, - }, - gearSets: ['速度', '命中'], - artifact: {name: 'Elbris Ritual Sword', img: ''}, - }).map((build, idx) => ( + {heroSetShows.map((build, idx) => (
-

Build {idx + 1}

+
+

面板 {idx + 1}

+ 最近更新:{build.ctr} +
{/* 左侧:属性 */}

属性

- {Object.entries(build.stats).map(([key, value]) => ( -
-
- {key} - {ATTR_LABEL_MAP[key] || key} + {Object.entries(build).map(([key, value]) => { + const displayKey = ATTR_KEY_MAP[key]; + if (!displayKey || key === 'cp') return null; + return ( +
+
+ {displayKey} + {ATTR_LABEL_MAP[displayKey]} +
+ + {formatNumber(value)}{['critChance', 'critDamage', 'effectiveness', 'effectResistance'].includes(displayKey) ? '%' : ''} +
- - {value}{['critChance', 'critDamage', 'effectiveness', 'effectResistance'].includes(key) ? '%' : ''} - -
- ))} + ); + })}
{/* 右侧:套装图标+神器图片+神器名 */}
@@ -449,7 +480,7 @@ const CharacterDetail: React.FC = () => {

套装组合

{/* 套装组合图标 */}
- {build.gearSets.map((set, i) => ( + {build.hds?.split(',').map((set: string, i: number) => ( setIconMap[set] ? ( {set} @@ -461,21 +492,10 @@ const CharacterDetail: React.FC = () => {
{/* 神器图片 */}
- {artifactImgMap[build.artifact.name] ? ( - {build.artifact.name} - ) : build.artifact.img ? ( - {build.artifact.name} - ) : ( -
?
- )} +
?
{/* 神器名称 */} - {build.artifact.name} + 暂无神器数据