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 {
|
||||
position: fixed;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
bottom: max(12px, env(safe-area-inset-bottom));
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
padding: 6px;
|
||||
background: rgba(15, 22, 36, 0.88);
|
||||
padding: 6px 8px calc(6px + env(safe-area-inset-bottom, 0px));
|
||||
background: rgba(8, 12, 20, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
border-radius: 0;
|
||||
z-index: 100;
|
||||
box-shadow: var(--shadow-md);
|
||||
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.mobile-nav button {
|
||||
flex: 1;
|
||||
@@ -1301,7 +1304,42 @@ select.input {
|
||||
<script>
|
||||
|
||||
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'}
|
||||
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';
|
||||
|
||||
Reference in New Issue
Block a user