前端: - world.html 游戏内聊天组件重构:表情选择器改固定方格+自适应列+去滚动条; 输入框清空占位文字+隐藏滚动条;聊天面板整体高度缩短约 1/3; 顶部用户区改为纯文字并点开个人信息弹窗(头像/头像框/性别/说明/称号/注册) - chat.html 聊天输入框压扁(46→34px) - codex.html 新增神话品质、天赋/技能书/书籍分类与预置数据 - rules.html 规则文档更新 后端(Odoo): - game_base 附件改为看板视图(图片/名称/备注/大小) - yt_world 玩家菜单直接指向 hr.employee - game_base 员工表单隐藏原生联系人/组织架构页签 - chat 模型/接口/视图与规则数据扩充 资源:生成 4x4 资源精灵表(树木/石材/芦苇/宝箱)
235 lines
15 KiB
HTML
235 lines
15 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" />
|
||
<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>
|