feat(api): 添加图片识别功能并优化阵容搜索
- 新增图片识别接口和相关功能 -重构阵容搜索逻辑,支持两组英雄同时搜索 - 优化用户界面,增加上传图片预览和示例图片提示 -调整布局和样式,提升用户体验
This commit is contained in:
8
public/favicon.svg
Normal file
8
public/favicon.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="32" height="32" rx="16" fill="#C17F59"/>
|
||||||
|
<path d="M16 8L20 12H12L16 8Z" fill="#E6B17E"/>
|
||||||
|
<path d="M16 24L12 20H20L16 24Z" fill="#E6B17E"/>
|
||||||
|
<path d="M8 16L12 12V20L8 16Z" fill="#E6B17E"/>
|
||||||
|
<path d="M24 16L20 20V12L24 16Z" fill="#E6B17E"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 402 B |
BIN
public/tt.png
Normal file
BIN
public/tt.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 250 KiB |
@@ -1,5 +1,5 @@
|
|||||||
import {Api} from "../utils/axios/config";
|
import {Api} from "../utils/axios/config";
|
||||||
import type { AxiosRequestConfig } from "axios";
|
import axios, {AxiosRequestConfig} from "axios";
|
||||||
|
|
||||||
// 查询参数接口
|
// 查询参数接口
|
||||||
export interface GvgTeamQueryParams {
|
export interface GvgTeamQueryParams {
|
||||||
@@ -78,3 +78,22 @@ export const getHeroList = async () => {
|
|||||||
const response = await Api.get<Hero[]>("/epic/hero/list-all");
|
const response = await Api.get<Hero[]>("/epic/hero/list-all");
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ImageRecognitionResult {
|
||||||
|
heroNames: string[]; // 6个有序的角色名称,前3个为一组,后3个为一组
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片识别接口
|
||||||
|
export const recognizeHeroesFromImage = async (file: File): Promise<ImageRecognitionResult> => {
|
||||||
|
try {
|
||||||
|
const response = await Api.upload<string[]>('/epic/hero/recognize', file);
|
||||||
|
console.log(response)
|
||||||
|
if (response && Array.isArray(response)) {
|
||||||
|
return { heroNames: response };
|
||||||
|
}
|
||||||
|
throw new Error("图片识别失败");
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to recognize heroes from image:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {useState, useEffect} from "react";
|
import React, {useState, useEffect, useRef} from "react";
|
||||||
import * as EpicApi from '@/api/index';
|
import * as EpicApi from '@/api/index';
|
||||||
import { Character } from '@/api/types';
|
import { Character } from '@/api/types';
|
||||||
|
|
||||||
@@ -7,9 +7,15 @@ const Lineup: React.FC = () => {
|
|||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [heroes, setHeroes] = useState<EpicApi.Hero[]>([]);
|
const [heroes, setHeroes] = useState<EpicApi.Hero[]>([]);
|
||||||
const [selectedHeroes, setSelectedHeroes] = useState<EpicApi.Hero[]>([]);
|
const [selectedHeroes1, setSelectedHeroes1] = useState<EpicApi.Hero[]>([]);
|
||||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
const [selectedHeroes2, setSelectedHeroes2] = useState<EpicApi.Hero[]>([]);
|
||||||
const [searchInput, setSearchInput] = useState("");
|
const [isDropdownOpen1, setIsDropdownOpen1] = useState(false);
|
||||||
|
const [isDropdownOpen2, setIsDropdownOpen2] = useState(false);
|
||||||
|
const [searchInput1, setSearchInput1] = useState("");
|
||||||
|
const [searchInput2, setSearchInput2] = useState("");
|
||||||
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>('/tt.png');
|
||||||
|
|
||||||
// Fetch heroes data
|
// Fetch heroes data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -30,38 +36,43 @@ const Lineup: React.FC = () => {
|
|||||||
handleSearch();
|
handleSearch();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleHeroSelect = (hero: EpicApi.Hero) => {
|
const handleHeroSelect = (hero: EpicApi.Hero, group: number) => {
|
||||||
if (selectedHeroes.length < 3 && !selectedHeroes.find(h => h.id === hero.id)) {
|
if (group === 1) {
|
||||||
setSelectedHeroes([...selectedHeroes, hero]);
|
if (selectedHeroes1.length < 3 && !selectedHeroes1.find(h => h.id === hero.id)) {
|
||||||
setSearchInput("");
|
setSelectedHeroes1([...selectedHeroes1, hero]);
|
||||||
setIsDropdownOpen(false);
|
setSearchInput1("");
|
||||||
|
setIsDropdownOpen1(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedHeroes2.length < 3 && !selectedHeroes2.find(h => h.id === hero.id)) {
|
||||||
|
setSelectedHeroes2([...selectedHeroes2, hero]);
|
||||||
|
setSearchInput2("");
|
||||||
|
setIsDropdownOpen2(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHeroRemove = (heroId: number) => {
|
const handleHeroRemove = (heroId: number, group: number) => {
|
||||||
setSelectedHeroes(selectedHeroes.filter(h => h.id !== heroId));
|
if (group === 1) {
|
||||||
|
setSelectedHeroes1(selectedHeroes1.filter(h => h.id !== heroId));
|
||||||
|
} else {
|
||||||
|
setSelectedHeroes2(selectedHeroes2.filter(h => h.id !== heroId));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = async () => {
|
const handleSearch = async (group?: number) => {
|
||||||
if (selectedHeroes.length === 0) {
|
|
||||||
// If no heroes selected, fetch all lineups
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await EpicApi.getGvgTeamList([]);
|
let heroCodes: string[] = [];
|
||||||
setLineups(data);
|
if (group === 1) {
|
||||||
setError(null);
|
heroCodes = selectedHeroes1.map(hero => hero.heroCode);
|
||||||
} catch (err) {
|
} else if (group === 2) {
|
||||||
console.error('Failed to fetch lineups:', err);
|
heroCodes = selectedHeroes2.map(hero => hero.heroCode);
|
||||||
setError(err instanceof Error ? err.message : '获取阵容数据失败,请稍后再试');
|
} else {
|
||||||
} finally {
|
// 如果未指定组,使用第一组的英雄
|
||||||
setLoading(false);
|
heroCodes = selectedHeroes1.map(hero => hero.heroCode);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const heroCodes = selectedHeroes.map(hero => hero.heroCode);
|
|
||||||
const data = await EpicApi.getGvgTeamList(heroCodes);
|
const data = await EpicApi.getGvgTeamList(heroCodes);
|
||||||
setLineups(data);
|
setLineups(data);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -73,22 +84,188 @@ const Lineup: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = (group: number) => {
|
||||||
setSelectedHeroes([]);
|
if (group === 1) {
|
||||||
setSearchInput("");
|
setSelectedHeroes1([]);
|
||||||
setLineups([]);
|
setSearchInput1("");
|
||||||
setError(null);
|
} else {
|
||||||
handleSearch(); // Fetch all lineups after reset
|
setSelectedHeroes2([]);
|
||||||
|
setSearchInput2("");
|
||||||
|
}
|
||||||
|
handleSearch(group);
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredHeroes = heroes.filter(hero => {
|
const filteredHeroes = (searchInput: string) => heroes.filter(hero => {
|
||||||
const searchLower = searchInput.toLowerCase();
|
const searchLower = searchInput.toLowerCase();
|
||||||
return hero.heroName.toLowerCase().includes(searchLower) ||
|
return hero.heroName.toLowerCase().includes(searchLower) ||
|
||||||
(hero.nickName && hero.nickName.toLowerCase().includes(searchLower));
|
(hero.nickName && hero.nickName.toLowerCase().includes(searchLower));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleImageUpload = async (file: File) => {
|
||||||
|
setIsUploading(true);
|
||||||
|
try {
|
||||||
|
// 创建预览图片URL
|
||||||
|
const previewUrl = URL.createObjectURL(file);
|
||||||
|
setPreviewImage(previewUrl);
|
||||||
|
|
||||||
|
const result = await EpicApi.recognizeHeroesFromImage(file);
|
||||||
|
|
||||||
|
// 更新第一组英雄,保持顺序
|
||||||
|
const heroes1 = result.heroNames.slice(0, 3).map(name => {
|
||||||
|
const hero = heroes.find(h => h.heroName === name);
|
||||||
|
return hero;
|
||||||
|
}).filter((hero): hero is EpicApi.Hero => hero !== undefined);
|
||||||
|
setSelectedHeroes1(heroes1);
|
||||||
|
|
||||||
|
// 更新第二组英雄,保持顺序
|
||||||
|
const heroes2 = result.heroNames.slice(3, 6).map(name => {
|
||||||
|
const hero = heroes.find(h => h.heroName === name);
|
||||||
|
return hero;
|
||||||
|
}).filter((hero): hero is EpicApi.Hero => hero !== undefined);
|
||||||
|
setSelectedHeroes2(heroes2);
|
||||||
|
|
||||||
|
// 自动触发搜索
|
||||||
|
// handleSearch();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to process image:', error);
|
||||||
|
setError('图片识别失败,请重试');
|
||||||
|
} finally {
|
||||||
|
setIsUploading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清理预览图片URL
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (previewImage) {
|
||||||
|
URL.revokeObjectURL(previewImage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [previewImage]);
|
||||||
|
|
||||||
|
const handlePaste = async (e: React.ClipboardEvent) => {
|
||||||
|
const items = e.clipboardData.items;
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (items[i].type.indexOf('image') !== -1) {
|
||||||
|
const file = items[i].getAsFile();
|
||||||
|
if (file) {
|
||||||
|
await handleImageUpload(file);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
await handleImageUpload(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderHeroSearch = (group: number) => {
|
||||||
|
const selectedHeroes = group === 1 ? selectedHeroes1 : selectedHeroes2;
|
||||||
|
const isDropdownOpen = group === 1 ? isDropdownOpen1 : isDropdownOpen2;
|
||||||
|
const searchInput = group === 1 ? searchInput1 : searchInput2;
|
||||||
|
const setSearchInput = group === 1 ? setSearchInput1 : setSearchInput2;
|
||||||
|
const setIsDropdownOpen = group === 1 ? setIsDropdownOpen1 : setIsDropdownOpen2;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#1A1412] text-white font-sans relative">
|
<div className="relative flex gap-2">
|
||||||
|
<div
|
||||||
|
className="bg-[#1A1412] border border-[#C17F59]/30 px-4 py-2 rounded-md text-sm w-[32rem] text-[#E6B17E] cursor-pointer flex items-center justify-between h-12"
|
||||||
|
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-wrap gap-2 flex-1 items-center">
|
||||||
|
{selectedHeroes.length > 0 ? (
|
||||||
|
selectedHeroes.map((hero) => (
|
||||||
|
<div
|
||||||
|
key={hero.id}
|
||||||
|
className="flex items-center gap-2 px-2 py-1 bg-[#2A211E] rounded-md"
|
||||||
|
>
|
||||||
|
<span>{hero.heroName}</span>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHeroRemove(hero.id, group);
|
||||||
|
}}
|
||||||
|
className="text-[#C17F59] hover:text-[#E6B17E]"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<span className="text-[#9B8579]">选择防守英雄(最多3个)</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className={`transform transition-transform duration-200 ${isDropdownOpen ? 'rotate-180' : ''} ml-2`}>
|
||||||
|
▼
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="bg-[#1A1412] border border-[#C17F59]/30 px-4 py-2 rounded-md text-sm text-[#E6B17E] hover:bg-[#2A211E] hover:border-[#C17F59] transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
|
||||||
|
onClick={() => handleSearch(group)}
|
||||||
|
disabled={selectedHeroes.length === 0}
|
||||||
|
>
|
||||||
|
搜索
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="bg-[#1A1412] border border-[#C17F59]/30 px-4 py-2 rounded-md text-sm text-[#E6B17E] hover:bg-[#2A211E] hover:border-[#C17F59] transition-colors duration-200 whitespace-nowrap"
|
||||||
|
onClick={() => handleReset(group)}
|
||||||
|
>
|
||||||
|
重置
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isDropdownOpen && (
|
||||||
|
<div className="absolute top-full left-0 mt-1 bg-[#1A1412] border border-[#C17F59]/30 rounded-md shadow-lg max-h-60 overflow-y-auto z-50 w-[32rem]">
|
||||||
|
<div className="p-2 border-b border-[#C17F59]/30">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchInput}
|
||||||
|
onChange={(e) => setSearchInput(e.target.value)}
|
||||||
|
placeholder="输入英雄名称或昵称搜索..."
|
||||||
|
className="w-full px-3 py-2 bg-[#1A1412] text-[#E6B17E] border border-[#C17F59]/30 rounded-md focus:outline-none focus:border-[#C17F59]"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{filteredHeroes(searchInput).map((hero) => (
|
||||||
|
<div
|
||||||
|
key={hero.id}
|
||||||
|
className={`px-4 py-2 cursor-pointer ${
|
||||||
|
selectedHeroes.find(h => h.id === hero.id)
|
||||||
|
? "bg-[#2A211E] text-[#E6B17E]"
|
||||||
|
: "text-[#E6B17E] hover:bg-[#2A211E]"
|
||||||
|
} ${selectedHeroes.length >= 3 && !selectedHeroes.find(h => h.id === hero.id) ? "opacity-50 cursor-not-allowed" : ""}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedHeroes.length < 3 || selectedHeroes.find(h => h.id === hero.id)) {
|
||||||
|
handleHeroSelect(hero, group);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
src={hero.headImgUrl}
|
||||||
|
alt={hero.heroName}
|
||||||
|
className="w-6 h-6 rounded-full"
|
||||||
|
/>
|
||||||
|
<span>{hero.heroName}</span>
|
||||||
|
{hero.nickName && (
|
||||||
|
<span className="text-[#9B8579] text-sm">({hero.nickName})</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[#1A1412] text-white font-sans relative" onPaste={handlePaste}>
|
||||||
{/* 背景装饰 */}
|
{/* 背景装饰 */}
|
||||||
<div className="absolute inset-0 bg-gradient-to-b from-[#1A1412] via-[#2A211E] to-[#1A1412]"></div>
|
<div className="absolute inset-0 bg-gradient-to-b from-[#1A1412] via-[#2A211E] to-[#1A1412]"></div>
|
||||||
|
|
||||||
@@ -115,101 +292,65 @@ const Lineup: React.FC = () => {
|
|||||||
<span>感谢分享</span>
|
<span>感谢分享</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 英雄多选下拉框 */}
|
<div className="flex gap-6">
|
||||||
<div className="relative flex gap-2">
|
<div className="flex-1">
|
||||||
<div
|
{/* 图片上传区域 */}
|
||||||
className="bg-[#1A1412] border border-[#C17F59]/30 px-4 py-2 rounded-md text-sm w-[32rem] text-[#E6B17E] cursor-pointer flex items-center justify-between h-12"
|
<div className="flex items-center gap-4 mb-4">
|
||||||
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
<div className="flex-1">
|
||||||
>
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex flex-wrap gap-2 flex-1 items-center">
|
<input
|
||||||
{selectedHeroes.length > 0 ? (
|
type="file"
|
||||||
selectedHeroes.map((hero) => (
|
ref={fileInputRef}
|
||||||
<div
|
onChange={handleFileChange}
|
||||||
key={hero.id}
|
accept="image/*"
|
||||||
className="flex items-center gap-2 px-2 py-1 bg-[#2A211E] rounded-md"
|
className="hidden"
|
||||||
>
|
/>
|
||||||
<span>{hero.heroName}</span>
|
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={() => fileInputRef.current?.click()}
|
||||||
e.stopPropagation();
|
className="bg-[#1A1412] border border-[#C17F59]/30 px-4 py-2 rounded-md text-sm text-[#E6B17E] hover:bg-[#2A211E] hover:border-[#C17F59] transition-colors duration-200 whitespace-nowrap"
|
||||||
handleHeroRemove(hero.id);
|
|
||||||
}}
|
|
||||||
className="text-[#C17F59] hover:text-[#E6B17E]"
|
|
||||||
>
|
>
|
||||||
×
|
上传图片
|
||||||
</button>
|
</button>
|
||||||
</div>
|
<span className="text-[#9B8579] text-sm">
|
||||||
))
|
或直接粘贴图片 (Ctrl+V),请参考右侧图片示例
|
||||||
) : (
|
|
||||||
<span className="text-[#9B8579]">选择防守英雄(最多3个)</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<span className={`transform transition-transform duration-200 ${isDropdownOpen ? 'rotate-180' : ''} ml-2`}>
|
|
||||||
▼
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
className="bg-[#1A1412] border border-[#C17F59]/30 px-4 py-2 rounded-md text-sm text-[#E6B17E] hover:bg-[#2A211E] hover:border-[#C17F59] transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
|
|
||||||
onClick={handleSearch}
|
|
||||||
disabled={selectedHeroes.length === 0}
|
|
||||||
>
|
|
||||||
搜索
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="bg-[#1A1412] border border-[#C17F59]/30 px-4 py-2 rounded-md text-sm text-[#E6B17E] hover:bg-[#2A211E] hover:border-[#C17F59] transition-colors duration-200 whitespace-nowrap"
|
|
||||||
onClick={handleReset}
|
|
||||||
>
|
|
||||||
重置
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{isDropdownOpen && (
|
|
||||||
<div className="absolute top-full left-0 mt-1 bg-[#1A1412] border border-[#C17F59]/30 rounded-md shadow-lg max-h-60 overflow-y-auto z-50 w-[32rem]">
|
|
||||||
<div className="p-2 border-b border-[#C17F59]/30">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={searchInput}
|
|
||||||
onChange={(e) => setSearchInput(e.target.value)}
|
|
||||||
placeholder="输入英雄名称或昵称搜索..."
|
|
||||||
className="w-full px-3 py-2 bg-[#1A1412] text-[#E6B17E] border border-[#C17F59]/30 rounded-md focus:outline-none focus:border-[#C17F59]"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{filteredHeroes.map((hero) => (
|
</div>
|
||||||
<div
|
|
||||||
key={hero.id}
|
{/* 第一组搜索 */}
|
||||||
className={`px-4 py-2 cursor-pointer ${
|
<div className="mb-4">
|
||||||
selectedHeroes.find(h => h.id === hero.id)
|
<h3 className="text-[#E6B17E] mb-2">防守阵容 1</h3>
|
||||||
? "bg-[#2A211E] text-[#E6B17E]"
|
{renderHeroSearch(1)}
|
||||||
: "text-[#E6B17E] hover:bg-[#2A211E]"
|
</div>
|
||||||
} ${selectedHeroes.length >= 3 && !selectedHeroes.find(h => h.id === hero.id) ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
||||||
onClick={() => {
|
{/* 第二组搜索 */}
|
||||||
if (selectedHeroes.length < 3 || selectedHeroes.find(h => h.id === hero.id)) {
|
<div>
|
||||||
handleHeroSelect(hero);
|
<h3 className="text-[#E6B17E] mb-2">防守阵容 2</h3>
|
||||||
}
|
{renderHeroSearch(2)}
|
||||||
}}
|
</div>
|
||||||
>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
|
{/* 图片预览区域 */}
|
||||||
|
{previewImage && (
|
||||||
|
<div className="w-64 flex-shrink-0">
|
||||||
|
<h3 className="text-[#E6B17E] mb-2">上传图片预览</h3>
|
||||||
|
<div className="relative aspect-square rounded-lg overflow-hidden border border-[#C17F59]/30">
|
||||||
<img
|
<img
|
||||||
src={hero.headImgUrl}
|
src={previewImage}
|
||||||
alt={hero.heroName}
|
alt="预览图片"
|
||||||
className="w-6 h-6 rounded-full"
|
className="w-full h-full object-contain"
|
||||||
/>
|
/>
|
||||||
<span>{hero.heroName}</span>
|
|
||||||
{hero.nickName && (
|
|
||||||
<span className="text-[#9B8579] text-sm">({hero.nickName})</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/* 示例图片提示 */}
|
||||||
))}
|
{previewImage === '/tt.png'}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 加载状态 */}
|
{/* 加载状态 */}
|
||||||
{loading && (
|
{(loading || isUploading) && (
|
||||||
<div className="flex justify-center items-center py-12">
|
<div className="flex justify-center items-center py-12">
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-[#E6B17E]"></div>
|
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-[#E6B17E]"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,7 +366,8 @@ const Lineup: React.FC = () => {
|
|||||||
{/* 阵容列表 */}
|
{/* 阵容列表 */}
|
||||||
{!loading && (
|
{!loading && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{lineups.map((lineup) => (
|
{lineups.length > 0 ? (
|
||||||
|
lineups.map((lineup) => (
|
||||||
<div
|
<div
|
||||||
key={lineup.id}
|
key={lineup.id}
|
||||||
className="bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg overflow-hidden border border-[#C17F59]/30 hover:border-[#C17F59] transition-all duration-300 transform hover:-translate-y-1 hover:shadow-xl hover:shadow-[#C17F59]/10 backdrop-blur-sm"
|
className="bg-gradient-to-br from-[#2A211E] to-[#1A1412] rounded-lg overflow-hidden border border-[#C17F59]/30 hover:border-[#C17F59] transition-all duration-300 transform hover:-translate-y-1 hover:shadow-xl hover:shadow-[#C17F59]/10 backdrop-blur-sm"
|
||||||
@@ -300,7 +442,16 @@ const Lineup: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col items-center justify-center py-12 text-[#9B8579]">
|
||||||
|
<svg className="w-16 h-16 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<p className="text-lg">暂无对战阵容信息</p>
|
||||||
|
<p className="text-sm mt-2">请尝试其他英雄组合</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user