fix(ui): attach mobile nav to viewport bottom and localize api error messages
This commit is contained in:
+47
-9
@@ -743,19 +743,22 @@ select.input {
|
|||||||
|
|
||||||
.mobile-nav {
|
.mobile-nav {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 12px;
|
left: 0;
|
||||||
right: 12px;
|
right: 0;
|
||||||
bottom: max(12px, env(safe-area-inset-bottom));
|
bottom: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
padding: 6px;
|
padding: 6px 8px calc(6px + env(safe-area-inset-bottom, 0px));
|
||||||
background: rgba(15, 22, 36, 0.88);
|
background: rgba(8, 12, 20, 0.95);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
border: 1px solid var(--border-subtle);
|
border-top: 1px solid var(--border-subtle);
|
||||||
border-radius: var(--radius-lg);
|
border-left: 0;
|
||||||
|
border-right: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-radius: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
box-shadow: var(--shadow-md);
|
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
.mobile-nav button {
|
.mobile-nav button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -1301,7 +1304,42 @@ select.input {
|
|||||||
<script>
|
<script>
|
||||||
|
|
||||||
const $=id=>document.getElementById(id);let searchPage=1,searchKeyword='',searchSource='kw',searchLoading=false,hasMore=false,taskFilter='all';
|
const $=id=>document.getElementById(id);let searchPage=1,searchKeyword='',searchSource='kw',searchLoading=false,hasMore=false,taskFilter='all';
|
||||||
async function api(u,o){const r=await fetch('/api'+u,o);if(r.status===401){showLogin();throw new Error('请先登录')}if(!r.ok)throw new Error(await r.text());return r.status===204?null:r.json()}
|
function parseErrorMsg(msg) {
|
||||||
|
if (!msg) return '请求失败';
|
||||||
|
let str = String(msg).trim();
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(str);
|
||||||
|
if (obj && obj.error) str = String(obj.error);
|
||||||
|
else if (obj && obj.message) str = String(obj.message);
|
||||||
|
} catch(e) {}
|
||||||
|
const dict = {
|
||||||
|
'code already used or unavailable': '该兑换码已被使用或已失效',
|
||||||
|
'record not found': '兑换码不存在,请核对后重试',
|
||||||
|
'insufficient credits': '积分不足,请先兑换或充值积分',
|
||||||
|
'code expired': '该兑换码已过期',
|
||||||
|
'code required': '请输入兑换码',
|
||||||
|
'credits/count invalid': '积分或发行数量不合法',
|
||||||
|
'user not found': '用户不存在',
|
||||||
|
'user disabled': '账户已被禁用,无法执行此操作',
|
||||||
|
'amount must not be zero': '积分调整金额不能为 0',
|
||||||
|
'amount cannot be zero': '积分调整金额不能为 0',
|
||||||
|
'keyword required': '请输入搜索关键词',
|
||||||
|
'source/rid/title required': '歌曲信息不完整,无法加入下载队列',
|
||||||
|
'only failed tasks can be retried': '只有失败的任务才支持重试',
|
||||||
|
'invalid role': '无效的用户角色',
|
||||||
|
'invalid status': '无效的账户状态',
|
||||||
|
'invalid user id': '无效的用户 ID',
|
||||||
|
'invalid request body': '请求参数格式错误',
|
||||||
|
'username must be 4-10 English letters and password must be 8+ characters': '用户名必须为 4-10 位英文字母,密码至少 8 位',
|
||||||
|
'cannot remove the last active admin account': '系统必须保留至少一个激活的管理员账号',
|
||||||
|
'WebDAV URL、用户名或密码未配置': 'WebDAV URL、用户名或密码未配置'
|
||||||
|
};
|
||||||
|
for (const [k, v] of Object.entries(dict)) {
|
||||||
|
if (str.toLowerCase().includes(k.toLowerCase())) return v;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
async function api(u,o){const r=await fetch('/api'+u,o);if(r.status===401){showLogin();throw new Error('请先登录')}if(!r.ok){const txt=await r.text();throw new Error(parseErrorMsg(txt))}return r.status===204?null:r.json()}
|
||||||
function showLogin(){document.getElementById('loginOverlay').style.display='flex'}
|
function showLogin(){document.getElementById('loginOverlay').style.display='flex'}
|
||||||
async function loadAccount(){try{const c=await api('/credits');$('creditPanel').textContent='余额 '+c.balance+' · 预留 '+c.reserved+' · 可用 '+c.available;const u=window.currentUser||{};$('userDavCard').style.display=(u.role==='admin'||u.role==='privileged')?'':'none';if(u.role==='admin'||u.role==='privileged'){const d=await api('/webdav/config');$('userDavUrl').value=d.url||'';$('userDavUser').value=d.username||'';$('userDavRoot').value=d.rootPath||''}}catch(e){$('creditPanel').textContent=e.message}}
|
async function loadAccount(){try{const c=await api('/credits');$('creditPanel').textContent='余额 '+c.balance+' · 预留 '+c.reserved+' · 可用 '+c.available;const u=window.currentUser||{};$('userDavCard').style.display=(u.role==='admin'||u.role==='privileged')?'':'none';if(u.role==='admin'||u.role==='privileged'){const d=await api('/webdav/config');$('userDavUrl').value=d.url||'';$('userDavUser').value=d.username||'';$('userDavRoot').value=d.rootPath||''}}catch(e){$('creditPanel').textContent=e.message}}
|
||||||
let adminUsersCache=[];let adminRoleFilter='all';
|
let adminUsersCache=[];let adminRoleFilter='all';
|
||||||
|
|||||||
Reference in New Issue
Block a user