Compare commits
10 Commits
00b7cdc382
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bfcc83bdc | ||
|
|
d859e54902 | ||
|
|
c945c99174 | ||
|
|
b391ee88d4 | ||
|
|
2a898c9bcc | ||
|
|
739a35249b | ||
|
|
bc69a6c0e0 | ||
|
|
73c10b5f5e | ||
|
|
9fb420c05e | ||
|
|
137ea8ff7d |
52
.drone.yml
Normal file
52
.drone.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- manual
|
||||
- custom
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: node:18-alpine
|
||||
commands:
|
||||
- echo "=== 安装依赖 ==="
|
||||
- npm install -g pnpm
|
||||
- pnpm install
|
||||
- echo "=== 构建项目 ==="
|
||||
- pnpm build
|
||||
- echo "=== 检查构建结果 ==="
|
||||
- ls -la dist/
|
||||
|
||||
- name: upload
|
||||
image: appleboy/drone-scp
|
||||
settings:
|
||||
host: 193.112.151.199
|
||||
username: root
|
||||
password:
|
||||
from_secret: scp_password
|
||||
port: 222
|
||||
source:
|
||||
- dist/*
|
||||
target: /opt/1panel/apps/openresty/openresty/www/sites/epic7/index
|
||||
strip_components: 1
|
||||
|
||||
- name: set permissions
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
host: 193.112.151.199
|
||||
username: root
|
||||
password:
|
||||
from_secret: scp_password
|
||||
port: 222
|
||||
script:
|
||||
- echo "=== 检查部署文件 ==="
|
||||
- ls -la /opt/1panel/apps/openresty/openresty/www/sites/epic7/index/
|
||||
- echo "=== 设置文件权限 ==="
|
||||
- chown -R www-data:www-data /opt/1panel/apps/openresty/openresty/www/sites/epic7/index/
|
||||
- chmod -R 755 /opt/1panel/apps/openresty/openresty/www/sites/epic7/index/
|
||||
- echo "=== 重新加载OpenResty配置 ==="
|
||||
- docker exec $(docker ps -q --filter "name=1Panel-openresty") nginx -s reload || echo "Failed to reload OpenResty config"
|
||||
- echo "=== 检查OpenResty容器状态 ==="
|
||||
- docker ps | grep openresty || echo "OpenResty container not found"
|
||||
@@ -1,315 +0,0 @@
|
||||
name: Epic UI Build & Deploy
|
||||
run-name: ${{ gitea.actor }} 正在构建 Epic UI 前端项目 🚀
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master, develop ]
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: gitea_labels
|
||||
container:
|
||||
image: gitea-ci-bash:latest
|
||||
env:
|
||||
# 指定容器将工具缓存路径存放到 /opt/hostedtoolcache,该目录是Gitea Runner的标准工具缓存目录
|
||||
RUNNER_TOOL_CACHE: /opt/hostedtoolcache
|
||||
volumes:
|
||||
# 直接挂载到指定的宿主机路径
|
||||
- /opt/gitea-runner-cache:/opt/hostedtoolcache
|
||||
# 挂载生产环境目录
|
||||
- /opt/1panel/apps/openresty/openresty/www/sites/epic7/index:/opt/1panel/apps/openresty/openresty/www/sites/epic7/index
|
||||
steps:
|
||||
- name: 检出代码
|
||||
shell: bash
|
||||
run: |
|
||||
echo "📥 检出代码到工作目录..."
|
||||
|
||||
# 解析分支名
|
||||
BRANCH_NAME=$(echo "${{ gitea.ref }}" | sed 's#refs/heads/##')
|
||||
|
||||
# 拉取代码(使用token鉴权)
|
||||
git clone --depth=1 -b "$BRANCH_NAME" "http://1c18ee1ab9a9cb291506d0c5c016a33be7d59e8c:x-oauth-basic@gitea.htoop.cn/${{ gitea.repository }}.git" .
|
||||
|
||||
echo "✅ 代码检出成功"
|
||||
|
||||
- name: 安装Node.js环境
|
||||
shell: bash
|
||||
run: |
|
||||
echo "🔧 安装Node.js环境..."
|
||||
if command -v node &> /dev/null; then
|
||||
echo "✅ Node.js已安装: $(node --version)"
|
||||
else
|
||||
echo "📥 下载并安装Node.js..."
|
||||
NODE_VERSION="18.19.0"
|
||||
NODE_ARCH="linux-x64"
|
||||
MIRRORS=(
|
||||
"https://mirrors.aliyun.com/nodejs-release/v${NODE_VERSION}/node-v${NODE_VERSION}-${NODE_ARCH}.tar.xz"
|
||||
"https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-${NODE_ARCH}.tar.xz"
|
||||
)
|
||||
DOWNLOAD_SUCCESS=false
|
||||
for mirror in "${MIRRORS[@]}"; do
|
||||
echo "尝试从镜像下载: $mirror"
|
||||
if wget -q --timeout=30 --tries=3 "$mirror" -O node.tar.xz; then
|
||||
echo "✅ 下载成功: $mirror"
|
||||
DOWNLOAD_SUCCESS=true
|
||||
break
|
||||
else
|
||||
echo "❌ 下载失败: $mirror"
|
||||
continue
|
||||
fi
|
||||
done
|
||||
if [ "$DOWNLOAD_SUCCESS" = false ]; then
|
||||
echo "❌ 所有镜像源下载失败"
|
||||
exit 1
|
||||
fi
|
||||
echo "解压Node.js到/usr/local..."
|
||||
tar -C /usr/local -xJf node.tar.xz --strip-components=1
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
rm node.tar.xz
|
||||
echo "✅ Node.js安装完成"
|
||||
fi
|
||||
|
||||
- name: 安装pnpm
|
||||
shell: bash
|
||||
run: |
|
||||
echo "📦 安装pnpm包管理器..."
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
|
||||
if command -v pnpm &> /dev/null; then
|
||||
echo "✅ pnpm已安装: $(pnpm --version)"
|
||||
else
|
||||
echo "📥 安装pnpm..."
|
||||
npm install -g pnpm
|
||||
echo "✅ pnpm安装完成: $(pnpm --version)"
|
||||
fi
|
||||
|
||||
- name: 配置pnpm缓存
|
||||
shell: bash
|
||||
run: |
|
||||
echo "📦 配置pnpm缓存..."
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
|
||||
# 配置pnpm缓存目录
|
||||
PNPM_STORE_DIR="/opt/hostedtoolcache/pnpm-store"
|
||||
mkdir -p "$PNPM_STORE_DIR"
|
||||
|
||||
# 设置pnpm使用缓存目录
|
||||
pnpm config set store-dir "$PNPM_STORE_DIR"
|
||||
pnpm config set cache-dir "/opt/hostedtoolcache/pnpm-cache"
|
||||
|
||||
echo "✅ pnpm缓存配置完成"
|
||||
|
||||
- name: 恢复依赖缓存
|
||||
shell: bash
|
||||
run: |
|
||||
echo "📦 检查并恢复依赖缓存..."
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
|
||||
# 生成缓存键
|
||||
CACHE_KEY=$(md5sum pnpm-lock.yaml | cut -d' ' -f1)
|
||||
echo "缓存键: $CACHE_KEY"
|
||||
|
||||
# 使用 /opt/hostedtoolcache 目录
|
||||
CACHE_FILE="/opt/hostedtoolcache/node_modules_${CACHE_KEY}.tar.gz"
|
||||
echo "📁 检查缓存文件: $CACHE_FILE"
|
||||
|
||||
if [ -f "$CACHE_FILE" ]; then
|
||||
echo "✅ 找到缓存文件: $CACHE_FILE"
|
||||
echo "📦 缓存文件大小: $(du -sh "$CACHE_FILE" | cut -f1)"
|
||||
echo "正在恢复缓存..."
|
||||
|
||||
if tar -xzf "$CACHE_FILE"; then
|
||||
echo "✅ 缓存恢复成功"
|
||||
echo "📦 恢复的 node_modules 大小:"
|
||||
du -sh node_modules 2>/dev/null || echo "node_modules 目录不存在"
|
||||
else
|
||||
echo "❌ 缓存恢复失败"
|
||||
echo "📥 将重新安装依赖"
|
||||
fi
|
||||
else
|
||||
echo "📥 未找到缓存文件,将重新安装依赖"
|
||||
fi
|
||||
|
||||
- name: 安装项目依赖
|
||||
shell: bash
|
||||
run: |
|
||||
echo "📦 安装项目依赖..."
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
|
||||
# 检查是否已有node_modules
|
||||
if [ -d "node_modules" ] && [ -f "node_modules/.pnpm-debug.log" ]; then
|
||||
echo "✅ 检测到已存在的依赖,跳过安装"
|
||||
else
|
||||
echo "📥 安装项目依赖..."
|
||||
pnpm install --frozen-lockfile --prefer-offline
|
||||
echo "✅ 依赖安装完成"
|
||||
fi
|
||||
|
||||
- name: 保存依赖缓存
|
||||
shell: bash
|
||||
run: |
|
||||
echo "💾 保存依赖缓存..."
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
|
||||
# 生成缓存键
|
||||
CACHE_KEY=$(md5sum pnpm-lock.yaml | cut -d' ' -f1)
|
||||
echo "缓存键: $CACHE_KEY"
|
||||
|
||||
# 检查node_modules是否存在
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo "❌ node_modules 目录不存在,跳过缓存保存"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查缓存文件是否已存在且是最新的
|
||||
CACHE_FILE="/opt/hostedtoolcache/node_modules_${CACHE_KEY}.tar.gz"
|
||||
if [ -f "$CACHE_FILE" ]; then
|
||||
echo "✅ 缓存文件已存在且是最新的,跳过创建: $CACHE_FILE"
|
||||
echo "📦 现有缓存文件大小: $(du -sh "$CACHE_FILE" | cut -f1)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 缓存文件不存在,需要创建
|
||||
echo "📦 正在创建缓存文件: $CACHE_FILE"
|
||||
|
||||
if tar -czf "$CACHE_FILE" node_modules; then
|
||||
echo "✅ 缓存已保存到: $CACHE_FILE"
|
||||
echo "📦 缓存文件大小: $(du -sh "$CACHE_FILE" | cut -f1)"
|
||||
else
|
||||
echo "❌ 缓存保存失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
- name: 构建项目
|
||||
shell: bash
|
||||
run: |
|
||||
echo "🔨 构建 Epic UI 前端项目..."
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
|
||||
# 执行构建
|
||||
pnpm build
|
||||
|
||||
echo "✅ 构建完成"
|
||||
|
||||
- name: 检查构建产物
|
||||
shell: bash
|
||||
run: |
|
||||
echo "📦 构建产物信息:"
|
||||
if [ -d "dist" ]; then
|
||||
echo "✅ 找到dist目录"
|
||||
ls -la dist/
|
||||
echo "dist目录大小: $(du -sh dist | cut -f1)"
|
||||
else
|
||||
echo "❌ 未找到dist目录"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: 显示项目信息
|
||||
shell: bash
|
||||
run: |
|
||||
echo "📋 项目信息:"
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
echo "Node.js 版本: $(node --version)"
|
||||
echo "npm 版本: $(npm --version)"
|
||||
echo "pnpm 版本: $(pnpm --version)"
|
||||
echo "构建时间: $(date)"
|
||||
echo "分支: ${{ gitea.ref }}"
|
||||
echo "提交: ${{ gitea.sha }}"
|
||||
|
||||
- name: 部署到生产环境
|
||||
shell: bash
|
||||
run: |
|
||||
echo "🚀 部署到生产环境..."
|
||||
|
||||
# 生产环境目录(容器内路径,直接映射到nginx静态文件目录)
|
||||
PROD_DIR="/opt/1panel/apps/openresty/openresty/www/sites/epic7/index"
|
||||
|
||||
# 检查构建产物
|
||||
if [ ! -d "dist" ]; then
|
||||
echo "❌ 构建产物不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📦 构建产物内容:"
|
||||
ls -la dist/
|
||||
|
||||
# 检查生产目录挂载
|
||||
echo "📁 检查生产目录挂载状态..."
|
||||
if [ -d "$PROD_DIR" ]; then
|
||||
echo "✅ 生产目录已存在: $PROD_DIR"
|
||||
echo "📁 当前生产目录内容:"
|
||||
ls -la "$PROD_DIR" 2>/dev/null || echo "目录为空或无法访问"
|
||||
else
|
||||
echo "📁 生产目录不存在,将创建: $PROD_DIR"
|
||||
fi
|
||||
|
||||
# 备份当前生产环境
|
||||
if [ -d "$PROD_DIR" ] && [ "$(ls -A "$PROD_DIR" 2>/dev/null)" ]; then
|
||||
BACKUP_DIR="/opt/prod_backup_$(date +%Y%m%d_%H%M%S)"
|
||||
echo "📦 备份当前生产环境到: $BACKUP_DIR"
|
||||
cp -r "$PROD_DIR" "$BACKUP_DIR"
|
||||
fi
|
||||
|
||||
# 确保生产目录存在并清空
|
||||
echo "📤 部署构建产物到nginx静态文件目录..."
|
||||
mkdir -p "$PROD_DIR"
|
||||
rm -rf "$PROD_DIR"/*
|
||||
|
||||
# 复制构建产物到生产环境
|
||||
echo "📦 复制构建产物..."
|
||||
if cp -r dist/* "$PROD_DIR/"; then
|
||||
echo "✅ 构建产物复制成功"
|
||||
else
|
||||
echo "❌ 构建产物复制失败"
|
||||
echo "📁 检查目标目录权限和空间..."
|
||||
df -h "$PROD_DIR" 2>/dev/null || echo "无法检查磁盘空间"
|
||||
ls -ld "$PROD_DIR" 2>/dev/null || echo "无法检查目录权限"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 设置权限
|
||||
chmod -R 755 "$PROD_DIR"
|
||||
|
||||
# 强制同步文件系统 - 确保文件写入到宿主机
|
||||
echo "🔄 同步文件系统..."
|
||||
sync
|
||||
|
||||
# 等待文件系统同步完成
|
||||
echo "⏳ 等待文件系统同步..."
|
||||
sleep 3
|
||||
|
||||
# 再次强制同步
|
||||
sync
|
||||
|
||||
# 验证文件是否真的写入到宿主机
|
||||
echo "🔍 验证文件同步状态..."
|
||||
if [ -f "$PROD_DIR/index.html" ]; then
|
||||
echo "✅ 确认index.html已同步到宿主机"
|
||||
echo "📋 文件大小: $(ls -lh "$PROD_DIR/index.html" | awk '{print $5}')"
|
||||
echo "📋 文件时间: $(ls -l "$PROD_DIR/index.html" | awk '{print $6, $7, $8}')"
|
||||
else
|
||||
echo "❌ index.html未同步到宿主机"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ 部署完成"
|
||||
echo "📁 生产环境目录: $PROD_DIR (直接映射到宿主机)"
|
||||
echo "📦 部署的文件:"
|
||||
ls -la "$PROD_DIR"
|
||||
|
||||
# 验证部署结果
|
||||
echo "🔍 验证部署结果..."
|
||||
echo "📋 文件数量: $(find "$PROD_DIR" -type f | wc -l)"
|
||||
echo "📋 目录数量: $(find "$PROD_DIR" -type d | wc -l)"
|
||||
echo "📋 总大小: $(du -sh "$PROD_DIR" | cut -f1)"
|
||||
|
||||
# 检查关键文件
|
||||
if [ -f "$PROD_DIR/index.html" ]; then
|
||||
echo "✅ index.html 存在"
|
||||
echo "📋 index.html 大小: $(ls -lh "$PROD_DIR/index.html" | awk '{print $5}')"
|
||||
else
|
||||
echo "❌ index.html 不存在"
|
||||
fi
|
||||
5
.npmrc
5
.npmrc
@@ -1,5 +0,0 @@
|
||||
# pnpm 性能优化配置
|
||||
registry=https://registry.npmmirror.com/
|
||||
prefer-offline=true
|
||||
auto-install-peers=true
|
||||
shamefully-hoist=true
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
/* body {
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useNavigate} from 'react-router-dom';
|
||||
import {Select} from 'antd';
|
||||
import {Input} from 'antd';
|
||||
import type {Hero} from '@/api/index';
|
||||
import * as EpicApi from '@/api/index';
|
||||
|
||||
@@ -14,59 +14,30 @@ type ExtendedHero = Hero & {
|
||||
};
|
||||
|
||||
// 自定义样式覆盖 Ant Design 默认样式
|
||||
const selectStyles = `
|
||||
.ant-select {
|
||||
const inputStyles = `
|
||||
.ant-input, .ant-input-affix-wrapper, .ant-input-affix-wrapper input {
|
||||
background-color: #1A1412 !important;
|
||||
border-color: rgba(193, 127, 89, 0.3) !important;
|
||||
color: #E6B17E !important;
|
||||
border-radius: 0.5rem !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.ant-input:focus, .ant-input-affix-wrapper:focus, .ant-input-affix-wrapper-focused, .ant-input:active {
|
||||
background-color: #1A1412 !important;
|
||||
border-color: #C17F59 !important;
|
||||
color: #E6B17E !important;
|
||||
}
|
||||
|
||||
.ant-select .ant-select-selector {
|
||||
background-color: #1A1412 !important;
|
||||
border-color: #C17F59 !important;
|
||||
color: #E6B17E !important;
|
||||
}
|
||||
|
||||
.ant-select-focused .ant-select-selector {
|
||||
border-color: #C17F59 !important;
|
||||
box-shadow: 0 0 0 2px rgba(193, 127, 89, 0.2) !important;
|
||||
}
|
||||
|
||||
.ant-select-dropdown {
|
||||
background-color: #1A1412 !important;
|
||||
border-color: #C17F59 !important;
|
||||
}
|
||||
|
||||
.ant-select-item {
|
||||
background-color: #1A1412 !important;
|
||||
color: #E6B17E !important;
|
||||
}
|
||||
|
||||
.ant-select-item-option-selected {
|
||||
background-color: #2A211E !important;
|
||||
color: #E6B17E !important;
|
||||
}
|
||||
|
||||
.ant-select-item-option-active {
|
||||
background-color: #2A211E !important;
|
||||
color: #E6B17E !important;
|
||||
}
|
||||
|
||||
.ant-select-selection-placeholder {
|
||||
.ant-input::placeholder, .ant-input-affix-wrapper input::placeholder {
|
||||
color: #9B8579 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.ant-select-selection-item {
|
||||
.ant-input-clear-icon {
|
||||
color: #E6B17E !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-select-arrow {
|
||||
color: #E6B17E !important;
|
||||
}
|
||||
|
||||
.ant-select-clear {
|
||||
background-color: #1A1412 !important;
|
||||
color: #E6B17E !important;
|
||||
.ant-input-clear-icon:hover {
|
||||
color: #C17F59 !important;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -111,18 +82,24 @@ const Characters: React.FC = () => {
|
||||
const [selectedRole, setSelectedRole] = useState<string>("all");
|
||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||
const [heroes, setHeroes] = useState<ExtendedHero[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 拉取英雄数据
|
||||
useEffect(() => {
|
||||
EpicApi.getHeroList().then(data => {
|
||||
// console.log('Heroes:', data);
|
||||
// 类型转换,假设 API 返回的数据包含所需的所有属性
|
||||
setLoading(true);
|
||||
EpicApi.getHeroList()
|
||||
.then(data => {
|
||||
const extendedData = data as ExtendedHero[];
|
||||
setHeroes(extendedData);
|
||||
setAllHeroes(extendedData);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setHeroes([]);
|
||||
setAllHeroes([]);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
// 过滤逻辑
|
||||
@@ -157,15 +134,15 @@ const Characters: React.FC = () => {
|
||||
return <img src={`/pic/role/${role}.png`} alt={role} className="w-7 h-7" />;
|
||||
};
|
||||
|
||||
// 处理英雄选择
|
||||
const handleHeroSelect = (value: string) => {
|
||||
setSearchTerm(value);
|
||||
// 处理搜索输入
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchTerm(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#1A1412] text-white font-sans relative">
|
||||
{/* 注入自定义样式 */}
|
||||
<style dangerouslySetInnerHTML={{ __html: selectStyles }} />
|
||||
<style dangerouslySetInnerHTML={{ __html: inputStyles }} />
|
||||
{/* 背景装饰 */}
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-[#1A1412] via-[#2A211E] to-[#1A1412]"></div>
|
||||
|
||||
@@ -179,26 +156,11 @@ const Characters: React.FC = () => {
|
||||
{/* 搜索组件 */}
|
||||
<div className="bg-gradient-to-br from-[#2A211E] to-[#1A1412] p-4 rounded-lg mb-8 border border-[#C17F59]/30 backdrop-blur-sm">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="选择英雄名称/昵称"
|
||||
value={searchTerm || undefined}
|
||||
onChange={handleHeroSelect}
|
||||
onSearch={setSearchTerm}
|
||||
filterOption={(input, option) => {
|
||||
const heroName = option?.value as string;
|
||||
return heroName.toLowerCase().includes(input.toLowerCase());
|
||||
}}
|
||||
options={allHeroes.map(hero => ({
|
||||
value: hero.heroName,
|
||||
label: (
|
||||
<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>
|
||||
)
|
||||
}))}
|
||||
<Input
|
||||
placeholder="搜索英雄名称或昵称"
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
allowClear
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
@@ -246,6 +208,13 @@ const Characters: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex flex-col items-center justify-center py-16">
|
||||
<span className="inline-block w-10 h-10 mb-4 border-4 border-[#C17F59] border-t-transparent rounded-full animate-spin"></span>
|
||||
<div className="text-[#C17F59] text-lg font-medium">加载中...</div>
|
||||
</div>
|
||||
) : (
|
||||
filteredHeroes.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{filteredHeroes.map((hero) => (
|
||||
<div
|
||||
@@ -286,6 +255,17 @@ const Characters: React.FC = () => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-16">
|
||||
<div className="text-[#9B8579] text-xl font-medium mb-4">
|
||||
暂无匹配的角色数据
|
||||
</div>
|
||||
<div className="text-[#7A6B5F] text-sm">
|
||||
请尝试调整搜索条件或筛选条件
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user