前端: - world.html 游戏内聊天组件重构:表情选择器改固定方格+自适应列+去滚动条; 输入框清空占位文字+隐藏滚动条;聊天面板整体高度缩短约 1/3; 顶部用户区改为纯文字并点开个人信息弹窗(头像/头像框/性别/说明/称号/注册) - chat.html 聊天输入框压扁(46→34px) - codex.html 新增神话品质、天赋/技能书/书籍分类与预置数据 - rules.html 规则文档更新 后端(Odoo): - game_base 附件改为看板视图(图片/名称/备注/大小) - yt_world 玩家菜单直接指向 hr.employee - game_base 员工表单隐藏原生联系人/组织架构页签 - chat 模型/接口/视图与规则数据扩充 资源:生成 4x4 资源精灵表(树木/石材/芦苇/宝箱)
783 lines
37 KiB
HTML
783 lines
37 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;}
|
||
|
||
/* ===== 分类页签(与规则页统一风格) ===== */
|
||
.rule-tabs{
|
||
display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-start;
|
||
padding:6px 0 8px;
|
||
}
|
||
.rule-tab{
|
||
padding:8px 20px;border-radius:999px;font-size:15px;cursor:pointer;user-select:none;
|
||
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);color:#9fb3cc;transition:.18s;
|
||
font-family:"ZCOOL QingKe HuangYou",sans-serif;letter-spacing:1px;
|
||
}
|
||
.rule-tab:hover{background:rgba(139,92,246,.14);color:#d0dfff;border-color:rgba(139,92,246,.3);}
|
||
.rule-tab.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,.25);}
|
||
|
||
/* 品质筛点 */
|
||
.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-mythic::before{background:linear-gradient(90deg,#dc2626,#f59e0b);}
|
||
/* 品质光晕 */
|
||
.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.q-mythic:hover{box-shadow:0 8px 24px rgba(0,0,0,.4),0 0 24px rgba(220,38,38,.18),0 0 12px rgba(245,158,11,.1);}
|
||
|
||
/* 品质角标 */
|
||
.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);}
|
||
.qbadge.mythic{background:linear-gradient(135deg,rgba(220,38,38,.18),rgba(245,158,11,.18));color:#f87171;border:1px solid rgba(220,38,38,.35);text-shadow:0 0 6px rgba(245,158,11,.4);}
|
||
|
||
/* 生物/野怪/种族 专属样式 */
|
||
.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="rule-tabs" id="catTabs">
|
||
<span class="rule-tab active" data-cat="all" onclick="setCat('all')">🏛️ 全部</span>
|
||
<span class="rule-tab" data-cat="material" onclick="setCat('material')">🪵 材料</span>
|
||
<span class="rule-tab" data-cat="processed" onclick="setCat('processed')">🏭 加工品</span>
|
||
<span class="rule-tab" data-cat="creature" onclick="setCat('creature')">🐾 生物</span>
|
||
<span class="rule-tab" data-cat="monster" onclick="setCat('monster')">👹 野怪</span>
|
||
<span class="rule-tab" data-cat="race" onclick="setCat('race')">🌍 种族</span>
|
||
<span class="rule-tab" data-cat="talent" onclick="setCat('talent')">🌟 天赋</span>
|
||
<span class="rule-tab" data-cat="skill_book" onclick="setCat('skill_book')">📖 技能书</span>
|
||
<span class="rule-tab" data-cat="book" onclick="setCat('book')">📚 书籍</span>
|
||
<span class="rule-tab" data-cat="special" onclick="setCat('special')">✨ 特殊</span>
|
||
</div>
|
||
<div class="filterRow">
|
||
<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>
|
||
<span class="ql" data-ql="mythic" onclick="setQual('mythic')">
|
||
<span class="dot" style="background:linear-gradient(135deg,#dc2626,#f59e0b);"></span>
|
||
<span class="lbl">神话</span>
|
||
</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>
|
||
|
||
<!-- ===== 卡片网格 ===== -->
|
||
<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','mythic'];
|
||
const QUALITY_NAMES = { common:'普通', uncommon:'优秀', rare:'精良', epic:'史诗', legendary:'传说', mythic:'神话' };
|
||
const TIER_NAMES = { normal:'普通野怪', elite:'精英野怪', boss:'Boss' };
|
||
const CAT_ORDER = ['material','processed','creature','monster','race','talent','skill_book','book','special'];
|
||
|
||
// ===== 天赋预置数据(前端硬编码,后端就绪后迁移) =====
|
||
const TALENT_DATA = [
|
||
{ code:'talent_ancient_blood', name:'先民血脉', icon:'🩸', quality:'mythic',
|
||
trigger:'被动', effect:'生命回复+20%;濒死时触发先民护盾,5秒内免伤',
|
||
desc:'流淌着远古先民的血脉,身体拥有超乎寻常的自愈与守护之力。传说中先民以此血脉抵御了灭世之灾。' },
|
||
{ code:'talent_dragon_soul', name:'龙裔之魂', icon:'🐉', quality:'mythic',
|
||
trigger:'被动', effect:'火焰抗性+50%;攻击附带灼烧效果',
|
||
desc:'体内沉睡着巨龙的灵魂碎片,对火焰有着天然的亲和与免疫,攻击时能唤出龙息灼烧敌人。' },
|
||
{ code:'talent_star_resonance', name:'星辰共鸣', icon:'✨', quality:'legendary',
|
||
trigger:'被动', effect:'法力回复+15%;夜间额外+10%',
|
||
desc:'与星辰之力产生共鸣,能够汲取星光转化为法力。夜幕降临时,这份共鸣尤为强烈。' },
|
||
{ code:'talent_beast_king', name:'兽王本能', icon:'🐾', quality:'epic',
|
||
trigger:'被动', effect:'驯养生物好感度+30%;生物产出+20%',
|
||
desc:'天生与野兽亲和,能感知它们的情绪与需求。驯养的生物更加忠诚,产出也更加丰厚。' },
|
||
{ code:'talent_smith_hand', name:'铁匠之手', icon:'🔨', quality:'rare',
|
||
trigger:'被动', effect:'加工品品质+1阶;加工成功率+10%',
|
||
desc:'双手有着与生俱来的锻造天赋,加工材料时更加精准,成品的品质也往往超出预期。' },
|
||
{ code:'talent_forest_walker', name:'林间行者', icon:'🌿', quality:'rare',
|
||
trigger:'被动', effect:'森林地形移速+25%;隐蔽+15%',
|
||
desc:'在密林中如鱼得水,脚步轻盈无声,仿佛与森林融为一体。敌人很难在林间发现你的踪迹。' },
|
||
{ code:'talent_ore_sense', name:'矿脉感应', icon:'⛏️', quality:'uncommon',
|
||
trigger:'被动', effect:'发现稀有矿脉概率+20%',
|
||
desc:'对地底的矿脉有着敏锐的直觉,总能在别人忽略的地方发现珍贵的矿石。' },
|
||
{ code:'talent_harvest_blessing', name:'丰收祝福', icon:'🌾', quality:'uncommon',
|
||
trigger:'被动', effect:'农作物产量+15%;生长速度+10%',
|
||
desc:'受到大地之灵的祝福,经手种植的作物总是长势喜人,收获时也比旁人更加丰盈。' },
|
||
];
|
||
|
||
// ===== 技能书预置数据(前端硬编码,后端就绪后迁移) =====
|
||
const SKILL_BOOK_DATA = [
|
||
{ code:'sb_inferno_v1', name:'烈焰术·卷一', icon:'🔥', quality:'rare',
|
||
skillType:'主动', learnLevel:5, cost:'法力 20',
|
||
effect:'释放火焰法术,对前方3格内敌人造成灼烧伤害,持续4秒',
|
||
desc:'记载着基础火焰法术的入门卷轴。虽然只是卷一,但修习者能感受到火焰之力的澎湃。' },
|
||
{ code:'sb_frost_domain_frag', name:'冰霜领域·残卷', icon:'❄️', quality:'epic',
|
||
skillType:'主动', learnLevel:15, cost:'法力 50',
|
||
effect:'冻结周围3格内所有敌人,持续3秒,破冰时受到额外伤害',
|
||
desc:'残缺的古老卷轴,记载着冰霜领域的部分奥义。虽不完整,但冰封万物的威力已然惊人。' },
|
||
{ code:'sb_thunder_judgment_full', name:'雷霆审判·全本', icon:'⚡', quality:'mythic',
|
||
skillType:'主动', learnLevel:30, cost:'法力 80',
|
||
effect:'召唤天雷轰击目标区域,无视火焰/冰霜抗性,造成毁灭性雷击伤害',
|
||
desc:'完整的雷霆审判秘典。传说修习至大成者可引天雷降世,审判一切罪恶。非大毅力者不可修习。' },
|
||
{ code:'sb_ironwall_secret', name:'铁壁守护·秘典', icon:'🛡️', quality:'rare',
|
||
skillType:'被动', learnLevel:8, cost:'—',
|
||
effect:'物理减伤+15%;格挡率+10%',
|
||
desc:'铁壁一脉代代相传的守护秘典。修习者身如铁壁,能为同伴抵挡致命一击。' },
|
||
{ code:'sb_gale_step_frag', name:'疾风步·残页', icon:'💨', quality:'uncommon',
|
||
skillType:'主动', learnLevel:3, cost:'法力 10',
|
||
effect:'短暂提升移速40%,持续5秒,冷却15秒',
|
||
desc:'仅存数页的残破秘籍,记载着疾风步的基本步法。虽不完整,但足以让修习者步履如风。' },
|
||
{ code:'sb_life_drain_forbidden', name:'生命汲取·禁典', icon:'💀', quality:'legendary',
|
||
skillType:'被动', learnLevel:20, cost:'—',
|
||
effect:'攻击时回复造成伤害10%的生命;击杀时额外回复15%最大生命',
|
||
desc:'被列为禁术的黑暗典籍。修习者以杀养身,越战越强,但也容易迷失在嗜血的渴望中。慎之。' },
|
||
];
|
||
|
||
// ===== 书籍预置数据(前端硬编码,后端就绪后迁移) =====
|
||
const BOOK_DATA = [
|
||
{ code:'book_genesis', name:'宇森创世纪', icon:'📖', bookType:'历史',
|
||
author:'佚名·先民', chapters:12,
|
||
desc:'记载世界诞生的远古文献。从混沌初开到万物生长,从先民觉醒到百族兴起,宇森世界的起源尽在此书中。' },
|
||
{ code:'book_hundred_wars', name:'百族大战纪略', icon:'📜', bookType:'历史',
|
||
author:'史官·凌岳', chapters:8,
|
||
desc:'记录各族群之间那场旷日持久的远古战争。战争的起因、过程与结局,以及那些被遗忘的英雄与罪人。' },
|
||
{ code:'book_star_child', name:'星辰之子传说', icon:'✨', bookType:'传说',
|
||
author:'游吟诗人·散人', chapters:6,
|
||
desc:'流传于民间的英雄史诗。讲述一个诞生于星光之下的孩子,如何走出荒野、拯救苍生的故事。真伪已不可考。' },
|
||
{ code:'book_vein_walk', name:'地脉行走指南', icon:'🗺️', bookType:'秘闻',
|
||
author:'探险家·石锤', chapters:15,
|
||
desc:'记录地底矿脉走向与秘境入口的探险手册。地脉行走者的必备指南,据说按图索骥可寻得珍稀矿藏。' },
|
||
{ code:'book_codex_vol1', name:'万物图鉴·初卷', icon:'📔', bookType:'图鉴',
|
||
author:'图鉴师·青木', chapters:20,
|
||
desc:'汇编已知生物与资源的初步图鉴。虽只是初卷,却已涵盖数百种生灵与材料,是图鉴师的入门必读。' },
|
||
{ code:'book_ancient_remnant', name:'先民遗书·残篇', icon:'🔏', bookType:'秘闻',
|
||
author:'先民·佚名', chapters:3,
|
||
desc:'先民留下的残缺手稿,字迹斑驳难辨。隐约可见关于古老力量与禁忌之地的记载,暗藏着不为人知的秘密。' },
|
||
];
|
||
|
||
function normTalent(t){
|
||
return {
|
||
type:'talent', id:t.code, cat:'talent', name:t.name, icon:t.icon||'🌟',
|
||
qual:t.quality||'rare', trigger:t.trigger||'被动', effect:t.effect||'',
|
||
desc:t.description||t.desc||''
|
||
};
|
||
}
|
||
function normSkillBook(s){
|
||
return {
|
||
type:'skill_book', id:s.code, cat:'skill_book', name:s.name, icon:s.icon||'📖',
|
||
qual:s.quality||'common', skillType:s.skillType||'主动', learnLevel:s.learnLevel||1,
|
||
cost:s.cost||'—', effect:s.effect||'', desc:s.description||s.desc||''
|
||
};
|
||
}
|
||
function normBook(b){
|
||
return {
|
||
type:'book', id:b.code, cat:'book', name:b.name, icon:b.icon||'📚',
|
||
bookType:b.bookType||'其他', author:b.author||'佚名', chapters:b.chapters||0,
|
||
desc:b.description||b.desc||''
|
||
};
|
||
}
|
||
|
||
let ALL_ENTRIES = []; // 归一化后的全部条目(来自后端)
|
||
let currentCat = 'all';
|
||
let currentQual = 'all';
|
||
let searchTerm = '';
|
||
|
||
// ---------- 筛选交互 ----------
|
||
function setCat(cat){
|
||
currentCat = cat;
|
||
document.querySelectorAll('.rule-tab').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),
|
||
...TALENT_DATA.map(normTalent),
|
||
...SKILL_BOOK_DATA.map(normSkillBook),
|
||
...BOOK_DATA.map(normBook),
|
||
];
|
||
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.effect||'', e.trigger||'', e.skillType||'',
|
||
e.bookType||'', e.author||'', e.cost||'',
|
||
(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' && e.type!=='talent' && e.type!=='skill_book') 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>
|
||
`;
|
||
} else if(e.type==='talent'){
|
||
div.classList.add('q-'+e.qual, 'talent');
|
||
div.innerHTML = `
|
||
<span class="qbadge ${e.qual}">${QUALITY_NAMES[e.qual]}</span>
|
||
<div class="icon">${e.icon}</div>
|
||
<div class="name">${e.name}</div>
|
||
<div class="tags">
|
||
<span class="tag">${e.trigger||'被动'}</span>
|
||
</div>
|
||
`;
|
||
} else if(e.type==='skill_book'){
|
||
div.classList.add('q-'+e.qual, 'skill_book');
|
||
div.innerHTML = `
|
||
<span class="qbadge ${e.qual}">${QUALITY_NAMES[e.qual]}</span>
|
||
<div class="icon">${e.icon}</div>
|
||
<div class="name">${e.name}</div>
|
||
<div class="tags">
|
||
<span class="tag">${e.skillType||'主动'}</span>
|
||
<span class="tag">Lv ${e.learnLevel||1}</span>
|
||
</div>
|
||
`;
|
||
} else if(e.type==='book'){
|
||
div.classList.add('book');
|
||
div.innerHTML = `
|
||
<div class="icon">${e.icon}</div>
|
||
<div class="name">${e.name}</div>
|
||
<div class="tags">
|
||
<span class="tag">${e.bookType||'其他'}</span>
|
||
<span class="tag">${(e.chapters||0)}章</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' || e.type==='talent' || e.type==='skill_book'){
|
||
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':e.qual==='legendary'?'245,158,11':'220,38,38';
|
||
const t = e.qual==='common'?'#9ca3af':e.qual==='uncommon'?'#4ade80':e.qual==='rare'?'#60a5fa':e.qual==='epic'?'#c084fc':e.qual==='legendary'?'#fbbf24':'#f87171';
|
||
html += `<span class="mt" style="background:rgba(${c},.15);color:${t}">${QUALITY_NAMES[e.qual]}</span>`;
|
||
const catLabel = e.type==='talent'?'🌟 天赋':e.type==='skill_book'?'📖 技能书':e.cat==='material'?'材料':e.cat==='processed'?'加工品':'特殊';
|
||
html += `<span class="mt">${catLabel}</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' || e.type==='talent' || e.type==='skill_book'){
|
||
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>`;
|
||
}
|
||
}
|
||
|
||
// 天赋详情
|
||
if(e.type==='talent'){
|
||
html += `<div class="md-section"><h3>📋 属性</h3><div class="md-grid">`;
|
||
html += `<div class="gi"><span class="gl">触发方式</span><span class="gv">${e.trigger||'被动'}</span></div>`;
|
||
html += `<div class="gi"><span class="gl">品质</span><span class="gv">${QUALITY_NAMES[e.qual]||'—'}</span></div>`;
|
||
html += `</div></div>`;
|
||
if(e.effect){
|
||
html += `<div class="md-section"><h3>⚡ 效果</h3><div class="md-desc" style="color:#f0e6d3;">${e.effect}</div></div>`;
|
||
}
|
||
}
|
||
|
||
// 技能书详情
|
||
if(e.type==='skill_book'){
|
||
html += `<div class="md-section"><h3>📋 属性</h3><div class="md-grid">`;
|
||
html += `<div class="gi"><span class="gl">技能类型</span><span class="gv">${e.skillType||'主动'}</span></div>`;
|
||
html += `<div class="gi"><span class="gl">学习等级</span><span class="gv">Lv ${e.learnLevel||1}</span></div>`;
|
||
html += `<div class="gi"><span class="gl">消耗</span><span class="gv">${e.cost||'—'}</span></div>`;
|
||
html += `<div class="gi"><span class="gl">品质</span><span class="gv">${QUALITY_NAMES[e.qual]||'—'}</span></div>`;
|
||
html += `</div></div>`;
|
||
if(e.effect){
|
||
html += `<div class="md-section"><h3>⚡ 技能效果</h3><div class="md-desc" style="color:#f0e6d3;">${e.effect}</div></div>`;
|
||
}
|
||
}
|
||
|
||
// 书籍详情
|
||
if(e.type==='book'){
|
||
html += `<div class="md-section"><h3>📋 属性</h3><div class="md-grid">`;
|
||
html += `<div class="gi"><span class="gl">类型</span><span class="gv">${e.bookType||'其他'}</span></div>`;
|
||
html += `<div class="gi"><span class="gl">作者</span><span class="gv">${e.author||'佚名'}</span></div>`;
|
||
html += `<div class="gi"><span class="gl">章节数</span><span class="gv">${e.chapters||0} 章</span></div>`;
|
||
html += `</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>
|