600 lines
26 KiB
HTML
600 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||
<title>图鉴 · 宇森 Yusen</title>
|
||
<link rel="stylesheet" href="assets/fonts/yusen-fonts.css" />
|
||
<link rel="icon" type="image/png" sizes="48x48" href="assets/ui/logo_planet_48.png?v=20260723">
|
||
<link rel="stylesheet" href="assets/common.css" />
|
||
<script src="assets/game-api.js"></script>
|
||
<style>
|
||
/* ===== 图鉴页面专用样式(公共变量/背景/导航由 common.css 提供) ===== */
|
||
/* 动态星空背景 */
|
||
body::before{
|
||
content:'';position:fixed;inset:0;z-index:-1;
|
||
background-image:
|
||
radial-gradient(1px 1px at 10% 20%,rgba(255,255,255,.3),transparent),
|
||
radial-gradient(1px 1px at 30% 65%,rgba(255,255,255,.2),transparent),
|
||
radial-gradient(1.5px 1.5px at 55% 15%,rgba(255,255,255,.25),transparent),
|
||
radial-gradient(1px 1px at 70% 80%,rgba(255,255,255,.15),transparent),
|
||
radial-gradient(1px 1px at 85% 40%,rgba(255,255,255,.2),transparent),
|
||
radial-gradient(1.5px 1.5px at 15% 85%,rgba(255,255,255,.1),transparent),
|
||
radial-gradient(1px 1px at 45% 50%,rgba(255,255,255,.15),transparent),
|
||
radial-gradient(1px 1px at 92% 12%,rgba(255,255,255,.2),transparent);
|
||
}
|
||
|
||
/* ===== 筛选区 ===== */
|
||
.filters{
|
||
position:sticky;top:0;z-index:49;
|
||
background:#0c1120;
|
||
padding:8px 20px;border-bottom:1px solid rgba(255,255,255,.06);
|
||
box-shadow:0 1px 0 rgba(0,0,0,.4);
|
||
}
|
||
.filterRow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
|
||
.filterRow+.filterRow{margin-top:6px;}
|
||
.filterLabel{font-size:13px;color:#7b8ea8;text-transform:uppercase;letter-spacing:1px;margin-right:4px;font-weight:700;}
|
||
|
||
/* 搜索框 */
|
||
.searchBox{
|
||
margin-left:auto;display:flex;align-items:center;gap:10px;
|
||
padding:6px 14px;border-radius:999px;font-size:13px;
|
||
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);
|
||
color:#9fb3cc;transition:.18s;min-width:180px;
|
||
}
|
||
.searchBox:focus-within{
|
||
background:rgba(139,92,246,.16);border-color:rgba(139,92,246,.5);
|
||
box-shadow:0 0 16px rgba(139,92,246,.25);
|
||
}
|
||
.searchBox input{
|
||
flex:1;min-width:0;background:transparent;border:none;outline:none;
|
||
color:#e6f0fb;font:inherit;font-size:15px;
|
||
}
|
||
.searchBox input::placeholder{color:#7b8ea8;}
|
||
.searchBox .icon{font-size:16px;opacity:.75;}
|
||
.searchBox .clear{
|
||
cursor:pointer;opacity:.5;font-size:13px;padding:0 6px;
|
||
transition:.15s;
|
||
}
|
||
.searchBox .clear:hover{opacity:1;color:#ffb4b4;}
|
||
|
||
/* 分类标签 */
|
||
.catTab{
|
||
padding:5px 14px;border-radius:999px;font-size:14px;cursor:pointer;
|
||
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);color:#9fb3cc;transition:.18s;
|
||
}
|
||
.catTab:hover{background:rgba(139,92,246,.14);color:#d0dfff;border-color:rgba(139,92,246,.3);}
|
||
.catTab.active{background:rgba(139,92,246,.2);color:#fff;border-color:rgba(139,92,246,.5);box-shadow:0 0 12px rgba(139,92,246,.2);}
|
||
|
||
/* 品质筛点 */
|
||
.ql{display:inline-flex;align-items:center;gap:6px;cursor:pointer;padding:4px 11px;border-radius:8px;transition:.15s;}
|
||
.ql:hover{background:rgba(255,255,255,.04);}
|
||
.ql .dot{width:11px;height:11px;border-radius:50%;display:inline-block;}
|
||
.ql .lbl{font-size:13px;color:#9fb3cc;}
|
||
.ql.active .lbl{color:#fff;font-weight:600;}
|
||
.ql.active .dot{box-shadow:0 0 8px currentColor;}
|
||
|
||
/* ===== 数据统计条 ===== */
|
||
/* statsBar 已移除 */
|
||
|
||
/* ===== 卡片网格 ===== */
|
||
.grid{
|
||
padding:20px 24px 40px;
|
||
display:grid;
|
||
grid-template-columns:repeat(auto-fill,minmax(160px,1fr));
|
||
gap:14px;
|
||
}
|
||
@media (min-width:900px){.grid{grid-template-columns:repeat(auto-fill,minmax(180px,1fr));}}
|
||
@media (max-width:500px){.grid{padding:14px 12px;gap:10px;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));}}
|
||
|
||
/* ===== 卡片 ===== */
|
||
.card{
|
||
position:relative;border-radius:14px;cursor:pointer;user-select:none;
|
||
background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.08);
|
||
padding:16px 12px 12px;transition:.22s;overflow:hidden;
|
||
display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center;
|
||
}
|
||
.card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;border-radius:14px 14px 0 0;opacity:0;transition:.22s;}
|
||
.card:hover{transform:translateY(-3px);box-shadow:0 8px 24px rgba(0,0,0,.4);}
|
||
.card:hover::before{opacity:1;}
|
||
.card .icon{font-size:36px;line-height:1.2;margin-bottom:2px;filter:drop-shadow(0 2px 4px rgba(0,0,0,.3));}
|
||
.card .name{font-size:14px;font-weight:700;color:#e6f0fb;line-height:1.3;}
|
||
.card .sub{font-size:11px;color:#7b8ea8;line-height:1.3;}
|
||
|
||
/* 品质色条 */
|
||
.card.q-common::before{background:linear-gradient(90deg,#9ca3af,#d1d5db);}
|
||
.card.q-uncommon::before{background:linear-gradient(90deg,#22c55e,#4ade80);}
|
||
.card.q-rare::before{background:linear-gradient(90deg,#3b82f6,#60a5fa);}
|
||
.card.q-epic::before{background:linear-gradient(90deg,#a855f7,#c084fc);}
|
||
.card.q-legendary::before{background:linear-gradient(90deg,#f59e0b,#fbbf24);}
|
||
/* 品质光晕 */
|
||
.card.q-common:hover{box-shadow:0 8px 24px rgba(0,0,0,.4),0 0 20px rgba(156,163,175,.08);}
|
||
.card.q-uncommon:hover{box-shadow:0 8px 24px rgba(0,0,0,.4),0 0 20px rgba(34,197,94,.12);}
|
||
.card.q-rare:hover{box-shadow:0 8px 24px rgba(0,0,0,.4),0 0 20px rgba(59,130,246,.12);}
|
||
.card.q-epic:hover{box-shadow:0 8px 24px rgba(0,0,0,.4),0 0 20px rgba(168,85,247,.12);}
|
||
.card.q-legendary:hover{box-shadow:0 8px 24px rgba(0,0,0,.4),0 0 20px rgba(245,158,11,.12);}
|
||
|
||
/* 品质角标 */
|
||
.card .qbadge{
|
||
position:absolute;top:8px;right:8px;
|
||
font-size:9px;font-weight:700;padding:1px 6px;border-radius:6px;
|
||
text-transform:uppercase;letter-spacing:.5px;
|
||
}
|
||
.qbadge.common{background:rgba(156,163,175,.2);color:#9ca3af;border:1px solid rgba(156,163,175,.3);}
|
||
.qbadge.uncommon{background:rgba(34,197,94,.15);color:#4ade80;border:1px solid rgba(34,197,94,.3);}
|
||
.qbadge.rare{background:rgba(59,130,246,.15);color:#60a5fa;border:1px solid rgba(59,130,246,.3);}
|
||
.qbadge.epic{background:rgba(168,85,247,.15);color:#c084fc;border:1px solid rgba(168,85,247,.3);}
|
||
.qbadge.legendary{background:rgba(245,158,11,.15);color:#fbbf24;border:1px solid rgba(245,158,11,.3);}
|
||
|
||
/* 生物/野怪/种族 专属样式 */
|
||
.card.creature{border-style:solid;border-color:rgba(100,200,255,.12);}
|
||
.card.creature .icon{font-size:38px;}
|
||
.card.creature .tags{display:flex;gap:4px;flex-wrap:wrap;justify-content:center;margin-top:2px;}
|
||
.card.creature .tag{font-size:9px;padding:1px 6px;border-radius:4px;background:rgba(255,255,255,.06);color:#8899aa;}
|
||
|
||
.card.monster{border-color:rgba(168,85,247,.18);}
|
||
.card.monster .icon{font-size:38px;}
|
||
.card.monster.tier-elite{border-color:rgba(168,85,247,.35);box-shadow:0 0 14px rgba(168,85,247,.1);}
|
||
.card.monster.tier-boss{border-color:rgba(245,158,11,.4);box-shadow:0 0 18px rgba(245,158,11,.14);}
|
||
.card.monster .tags{display:flex;gap:4px;flex-wrap:wrap;justify-content:center;margin-top:2px;}
|
||
.card.monster .tag{font-size:9px;padding:1px 6px;border-radius:4px;background:rgba(255,255,255,.06);color:#8899aa;}
|
||
|
||
.card.race{border-color:rgba(100,200,255,.14);}
|
||
.card.race .icon{font-size:38px;}
|
||
.card.race .tags{display:flex;gap:4px;flex-wrap:wrap;justify-content:center;margin-top:2px;}
|
||
.card.race .tag{font-size:9px;padding:1px 6px;border-radius:4px;background:rgba(255,255,255,.06);color:#8899aa;}
|
||
|
||
/* 野怪等级角标 */
|
||
.card .tierbadge{position:absolute;top:8px;right:8px;font-size:9px;font-weight:700;padding:1px 6px;border-radius:6px;letter-spacing:.5px;}
|
||
.tierbadge.normal{background:rgba(156,163,175,.2);color:#9ca3af;border:1px solid rgba(156,163,175,.3);}
|
||
.tierbadge.elite{background:rgba(168,85,247,.18);color:#c084fc;border:1px solid rgba(168,85,247,.35);}
|
||
.tierbadge.boss{background:rgba(245,158,11,.18);color:#fbbf24;border:1px solid rgba(245,158,11,.35);}
|
||
|
||
/* ===== 空状态 ===== */
|
||
.empty{
|
||
grid-column:1/-1;text-align:center;padding:60px 20px;color:#556677;
|
||
font-size:14px;display:none;
|
||
}
|
||
.empty.show{display:block;}
|
||
|
||
/* ===== 详情弹窗 ===== */
|
||
.modalOverlay{
|
||
position:fixed;inset:0;z-index:1000;
|
||
background:rgba(4,7,15,.65);backdrop-filter:blur(6px);
|
||
display:flex;align-items:center;justify-content:center;
|
||
opacity:0;visibility:hidden;transition:.28s;padding:20px;
|
||
}
|
||
.modalOverlay.open{opacity:1;visibility:visible;}
|
||
.modalBox{
|
||
width:520px;max-width:100%;max-height:90vh;overflow-y:auto;
|
||
background:linear-gradient(180deg,rgba(17,23,42,.98),rgba(10,14,26,.98));
|
||
border:1px solid rgba(139,92,246,.35);border-radius:18px;
|
||
box-shadow:0 12px 48px rgba(0,0,0,.6);
|
||
padding:28px 28px 24px;transform:translateY(20px);transition:transform .3s ease;
|
||
}
|
||
.modalOverlay.open .modalBox{transform:translateY(0);}
|
||
|
||
.md-close{
|
||
position:absolute;top:12px;right:14px;width:28px;height:28px;border-radius:8px;
|
||
border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.04);color:#9aa6c4;
|
||
font-size:13px;cursor:pointer;transition:.12s;
|
||
}
|
||
.md-close:hover{background:rgba(239,68,68,.2);color:#ffb4b4;}
|
||
|
||
.md-header{display:flex;align-items:center;gap:16px;margin-bottom:20px;position:relative;}
|
||
.md-icon{font-size:52px;line-height:1;}
|
||
.md-info{flex:1;}
|
||
.md-title{font-size:22px;font-weight:800;color:#eef2fb;}
|
||
|
||
.md-meta{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;}
|
||
.md-meta .mt{
|
||
font-size:11px;padding:2px 10px;border-radius:6px;
|
||
background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08);color:#9fb3cc;
|
||
}
|
||
|
||
.md-section{margin-top:18px;}
|
||
.md-section h3{font-size:13px;font-weight:700;color:#7b8ea8;margin-bottom:8px;text-transform:uppercase;letter-spacing:1px;}
|
||
|
||
.md-desc{font-size:14px;color:#cdd8e6;line-height:1.7;padding:10px 14px;border-radius:10px;background:rgba(255,255,255,.03);}
|
||
|
||
.md-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
|
||
.md-grid .gi{padding:8px 12px;border-radius:8px;background:rgba(255,255,255,.03);display:flex;justify-content:space-between;align-items:center;}
|
||
.md-grid .gi .gl{font-size:12px;color:#8899aa;}
|
||
.md-grid .gi .gv{font-size:13px;font-weight:600;color:#cdd8e6;}
|
||
|
||
/* 品质等级条 */
|
||
.qualityBar{display:flex;gap:6px;margin:12px 0;}
|
||
.qualityBar .qlv{
|
||
flex:1;height:4px;border-radius:2px;background:rgba(255,255,255,.08);
|
||
transition:.3s;
|
||
}
|
||
.qualityBar .qlv.on{background:linear-gradient(90deg,#9ca3af,#22c55e,#3b82f6,#a855f7,#f59e0b);}
|
||
.qualityBar .qlv.on+.qlv.on{background:linear-gradient(90deg,#22c55e,#3b82f6,#a855f7,#f59e0b);}
|
||
.qualityBar .qlv.on+.qlv.on+.qlv.on{background:linear-gradient(90deg,#3b82f6,#a855f7,#f59e0b);}
|
||
.qualityBar .qlv.on+.qlv.on+.qlv.on+.qlv.on{background:linear-gradient(90deg,#a855f7,#f59e0b);}
|
||
.qualityBar .qlv.on+.qlv.on+.qlv.on+.qlv.on+.qlv.on{background:#f59e0b;}
|
||
|
||
.md-recipes{margin-top:14px;display:flex;flex-direction:column;gap:8px;}
|
||
.md-recipe{
|
||
display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:10px;
|
||
background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);
|
||
}
|
||
.md-recipe .rp-icon{font-size:20px;}
|
||
.md-recipe .rp-name{font-size:13px;color:#cdd8e6;font-weight:600;}
|
||
.md-recipe .rp-num{font-size:12px;color:#8899aa;margin-left:auto;}
|
||
|
||
.md-chips{margin-top:14px;display:flex;flex-wrap:wrap;gap:8px;}
|
||
.md-chip{padding:6px 12px;border-radius:8px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);font-size:13px;color:#cdd8e6;}
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="app">
|
||
<header class="topbar">
|
||
<a href="index.html" class="logo"><span class="cn">宇森</span></a>
|
||
<nav class="nav">
|
||
<a href="announce.html">公告</a>
|
||
<a href="background.html">背景</a>
|
||
<a href="rules.html">规则</a>
|
||
<a href="codex.html">图鉴</a>
|
||
<a href="story.html">剧情</a>
|
||
<a href="forum.html" class="auth-only">论坛</a>
|
||
<a href="worlddata.html" class="auth-only">世界数据</a>
|
||
</nav>
|
||
<div class="spacer"></div>
|
||
<div class="user-box" id="userBox"></div>
|
||
</header>
|
||
|
||
<main class="page-content hide-scroll">
|
||
|
||
<!-- ===== 筛选区 ===== -->
|
||
<div class="filters" id="filters">
|
||
<div class="filterRow">
|
||
<span class="filterLabel">分类</span>
|
||
<span class="catTab active" data-cat="all" onclick="setCat('all')">🏛️ 全部</span>
|
||
<span class="catTab" data-cat="material" onclick="setCat('material')">🪵 材料</span>
|
||
<span class="catTab" data-cat="processed" onclick="setCat('processed')">🏭 加工品</span>
|
||
<span class="catTab" data-cat="creature" onclick="setCat('creature')">🐾 生物</span>
|
||
<span class="catTab" data-cat="monster" onclick="setCat('monster')">👹 野怪</span>
|
||
<span class="catTab" data-cat="race" onclick="setCat('race')">🌍 种族</span>
|
||
<span class="catTab" data-cat="special" onclick="setCat('special')">✨ 特殊</span>
|
||
<div class="searchBox">
|
||
<span class="icon">🔍</span>
|
||
<input id="searchInput" type="text" placeholder="搜索…" />
|
||
<span class="clear" id="searchClear" onclick="clearSearch()" title="清空" style="display:none;">✕</span>
|
||
</div>
|
||
</div>
|
||
<div class="filterRow">
|
||
<span class="filterLabel">品质</span>
|
||
<span class="ql active" data-ql="all" onclick="setQual('all')">
|
||
<span class="dot" style="background:linear-gradient(135deg,#9ca3af,#fbbf24);"></span>
|
||
<span class="lbl">全部</span>
|
||
</span>
|
||
<span class="ql" data-ql="common" onclick="setQual('common')">
|
||
<span class="dot" style="background:#9ca3af;"></span>
|
||
<span class="lbl">普通</span>
|
||
</span>
|
||
<span class="ql" data-ql="uncommon" onclick="setQual('uncommon')">
|
||
<span class="dot" style="background:#22c55e;"></span>
|
||
<span class="lbl">优秀</span>
|
||
</span>
|
||
<span class="ql" data-ql="rare" onclick="setQual('rare')">
|
||
<span class="dot" style="background:#3b82f6;"></span>
|
||
<span class="lbl">精良</span>
|
||
</span>
|
||
<span class="ql" data-ql="epic" onclick="setQual('epic')">
|
||
<span class="dot" style="background:#a855f7;"></span>
|
||
<span class="lbl">史诗</span>
|
||
</span>
|
||
<span class="ql" data-ql="legendary" onclick="setQual('legendary')">
|
||
<span class="dot" style="background:#f59e0b;"></span>
|
||
<span class="lbl">传说</span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== 卡片网格 ===== -->
|
||
<div class="grid" id="grid"></div>
|
||
|
||
<!-- ===== 详情弹窗 ===== -->
|
||
<div class="modalOverlay" id="modalOverlay" onclick="if(event.target===this)closeModal()">
|
||
<div class="modalBox" id="modalBox" style="position:relative;"></div>
|
||
</div>
|
||
|
||
<script>
|
||
// =====================================================================
|
||
// 宇森图鉴 — 全部数据来自后端 Odoo (yt_world 模块),前端无硬编码默认数据
|
||
// =====================================================================
|
||
|
||
const QUALITIES = ['common','uncommon','rare','epic','legendary'];
|
||
const QUALITY_NAMES = { common:'普通', uncommon:'优秀', rare:'精良', epic:'史诗', legendary:'传说' };
|
||
const TIER_NAMES = { normal:'普通野怪', elite:'精英野怪', boss:'Boss' };
|
||
const CAT_ORDER = ['material','processed','creature','monster','race','special'];
|
||
|
||
let ALL_ENTRIES = []; // 归一化后的全部条目(来自后端)
|
||
let currentCat = 'all';
|
||
let currentQual = 'all';
|
||
let searchTerm = '';
|
||
|
||
// ---------- 筛选交互 ----------
|
||
function setCat(cat){
|
||
currentCat = cat;
|
||
document.querySelectorAll('.catTab').forEach(el=>el.classList.toggle('active',el.dataset.cat===cat));
|
||
renderGrid();
|
||
}
|
||
function setQual(ql){
|
||
currentQual = ql;
|
||
document.querySelectorAll('.ql').forEach(el=>el.classList.toggle('active',el.dataset.ql===ql));
|
||
renderGrid();
|
||
}
|
||
function getQualIndex(q){ return QUALITIES.indexOf(q); }
|
||
|
||
// ---------- 搜索 ----------
|
||
function setSearch(v){
|
||
searchTerm = v;
|
||
const clr = document.getElementById('searchClear');
|
||
if(clr) clr.style.display = v ? 'inline' : 'none';
|
||
renderGrid();
|
||
}
|
||
function clearSearch(){
|
||
const inp = document.getElementById('searchInput');
|
||
if(inp) inp.value = '';
|
||
setSearch('');
|
||
}
|
||
document.addEventListener('DOMContentLoaded', ()=>{
|
||
const inp = document.getElementById('searchInput');
|
||
if(inp){
|
||
inp.addEventListener('input', e=> setSearch(e.target.value));
|
||
inp.addEventListener('keydown', e=>{ if(e.key==='Escape'){ clearSearch(); inp.blur(); } });
|
||
}
|
||
});
|
||
|
||
// ---------- 后端数据归一化 ----------
|
||
function normResource(r){
|
||
return {
|
||
type:'resource', id:r.code, cat:r.category, name:r.name, icon:r.icon||'📦',
|
||
qual:r.quality||'common', source:r.source||'', uses:r.uses||'',
|
||
recipe:Array.isArray(r.recipe)?r.recipe:[], desc:r.description||''
|
||
};
|
||
}
|
||
function normCreature(c){
|
||
return {
|
||
type:'creature', id:c.code, cat:'creature', name:c.name, icon:c.icon||'🐾',
|
||
hab:c.habitat||'', tame:!!c.tameable, diet:c.diet||'',
|
||
products:Array.isArray(c.products)?c.products:[], desc:c.description||''
|
||
};
|
||
}
|
||
function normMonster(m){
|
||
return {
|
||
type:'monster', id:m.code, cat:'monster', name:m.name, icon:m.icon||'👹',
|
||
tier:m.tier||'normal', levelMin:m.level_min||1, levelMax:m.level_max||1,
|
||
hab:m.habitat||'', drops:Array.isArray(m.drops)?m.drops:[], desc:m.description||''
|
||
};
|
||
}
|
||
function normRace(r){
|
||
return {
|
||
type:'race', id:r.code, cat:'race', name:r.name, icon:r.icon||'🧑',
|
||
layer:r.layer||'', traits:Array.isArray(r.traits)?r.traits:[], desc:r.description||''
|
||
};
|
||
}
|
||
function findEntry(code){ return ALL_ENTRIES.find(x=>x.id===code); }
|
||
|
||
// ---------- 加载(核心:从后端取数) ----------
|
||
async function loadCodexData(){
|
||
const grid = document.getElementById('grid');
|
||
grid.innerHTML = '<div class="empty show">正在从服务器加载图鉴数据…</div>';
|
||
try{
|
||
const [res, cre, mon, rac] = await Promise.all([
|
||
API.codexResources(), API.codexCreatures(), API.codexMonsters(), API.codexRaces()
|
||
]);
|
||
ALL_ENTRIES = [
|
||
...(res||[]).map(normResource),
|
||
...(cre||[]).map(normCreature),
|
||
...(mon||[]).map(normMonster),
|
||
...(rac||[]).map(normRace),
|
||
];
|
||
renderGrid();
|
||
}catch(err){
|
||
console.error('[codex] 加载失败:', err);
|
||
grid.innerHTML = '<div class="empty show">⚠️ 加载失败,请确认 Odoo 后端(8069)已启动且 yt_world 模块已更新。</div>';
|
||
}
|
||
}
|
||
|
||
// ---------- 渲染网格 ----------
|
||
function renderGrid(){
|
||
const q = searchTerm.trim().toLowerCase();
|
||
let filtered = ALL_ENTRIES.filter(e=>{
|
||
if(currentCat!=='all' && e.cat!==currentCat) return false;
|
||
if(q){
|
||
const hay = [e.name, e.desc, (e.products||[]).join(' '), (e.drops||[]).join(' '),
|
||
(e.traits||[]).join(' '), e.hab||'', e.layer||''].join(' ');
|
||
if(!hay.toLowerCase().includes(q)) return false;
|
||
}
|
||
if(currentQual!=='all'){
|
||
if(e.type!=='resource') return false;
|
||
if(e.qual!==currentQual) return false;
|
||
}
|
||
return true;
|
||
});
|
||
|
||
filtered.sort((a,b)=>{
|
||
const ac = CAT_ORDER.indexOf(a.cat), bc = CAT_ORDER.indexOf(b.cat);
|
||
return (ac===-1?99:ac)-(bc===-1?99:bc) || (a.id||'').localeCompare(b.id||'');
|
||
});
|
||
|
||
const grid = document.getElementById('grid');
|
||
grid.innerHTML = '';
|
||
|
||
if(!filtered.length){
|
||
grid.innerHTML = '<div class="empty show">没有匹配的条目</div>';
|
||
} else {
|
||
filtered.forEach(e=>{
|
||
const div = document.createElement('div');
|
||
div.className = 'card';
|
||
|
||
if(e.type==='resource'){
|
||
div.classList.add('q-'+e.qual);
|
||
div.innerHTML = `
|
||
<span class="qbadge ${e.qual}">${QUALITY_NAMES[e.qual]}</span>
|
||
<div class="icon">${e.icon}</div>
|
||
<div class="name">${e.name}</div>
|
||
`;
|
||
} else if(e.type==='creature'){
|
||
div.classList.add('creature');
|
||
div.innerHTML = `
|
||
<div class="icon">${e.icon}</div>
|
||
<div class="name">${e.name}</div>
|
||
<div class="tags">
|
||
<span class="tag">${e.hab||''}</span>
|
||
<span class="tag">${e.diet||''}</span>
|
||
<span class="tag">${e.tame?'可驯养':'野性'}</span>
|
||
</div>
|
||
`;
|
||
} else if(e.type==='monster'){
|
||
div.classList.add('monster','tier-'+e.tier);
|
||
div.innerHTML = `
|
||
<span class="tierbadge ${e.tier}">${TIER_NAMES[e.tier]}</span>
|
||
<div class="icon">${e.icon}</div>
|
||
<div class="name">${e.name}</div>
|
||
<div class="tags">
|
||
<span class="tag">Lv ${e.levelMin}-${e.levelMax}</span>
|
||
<span class="tag">${e.hab||''}</span>
|
||
</div>
|
||
`;
|
||
} else if(e.type==='race'){
|
||
div.classList.add('race');
|
||
div.innerHTML = `
|
||
<div class="icon">${e.icon}</div>
|
||
<div class="name">${e.name}</div>
|
||
<div class="tags">
|
||
<span class="tag">${e.layer||''}</span>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
div.addEventListener('click',()=>showDetail(e));
|
||
grid.appendChild(div);
|
||
});
|
||
}
|
||
|
||
updateStats(filtered);
|
||
}
|
||
|
||
function updateStats(){}
|
||
|
||
|
||
// =====================================================================
|
||
// 详 情 弹 窗
|
||
// =====================================================================
|
||
function closeModal(){
|
||
document.getElementById('modalOverlay').classList.remove('open');
|
||
}
|
||
|
||
function showDetail(e){
|
||
const overlay = document.getElementById('modalOverlay');
|
||
const box = document.getElementById('modalBox');
|
||
const qi = e.qual ? getQualIndex(e.qual) : -1;
|
||
|
||
let html = '<div style="position:relative;">';
|
||
html += `<button class="md-close" onclick="closeModal()" style="position:absolute;top:-8px;right:-8px;">✕</button>`;
|
||
|
||
// 头部
|
||
html += `<div class="md-header">`;
|
||
html += `<div class="md-icon">${e.icon}</div>`;
|
||
html += `<div class="md-info"><div class="md-title">${e.name}</div><div class="md-meta">`;
|
||
if(e.type==='resource'){
|
||
const c = e.qual==='common'?'156,163,175':e.qual==='uncommon'?'34,197,94':e.qual==='rare'?'59,130,246':e.qual==='epic'?'168,85,247':'245,158,11';
|
||
const t = e.qual==='common'?'#9ca3af':e.qual==='uncommon'?'#4ade80':e.qual==='rare'?'#60a5fa':e.qual==='epic'?'#c084fc':'#fbbf24';
|
||
html += `<span class="mt" style="background:rgba(${c},.15);color:${t}">${QUALITY_NAMES[e.qual]}</span>`;
|
||
html += `<span class="mt">${e.cat==='material'?'材料':e.cat==='processed'?'加工品':'特殊'}</span>`;
|
||
} else if(e.type==='creature'){
|
||
html += `<span class="mt">🐾 生物</span><span class="mt">${e.hab||''}</span><span class="mt">${e.diet||''}</span><span class="mt">${e.tame?'✅ 可驯养':'❌ 野性'}</span>`;
|
||
} else if(e.type==='monster'){
|
||
const tc = e.tier==='boss'?'245,158,11':e.tier==='elite'?'168,85,247':'156,163,175';
|
||
const tt = e.tier==='boss'?'#fbbf24':e.tier==='elite'?'#c084fc':'#9ca3af';
|
||
html += `<span class="mt" style="background:rgba(${tc},.18);color:${tt}">${TIER_NAMES[e.tier]}</span>`;
|
||
html += `<span class="mt">Lv ${e.levelMin}-${e.levelMax}</span><span class="mt">${e.hab||''}</span>`;
|
||
} else if(e.type==='race'){
|
||
html += `<span class="mt">🌍 种族</span><span class="mt">${e.layer||''}</span>`;
|
||
}
|
||
html += `</div></div></div>`;
|
||
|
||
// 品质等级条(资源)
|
||
if(e.type==='resource'){
|
||
html += `<div class="qualityBar">`;
|
||
QUALITIES.forEach((q,i)=>{ html += `<div class="qlv ${i<=qi?'on':''}" title="${QUALITY_NAMES[q]}"></div>`; });
|
||
html += `</div>`;
|
||
}
|
||
|
||
// 描述
|
||
if(e.desc){
|
||
html += `<div class="md-section"><h3>📖 描述</h3><div class="md-desc">${e.desc}</div></div>`;
|
||
}
|
||
|
||
// 资源详情
|
||
if(e.type==='resource'){
|
||
html += `<div class="md-section"><h3>📋 属性</h3><div class="md-grid">`;
|
||
html += `<div class="gi"><span class="gl">来源</span><span class="gv">${e.source||'未知'}</span></div>`;
|
||
html += `<div class="gi"><span class="gl">用途</span><span class="gv">${e.uses||'—'}</span></div>`;
|
||
html += `</div></div>`;
|
||
if(e.recipe && e.recipe.length){
|
||
html += `<div class="md-section"><h3>🏭 加工配方</h3><div class="md-recipes">`;
|
||
e.recipe.forEach(r=>{
|
||
const src = findEntry(r.code);
|
||
html += `<div class="md-recipe"><span class="rp-icon">${src?src.icon:'❓'}</span><span class="rp-name">${src?src.name:r.code}</span><span class="rp-num">×${r.num}</span></div>`;
|
||
});
|
||
html += `</div></div>`;
|
||
}
|
||
}
|
||
|
||
// 生物详情
|
||
if(e.type==='creature'){
|
||
html += `<div class="md-section"><h3>📋 属性</h3><div class="md-grid">`;
|
||
html += `<div class="gi"><span class="gl">栖息地</span><span class="gv">${e.hab||'未知'}</span></div>`;
|
||
html += `<div class="gi"><span class="gl">食性</span><span class="gv">${e.diet||'—'}</span></div>`;
|
||
html += `<div class="gi"><span class="gl">可驯养</span><span class="gv">${e.tame?'✅ 是':'❌ 否'}</span></div>`;
|
||
html += `</div></div>`;
|
||
if(e.products && e.products.length){
|
||
html += `<div class="md-section"><h3>🥚 产物</h3><div class="md-chips">` + e.products.map(p=>`<span class="md-chip">${p}</span>`).join('') + `</div></div>`;
|
||
}
|
||
}
|
||
|
||
// 野怪详情
|
||
if(e.type==='monster'){
|
||
html += `<div class="md-section"><h3>📋 属性</h3><div class="md-grid">`;
|
||
html += `<div class="gi"><span class="gl">等级</span><span class="gv">${TIER_NAMES[e.tier]}</span></div>`;
|
||
html += `<div class="gi"><span class="gl">等级范围</span><span class="gv">Lv ${e.levelMin}-${e.levelMax}</span></div>`;
|
||
html += `<div class="gi"><span class="gl">栖息地</span><span class="gv">${e.hab||'未知'}</span></div>`;
|
||
html += `</div></div>`;
|
||
if(e.drops && e.drops.length){
|
||
html += `<div class="md-section"><h3>💎 掉落</h3><div class="md-chips">` + e.drops.map(d=>`<span class="md-chip">${d}</span>`).join('') + `</div></div>`;
|
||
}
|
||
}
|
||
|
||
// 种族详情
|
||
if(e.type==='race'){
|
||
html += `<div class="md-section"><h3>📋 属性</h3><div class="md-grid">`;
|
||
html += `<div class="gi"><span class="gl">所属层域</span><span class="gv">${e.layer||'未知'}</span></div>`;
|
||
html += `</div></div>`;
|
||
if(e.traits && e.traits.length){
|
||
html += `<div class="md-section"><h3>✨ 天赋特性</h3><div class="md-chips">` + e.traits.map(t=>`<span class="md-chip">${t}</span>`).join('') + `</div></div>`;
|
||
}
|
||
}
|
||
|
||
html += '</div>';
|
||
box.innerHTML = html;
|
||
overlay.classList.add('open');
|
||
}
|
||
|
||
// ===== 键盘关闭 =====
|
||
document.addEventListener('keydown',e=>{ if(e.key==='Escape') closeModal(); });
|
||
|
||
// ===== 初始化:从后端拉取图鉴 =====
|
||
document.addEventListener('DOMContentLoaded', loadCodexData);
|
||
</script>
|
||
<script src="assets/common.js"></script>
|
||
</main>
|
||
</div>
|
||
</body>
|
||
</html>
|