183 lines
9.4 KiB
JavaScript
183 lines
9.4 KiB
JavaScript
/* ============================================================
|
||
宇森官网 · 公共脚本(所有子页面共享)
|
||
common.js — 含:导航高亮 / 登录态 / SVG 缩略图生成器
|
||
依赖:assets/game-api.js(需在本文件之前加载)
|
||
============================================================ */
|
||
|
||
// ---------- 导航栏:根据当前 URL 自动高亮对应链接 ----------
|
||
(function(){
|
||
const links = document.querySelectorAll('.nav a');
|
||
const page = location.pathname.split('/').pop().replace(/\.html$/,'') || 'index';
|
||
links.forEach(a => {
|
||
const href = a.getAttribute('href') || '';
|
||
const target = href.replace(/\.html$/,'').replace(/^\.\//,'');
|
||
if (target === page || (page === '' && target === 'index')) {
|
||
a.classList.add('active');
|
||
}
|
||
});
|
||
})();
|
||
|
||
// ---------- 登录态 / 右上角用户信息(从 Odoo 取) ----------
|
||
(function(){
|
||
const userBox = document.getElementById('userBox');
|
||
if (!userBox) return;
|
||
|
||
// sessionStorage key:'yusen:me' = JSON({uid,name,login}) 或 '' 表示已确认未登录
|
||
function saveMe(me){ try{ sessionStorage.setItem('yusen:me', me ? JSON.stringify(me) : ''); }catch(e){} }
|
||
function loadMe(){ try{ const v=sessionStorage.getItem('yusen:me'); return v===null?undefined:(v?JSON.parse(v):null); }catch(e){return undefined;} }
|
||
|
||
function renderLoggedOut(){
|
||
userBox.innerHTML = '<a href="login.html?next=world.html" class="auth-link">登录</a><a href="login.html?next=world.html" class="auth-btn">注册</a>';
|
||
document.querySelectorAll('.nav .auth-only').forEach(el => el.classList.remove('visible'));
|
||
}
|
||
|
||
function renderLoggedIn(me){
|
||
userBox.innerHTML = '<span class="ub-name"></span><button class="ub-logout" id="ubLogout">退出</button>';
|
||
const nm = userBox.querySelector('.ub-name');
|
||
if (nm) nm.textContent = me.name || me.login;
|
||
document.querySelectorAll('.nav .auth-only').forEach(el => el.classList.add('visible'));
|
||
const lb = document.getElementById('ubLogout');
|
||
if (lb) lb.addEventListener('click', async ()=>{
|
||
try { await API.logout(); } catch(e){}
|
||
saveMe(null);
|
||
sessionStorage.removeItem('yusen:me');
|
||
location.href = 'index.html';
|
||
});
|
||
}
|
||
|
||
// 1) 同步首屏:从 sessionStorage 立即渲染正确状态(消除 topbar 闪一下)
|
||
const cached = loadMe();
|
||
if (cached === undefined) {
|
||
// 第一次访问:渲染一个不闪烁的占位(保持默认空,等异步结果)
|
||
userBox.innerHTML = '<span class="ub-name" style="opacity:.5">…</span>';
|
||
} else if (cached && cached.uid) {
|
||
renderLoggedIn(cached);
|
||
} else {
|
||
renderLoggedOut();
|
||
}
|
||
|
||
// 2) 异步校验:拿真实登录态,纠正缓存
|
||
async function refreshUserBox(){
|
||
try{
|
||
const me = await API.me();
|
||
if (me && me.uid) { saveMe(me); renderLoggedIn(me); }
|
||
else { saveMe(null); renderLoggedOut(); }
|
||
}catch(e){
|
||
if (cached === undefined) renderLoggedOut(); // 首次访问 + 接口挂了才退回未登录
|
||
}
|
||
}
|
||
if (typeof API !== 'undefined') refreshUserBox();
|
||
else document.addEventListener('DOMContentLoaded', ()=>{ if (typeof API !== 'undefined') refreshUserBox(); });
|
||
})();
|
||
|
||
// ---------- 程序化矢量缩略图(图鉴/首页共用) ----------
|
||
function hashStr(s){let h=2166136261;for(let i=0;i<s.length;i++){h^=s.charCodeAt(i);h=Math.imul(h,16777619);}return h>>>0;}
|
||
function rng(seed){let s=seed>>>0;return function(){s=(Math.imul(s,1664525)+1013904223)>>>0;return s/4294967296;};}
|
||
const TIERCOL={'普通':'#9aa6c4','精良':'#5fd38a','稀有':'#4cc9f0','史诗':'#c77dff','传说':'#e7b85c'};
|
||
|
||
function starPts(cx,cy,spikes,outer,inner){
|
||
let pts=[],rot=-Math.PI/2;
|
||
for(let i=0;i<spikes*2;i++){const rad=i%2?inner:outer;const a=rot+i*Math.PI/spikes;pts.push((cx+Math.cos(a)*rad).toFixed(1)+','+(cy+Math.sin(a)*rad).toFixed(1));}
|
||
return pts.join(' ');
|
||
}
|
||
function polygonPts(cx,cy,sides,R){
|
||
let pts=[];for(let i=0;i<sides;i++){const a=-Math.PI/2+i*2*Math.PI/sides;pts.push((cx+Math.cos(a)*R).toFixed(1)+','+(cy+Math.sin(a)*R).toFixed(1));}
|
||
return pts.join(' ');
|
||
}
|
||
|
||
// 各品类图形生成器
|
||
function shRace(r,seed){
|
||
const v=Math.floor(r()*3);
|
||
let s=`<ellipse cx="50" cy="55" rx="19" ry="21" fill="url(#fg${seed})" stroke="rgba(255,255,255,.25)" stroke-width="1"/>`;
|
||
if(v===0){s+=`<path d="M35 40 L31 20 L43 36 Z" fill="url(#fg${seed})"/><path d="M65 40 L69 20 L57 36 Z" fill="url(#fg${seed})"/>`;}
|
||
else if(v===1){s+=`<path d="M32 52 L20 42 L36 46 Z" fill="url(#fg${seed})"/><path d="M68 52 L80 42 L64 46 Z" fill="url(#fg${seed})"/>`;}
|
||
else{s+=`<path d="M34 36 L42 24 L50 34 L58 24 L66 36 Z" fill="url(#fg${seed})"/>`;}
|
||
s+=`<circle cx="43" cy="55" r="2.6" fill="#0b1020"/><circle cx="57" cy="55" r="2.6" fill="#0b1020"/>`;
|
||
return s;
|
||
}
|
||
function shBuild(r,seed){
|
||
let s=`<rect x="34" y="44" width="32" height="34" rx="2" fill="url(#fg${seed})" opacity=".92"/>`;
|
||
s+=`<path d="M30 44 L50 28 L70 44 Z" fill="url(#fg${seed})"/>`;
|
||
for(let i=0;i<3;i++)s+=`<rect x="${39+i*7}" y="52" width="4" height="7" rx="1" fill="rgba(8,12,26,.7)"/>`;
|
||
return s;
|
||
}
|
||
function shElement(r,seed){
|
||
const sides=3+Math.floor(r()*6);
|
||
let s=`<polygon points="${polygonPts(50,52,sides,25)}" fill="none" stroke="url(#fg${seed})" stroke-width="3"/>`;
|
||
s+=`<circle cx="50" cy="52" r="7" fill="url(#fg${seed})"/>`;
|
||
return s;
|
||
}
|
||
function shPersona(r,seed){
|
||
let s=`<path d="M32 36 Q50 28 68 36 Q72 56 50 70 Q28 56 32 36 Z" fill="url(#fg${seed})" opacity=".9"/>`;
|
||
s+=`<circle cx="43" cy="48" r="2.8" fill="#0b1020"/><circle cx="57" cy="48" r="2.8" fill="#0b1020"/>`;
|
||
s+=`<path d="M43 60 Q50 64 57 60" stroke="#0b1020" stroke-width="2" fill="none"/>`;
|
||
return s;
|
||
}
|
||
function shTalent(r,seed){
|
||
const sp=4+Math.floor(r()*4);
|
||
let s=`<polygon points="${starPts(50,52,sp,26,11)}" fill="url(#fg${seed})" opacity=".92"/>`;
|
||
s+=`<circle cx="50" cy="52" r="5" fill="rgba(8,12,26,.55)"/>`;
|
||
return s;
|
||
}
|
||
function shRelic(r,seed){
|
||
let s=`<path d="M50 24 L70 46 L50 76 L30 46 Z" fill="url(#fg${seed})" opacity=".9" stroke="rgba(255,255,255,.3)" stroke-width="1"/>`;
|
||
s+=`<path d="M30 46 L70 46 M50 24 L42 46 L50 76 M50 24 L58 46 L50 76" stroke="rgba(8,12,26,.4)" stroke-width="1" fill="none"/>`;
|
||
return s;
|
||
}
|
||
function shEquip(sub,seed){
|
||
switch(sub){
|
||
case 'head': return `<path d="M32 56 Q32 30 50 30 Q68 30 68 56 L68 60 L32 60 Z" fill="url(#fg${seed})"/><rect x="44" y="44" width="12" height="5" rx="2" fill="rgba(8,12,26,.6)"/>`;
|
||
case 'hand': {let s=`<rect x="36" y="40" width="28" height="22" rx="6" fill="url(#fg${seed})"/><rect x="33" y="44" width="9" height="14" rx="3" fill="url(#fg${seed})"/>`;for(let i=0;i<3;i++)s+=`<rect x="${40+i*8}" y="62" width="5" height="8" rx="2" fill="url(#fg${seed})"/>`;return s;}
|
||
case 'chest': return `<path d="M30 46 Q50 38 70 46 L70 66 Q50 72 30 66 Z" fill="url(#fg${seed})"/><rect x="47" y="46" width="6" height="18" fill="rgba(8,12,26,.5)"/>`;
|
||
case 'pants': return `<path d="M38 32 L62 32 L60 74 L52 74 L50 50 L48 74 L40 74 Z" fill="url(#fg${seed})"/>`;
|
||
case 'shoes': return `<rect x="34" y="44" width="28" height="6" rx="3" fill="url(#fg${seed})"/><path d="M34 48 L60 48 L66 64 L34 64 Z" fill="url(#fg${seed})"/>`;
|
||
case 'ring': return `<circle cx="50" cy="55" r="16" fill="none" stroke="url(#fg${seed})" stroke-width="5"/><path d="M50 29 L57 40 L43 40 Z" fill="url(#fg${seed})"/>`;
|
||
case 'neck': return `<path d="M32 42 Q50 64 68 42" fill="none" stroke="url(#fg${seed})" stroke-width="3"/><path d="M44 58 L50 72 L56 58 Z" fill="url(#fg${seed})"/>`;
|
||
case 'treasure': return `<rect x="38" y="60" width="24" height="9" rx="2" fill="url(#fg${seed})"/><circle cx="50" cy="46" r="14" fill="url(#fg${seed})" opacity=".9"/><circle cx="50" cy="46" r="6" fill="rgba(255,255,255,.4)"/>`;
|
||
default: return `<circle cx="50" cy="50" r="18" fill="url(#fg${seed})"/>`;
|
||
}
|
||
}
|
||
function shEvent(r,seed){
|
||
let s=`<ellipse cx="50" cy="52" rx="22" ry="28" fill="none" stroke="url(#fg${seed})" stroke-width="3"/>`;
|
||
s+=`<ellipse cx="50" cy="52" rx="12" ry="16" fill="url(#fg${seed})" opacity=".5"/>`;
|
||
return s;
|
||
}
|
||
|
||
function shapeFor(cat,sub,r,seed){
|
||
switch(cat){
|
||
case 'race': return shRace(r,seed);
|
||
case 'build': return shBuild(r,seed);
|
||
case 'element': return shElement(r,seed);
|
||
case 'persona': return shPersona(r,seed);
|
||
case 'talent': return shTalent(r,seed);
|
||
case 'relic': return shRelic(r,seed);
|
||
case 'equip': return shEquip(sub,seed);
|
||
case 'event': return shEvent(r,seed);
|
||
default: return `<circle cx="50" cy="50" r="18" fill="url(#fg${seed})"/>`;
|
||
}
|
||
}
|
||
|
||
function thumbSVG(it){
|
||
const seed=hashStr(it.cat+it.sub+it.name);
|
||
const r=rng(seed);
|
||
const h=Math.floor(r()*360);
|
||
const h2=(h+40)%360;
|
||
const tcol=TIERCOL[it.tier]||'#9aa6c4';
|
||
const shape=shapeFor(it.cat,it.sub,r,seed);
|
||
return `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice">`+
|
||
`<defs>`+
|
||
`<radialGradient id="bg${seed}" cx="50%" cy="36%" r="78%">`+
|
||
`<stop offset="0%" stop-color="hsl(${h},48%,26%)"/>`+
|
||
`<stop offset="100%" stop-color="hsl(${h},42%,9%)"/>`+
|
||
`</radialGradient>`+
|
||
`<linearGradient id="fg${seed}" x1="0" y1="0" x2="0" y2="1">`+
|
||
`<stop offset="0%" stop-color="hsl(${h},72%,66%)"/>`+
|
||
`<stop offset="100%" stop-color="hsl(${h2},70%,56%)"/>`+
|
||
`</linearGradient>`+
|
||
`</defs>`+
|
||
`<rect width="100" height="100" fill="url(#bg${seed})"/>`+
|
||
shape+
|
||
`<circle cx="50" cy="50" r="46" fill="none" stroke="${tcol}" stroke-width="2.5" opacity=".9"/>`+
|
||
`</svg>`;
|
||
}
|