Files
game/rules.html
李鹏宇 73d764938d feat: 前端 UI 精修 + 后端视图调整 + 资源预置
前端:
- world.html 游戏内聊天组件重构:表情选择器改固定方格+自适应列+去滚动条;
  输入框清空占位文字+隐藏滚动条;聊天面板整体高度缩短约 1/3;
  顶部用户区改为纯文字并点开个人信息弹窗(头像/头像框/性别/说明/称号/注册)
- chat.html 聊天输入框压扁(46→34px)
- codex.html 新增神话品质、天赋/技能书/书籍分类与预置数据
- rules.html 规则文档更新

后端(Odoo):
- game_base 附件改为看板视图(图片/名称/备注/大小)
- yt_world 玩家菜单直接指向 hr.employee
- game_base 员工表单隐藏原生联系人/组织架构页签
- chat 模型/接口/视图与规则数据扩充

资源:生成 4x4 资源精灵表(树木/石材/芦苇/宝箱)
2026-08-14 14:27:15 +08:00

235 lines
15 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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" />
<style>
/* ===== 页签 ===== */
.rule-tabs{
display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-start;
max-width:1080px;width:100%;margin:24px auto 0;padding:0 24px;
}
.rule-tab{
padding:8px 22px;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);}
.rules-scroll{max-width:1080px;width:100%;margin:0 auto;display:flex;flex-direction:column;gap:34px;padding:26px 48px 40px;}
.rule-panel{display:none;}
.rule-panel.active{display:block;}
.rule-block{margin-top:6px;}
.rule-intro{font-size:17px;color:var(--text-dim);margin-bottom:18px;line-height:1.95;max-width:880px;}
.rule-intro b{color:#fff;}
.tier-list{display:flex;flex-direction:column;gap:10px;}
.tier-row{display:grid;grid-template-columns:104px 124px 1fr;gap:16px;align-items:center;background:var(--card);border:1px solid var(--card-bd);border-radius:12px;padding:14px 18px;transition:.2s;}
.tier-row:hover{border-color:var(--violet);background:rgba(139,92,246,.10);box-shadow:var(--glow);}
.tier-row .tn{font-family:"Press Start 2P",monospace;font-size:12px;color:var(--cyan);}
.tier-row .tname{font-size:18px;color:#fff;font-weight:600;}
.tier-row .tdesc{font-size:16.5px;color:var(--text-dim);line-height:1.7;}
/* ===== 属性卡片网格 ===== */
.attr-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(248px,1fr));gap:14px;margin-top:4px;}
.attr-sub{margin:22px 0 10px;}
.attr-sub:first-of-type{margin-top:14px;}
.attr-sub-label{display:inline-block;font-family:"ZCOOL QingKe HuangYou",sans-serif;font-size:21px;color:#fff;letter-spacing:2px;padding-bottom:6px;border-bottom:2px solid rgba(139,92,246,.45);text-shadow:0 2px 12px rgba(139,92,246,.3);}
.attr-card{background:var(--card);border:1px solid var(--card-bd);border-radius:14px;padding:16px 18px;transition:.2s;}
.attr-card:hover{border-color:var(--violet);background:rgba(139,92,246,.08);box-shadow:var(--glow);transform:translateY(-2px);}
.attr-card .ah{display:flex;align-items:center;gap:11px;margin-bottom:9px;}
.attr-card .ai{font-size:28px;line-height:1;}
.attr-card .an{font-size:18px;font-weight:700;color:#fff;}
.attr-card .as{font-size:11px;color:var(--cyan);margin-bottom:7px;font-family:"Press Start 2P",monospace;letter-spacing:.5px;}
.attr-card .ad{font-size:14.5px;color:var(--text-dim);line-height:1.72;}
@media (max-width:768px){
.rules-scroll{padding:20px 18px;}
.tier-row{grid-template-columns:72px 92px 1fr;gap:8px;}
.attr-grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));}
}
@media (max-width:480px){
.tier-row{grid-template-columns:60px 76px 1fr;gap:6px;}
}
</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" class="active">规则</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="rule-tabs" id="ruleTabs"></div>
<div class="rules-scroll" id="rulePanels"><!-- JS 渲染:优先后端 API.rules(),兜底硬编码 --></div>
</main>
</div>
<script src="assets/game-api.js"></script>
<script src="assets/common.js"></script>
<script>
// 属性名 -> emoji(前端映射,后台只存 name/desc/summary)
const ATTR_ICON = {
'生命值':'❤️','法力值':'🔷','物攻':'⚔️','法攻':'🔥','防御':'🛡️','物防':'🪖','法防':'🔮',
'速度':'💨','攻速':'⚡','暴击':'💥','爆伤':'💢','命中':'🎯','闪避':'🌫️',
'格挡':'🛡','抗性':'🌟','吸血':'🩸','幸运':'🍀'
};
// 类别 key -> 页签名
const TAB_LABEL = { world:'世界等级', character:'角色等级', currency:'货币等级', attribute_basic:'基础属性', attribute_advanced:'高级属性' };
const SYS_HTML = `<div class="rule-block">
<p class="rule-intro">三者相互独立又彼此加成:<b style="color:#fff">世界等级</b>是舞台上限,决定能发生什么;<b style="color:#fff">势力等级</b>是群体进度,决定能调动什么;<b style="color:#fff">角色等级</b>是个体成长,决定能做成什么。园丁的核心策略,是在三者间分配有限的引导资源,让文明自洽地向上演化。<br><br>跃迁逻辑:角色历练与装备积累→提升势力实力→推动世界阶段跃迁→解锁更深内容(更深的地下层、更远的星空层、更强的神器)。每一级提升都会扩大可叙事的事件池,世界因此越活越厚。</p></div>`;
// 硬编码兜底(与后端 rule_data.xml 对齐;后端不可达时使用)
const FALLBACK = {
groups: [
{ key:'world', label:'世界等级', items:[
{level:1,name:'鸿荒',desc:'万物生灵尚处蒙昧凡土,天地灵气稀薄,各族散居荒野,无族群联盟与政权,只求狩猎采集求生,一切秩序遵循自然法则。'},
{level:2,name:'启明',desc:'天地灵气复苏觉醒,各族聚拢形成聚落,诞生劳作分工、屋舍营建与物物交换,不同族群率先凝聚部族,世间初代势力自此诞生。'},
{level:3,name:'鼎世',desc:'各族部落兼并融合,广袤大地形成多元族群共治的完整疆域,征伐、道统、造物三大发展路线并行;各族大能、上古秘闻等传奇事件不断现世,鼎世文明迎来鼎盛割据时代。'},
{level:4,name:'寰界',desc:'生灵突破地表桎梏,打通地底深渊与浩瀚星空,天地上下各界尽数纳入族群管辖;各类上古神器、本命法器完整解锁,文明疆域遍布天地穹苍。'},
{level:5,name:'归元',desc:'各族顶尖文明勘破这片天地的诞生本源,挣脱本世界规则束缚,可踏上飞升之路、往复轮回,前往更高维度开启全新文明演化。'},
]},
{ key:'character', label:'人物角色等级', items:[
{level:1,name:'赤子',desc:'初入世界的无名者。'},{level:2,name:'稚民',desc:'掌握基础技艺。'},
{level:3,name:'劳者',desc:'可独立完成任务。'},{level:4,name:'巧匠',desc:'在某领域崭露头角。'},
{level:5,name:'贤能',desc:'群体中的中坚。'},{level:6,name:'巨擘',desc:'技艺臻于化境。'},
{level:7,name:'宗师',desc:'开宗立派的人物。'},{level:8,name:'传奇',desc:'事迹被载入编年史。'},
{level:9,name:'神话',desc:'近乎传说的存在。'},{level:10,name:'半神',desc:'触及世界法则。'},
{level:11,name:'圣灵',desc:'被信仰托举的意志。'},{level:12,name:'创世',desc:'超越生死的世界锚点。'},
]},
{ key:'currency', label:'货币等级', items:[
{level:1,name:'铜币',desc:'凡俗通用零钱,以贝壳、骨片、碎石铸成。100 铜币 = 1 银币。'},
{level:2,name:'银币',desc:'蕴含微量灵气的金属铸币,聚落以上势力日常使用。100 银币 = 1 金币。'},
{level:3,name:'金币',desc:'刻有鼎纹的稀有锭块,用于高价值交易与功法图纸。100 金币 = 1 紫金。'},
{level:4,name:'紫金',desc:'产自地底深渊的幽暗玉石,蕴含浓郁能量。100 紫金 = 1 星辉。'},
{level:5,name:'星辉',desc:'来自星空矿脉的稀有鎏金晶体,跨层贸易硬通货。100 星辉 = 1 宇玺。'},
{level:6,name:'宇玺',desc:'追溯世界本源凝聚而成的至高货币,可兑换神器、传说事件与维度权限。'},
]},
{ key:'attribute_basic', label:'基础属性', items:[
{name:'生命值',summary:'基础值随等级成长,装备/天赋加成',desc:'单位存活的血量上限。受到伤害时优先扣除,归零则该单位倒下。随等级、装备与回复技能提升。'},
{name:'法力值',summary:'技能消耗资源,脱战回复',desc:'释放技能与法术的消耗资源。普攻不耗蓝,技能按强度扣蓝;蓝量不足无法施法,脱战自然回复。'},
{name:'物攻',summary:'物理伤害基数',desc:'物理输出能力,决定普通攻击与物理系技能的基础伤害。越高打出越疼。'},
{name:'法攻',summary:'法术伤害基数',desc:'法术输出能力,决定法术技能的基础伤害。与物攻相互独立,受法强装备加成。'},
{name:'防御',summary:'物理减伤(通用)',desc:'通用物理减伤能力,是对物理伤害的笼统防护,多数装备与体质提供。与「物防」同源,物防为其专精项。'},
{name:'物防',summary:'物理伤害减免',desc:'针对物理伤害的专项减免,由护甲、盾牌等装备提供。面对物理系敌人时主要看此项。'},
{name:'法防',summary:'法术伤害减免',desc:'针对法术伤害的专项减免,由法抗装备、抗性符文提供。面对法师 / 术士时主要看此项。'},
{name:'速度',summary:'先攻 / 行动顺序',desc:'行动先后顺序与出手频率。速度高者先手,回合制中更频繁行动;也影响部分先攻技能触发。'},
{name:'攻速',summary:'攻击间隔',desc:'两次攻击的间隔,越短出手越快、单位时间输出越高。影响普攻与部分技能频率。'},
]},
{ key:'attribute_advanced', label:'高级属性', items:[
{name:'暴击',summary:'暴击率 %',desc:'造成暴击的概率(百分比)。暴击时伤害按「爆伤」倍率提升。'},
{name:'爆伤',summary:'暴击倍率 %',desc:'暴击时的伤害倍率(默认 150%)。暴击率越高,堆此项收益越大。'},
{name:'命中',summary:'命中率 %',desc:'攻击命中的概率。面对高闪避目标需足够命中,否则出现「未命中」。'},
{name:'闪避',summary:'闪避率 %',desc:'躲避攻击的概率。高闪避可让对手攻击落空,但受对方命中压制。'},
{name:'格挡',summary:'格挡率 %',desc:'受击时触发格挡、按比例减免伤害的概率(需持盾 / 特定姿态)。格挡成功大幅减伤。'},
{name:'抗性',summary:'异常抵抗 %',desc:'对灼烧、冰冻、中毒等异常状态的抵抗概率。高抗性降低被控与持续掉血风险。'},
{name:'吸血',summary:'吸血 %',desc:'造成伤害时按百分比回复自身生命。持续作战与单挑续航的关键属性。'},
{name:'幸运',summary:'幸运值(影响暴击/掉落/稀有事件)',desc:'影响暴击、掉落与稀有事件触发概率的隐性维度。高幸运带来更多意外之喜与关键转折,是冒险与博弈中的变数之源。'},
]},
]
};
function stripHtml(s){ return (s||'').replace(/<[^>]+>/g,''); }
function renderTierPanel(g, active){
const rows = (g.items||[]).map(it=>{
const lvl = (it.level != null && it.level>0) ? ('Lv.'+it.level) : (g.key==='world' ? '阶段' : '');
return `<div class="tier-row"><span class="tn">${lvl}</span><span class="tname">${it.name}</span><span class="tdesc">${stripHtml(it.desc)}</span></div>`;
}).join('');
return `<div class="rule-panel${active}" data-panel="${g.key}">
<div class="rule-block">
<div class="tier-list">${rows}</div></div></div>`;
}
function renderAttrCard(it){
const icon = ATTR_ICON[it.name] || '📊';
const sum = stripHtml(it.summary);
return `<div class="attr-card"><div class="ah"><span class="ai">${icon}</span><span class="an">${it.name}</span></div>`
+ (sum?`<div class="as">${sum}</div>`:'')
+ `<div class="ad">${stripHtml(it.desc)}</div></div>`;
}
function renderAttrPanel(attrGroups, active){
const basic = (attrGroups.find(g=>g.key==='attribute_basic')||{}).items || [];
const adv = (attrGroups.find(g=>g.key==='attribute_advanced')||{}).items || [];
let html = `<div class="rule-panel${active}" data-panel="attribute"><div class="rule-block">`
+ `<p class="rule-intro">战斗与成长的核心数值维度。物攻 / 法攻决定输出,防御类(防御 / 物防 / 法防)按伤害类型减伤,辅以速度、暴击、爆伤、命中闪避等衍生维度,共同构成单位的战力模型。</p>`;
if (basic.length){
html += `<div class="attr-sub"><span class="attr-sub-label">基础属性</span></div><div class="attr-grid">`
+ basic.map(renderAttrCard).join('') + `</div>`;
}
if (adv.length){
html += `<div class="attr-sub"><span class="attr-sub-label">高级属性</span></div><div class="attr-grid">`
+ adv.map(renderAttrCard).join('') + `</div>`;
}
html += `</div></div>`;
return html;
}
function renderGroups(groups){
let tabs = '', panels = '';
const attrGroups = groups.filter(g => g.key==='attribute_basic' || g.key==='attribute_advanced');
const otherGroups = groups.filter(g => g.key!=='attribute_basic' && g.key!=='attribute_advanced');
let first = true;
otherGroups.forEach(g=>{
const label = TAB_LABEL[g.key] || g.label || g.key;
const active = first ? ' active' : '';
tabs += `<span class="rule-tab${active}" data-key="${g.key}" onclick="switchRuleTab('${g.key}')">${label}</span>`;
panels += renderTierPanel(g, active);
first = false;
});
// 属性规则:单页签内分区(基础 / 高级)
if (attrGroups.length){
const active = first ? ' active' : '';
tabs += `<span class="rule-tab${active}" data-key="attribute" onclick="switchRuleTab('attribute')">属性规则</span>`;
panels += renderAttrPanel(attrGroups, active);
first = false;
}
// 制度说明(静态 tab)
tabs += `<span class="rule-tab" data-key="__sys" onclick="switchRuleTab('__sys')">制度说明</span>`;
panels += `<div class="rule-panel" data-panel="__sys">${SYS_HTML}</div>`;
document.getElementById('ruleTabs').innerHTML = tabs;
document.getElementById('rulePanels').innerHTML = panels;
}
function switchRuleTab(key){
document.querySelectorAll('.rule-tab').forEach(el=>el.classList.toggle('active', el.dataset.key===key));
document.querySelectorAll('.rule-panel').forEach(el=>el.classList.toggle('active', el.dataset.panel===key));
}
async function loadRules(){
let groups = [];
try {
const d = await API.rules();
groups = (d && d.groups) || [];
} catch(e){ groups = []; }
// 排除刻意隐藏的「势力等级」,且无条目的类别不显示
const visible = groups.filter(g => (g.key||'') !== 'faction' && (g.items||[]).length);
if (!visible.length) {
renderGroups(FALLBACK.groups);
return;
}
renderGroups(visible);
}
loadRules();
</script>
</body>
</html>