feat(web): 不支持内嵌时明确弹层;修掉被 promise 吞掉的 ReferenceError

1) 点不支持在工作区显示的应用 → 弹出提示卡片
   标题「该应用不支持在工作区显示」+ 域名 + 服务器返回的原因 +
   「在新标签页打开 / 知道了」两个按钮;面板里同时保留同样的说明。
   之前只在面板里显示一行字,用户点了之后缺少明确反馈。

2) 修 ReferenceError: openUrl is not defined(线上真实故障)
   setWorkspaceState 第 4 个参数早先从 openUrl 改名为 note 时,blocked
   分支漏改,于是每次被拒内嵌都在函数内部抛异常,异常沿 promise 链被吞,
   导致后面的弹层根本没有执行、面板提示也只渲染到一半(URL 行和按钮缺失)。
   教训:验收必须采集 Runtime.exceptionThrown,并且断言交互元素真的存在;
   只断言"状态=blocked + 提示文字前缀"会恰好掩盖这类错误。
This commit is contained in:
HomeDash Bot
2026-09-11 19:52:19 +08:00
parent 0d9619e446
commit 76a6a83e7d
4 changed files with 156 additions and 28 deletions

View File

@ -25,29 +25,8 @@ pageInfo:
description: Self-hosted unified navigation
navLinks: []
footer: ''
sections:
- name: Favorites
icon: fas fa-star
collapsed: false
items:
- title: Gitea
description: Code repository
icon: fas fa-code-branch
url: http://192.168.1.100:3000
statusCheck: true
- name: History
icon: fas fa-history
collapsed: false
items:
- title: 安装浏览历史插件
description: 一键安装,自动按访问频次排序
icon: fas fa-download
url: /extension/install.html
- name: Tools
icon: fas fa-tools
collapsed: true
items:
- title: Example Service
description: Add your own services
icon: fas fa-cog
url: http://192.168.1.100:8080
sections: []
# 分组由管理后台(/admin/ 的「导航分组」)增删,初始为空:
# 新建一个分组后再往里添加服务即可。
# 「历史访问」分组由 history-server 根据浏览记录自动创建,
# 请不要手工写在这里。

View File

@ -537,11 +537,25 @@ func rebuildDashySections() {
break
}
}
foundHist := false
for _, si := range secs {
if isHistSection(si.name) {
si.raw["items"] = histItems
foundHist = true
}
}
// 配置里没有「历史访问」分组时补一个。该分组只能由这里创建:
// 管理后台明确禁止手工新建/改名/删除它(否则下次同步就被覆写),
// 所以若只靠用户手动建,功能就一直不会出现。
if !foundHist {
sectionsRaw = append(sectionsRaw, map[string]any{
"name": histSectionZh,
"icon": "fas fa-history",
"collapsed": false,
"items": histItems,
})
fmt.Println("[history] 已补建「历史访问」分组")
}
}
cfg["sections"] = sectionsRaw

View File

@ -1034,6 +1034,84 @@ html[data-theme] .navi-ws-notice-url {
word-break: break-all !important;
}
/* ── 「不支持在工作区显示」弹层 ──
被拒内嵌时不能只是面板里一行字:用户点了必须有明确反馈。
z-index 低于 Dashy 自己的配置弹层(10000),避免挡住它。 */
html[data-theme] .navi-ws-modal {
position: fixed !important;
inset: 0 !important;
z-index: 9990 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
padding: 18px !important;
background: rgba(15, 23, 42, .28) !important;
backdrop-filter: blur(4px) !important;
-webkit-backdrop-filter: blur(4px) !important;
animation: navi-fade-in .18s ease both;
}
html[data-theme] .navi-ws-modal-card {
box-sizing: border-box !important;
width: min(420px, 100%) !important;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
gap: 8px !important;
padding: 24px 22px 20px !important;
text-align: center !important;
background: rgba(255, 255, 255, .96) !important;
border: 1px solid rgba(255, 255, 255, .9) !important;
border-radius: 20px !important;
box-shadow: 0 26px 60px rgba(15, 23, 42, .28), inset 0 1px 0 rgba(255, 255, 255, .95) !important;
animation: navi-fade-up .22s cubic-bezier(.22, 1, .36, 1) both;
}
html[data-theme] .navi-ws-modal-title {
color: #0f172a !important;
font-size: 17px !important;
font-weight: 650 !important;
}
html[data-theme] .navi-ws-modal-host {
color: #0284c7 !important;
font-size: 13px !important;
word-break: break-all !important;
}
html[data-theme] .navi-ws-modal-text {
color: #64748b !important;
font-size: 13.5px !important;
line-height: 1.5 !important;
}
html[data-theme] .navi-ws-modal-actions {
display: flex !important;
flex-wrap: wrap !important;
gap: 10px !important;
justify-content: center !important;
margin-top: 10px !important;
}
html[data-theme] .navi-ws-modal-ok {
padding: 9px 18px !important;
color: #334155 !important;
font-family: inherit !important;
font-size: 13.5px !important;
background: rgba(241, 245, 249, .95) !important;
border: 1px solid rgba(203, 213, 225, .9) !important;
border-radius: 999px !important;
cursor: pointer;
-webkit-tap-highlight-color: transparent !important;
outline: none !important;
}
html[data-theme] .navi-ws-modal-ok:hover,
html[data-theme] .navi-ws-modal-ok:focus {
color: #0f172a !important;
background: rgba(255, 255, 255, .98) !important;
}
html[data-theme] .navi-ws-open {
margin-top: 6px !important;
padding: 9px 18px !important;

View File

@ -290,7 +290,9 @@
notice.append(wsText('navi-ws-notice-text', reason
? '服务器返回:' + reason
: '服务器返回了禁止内嵌的响应头。'));
notice.append(wsText('navi-ws-notice-url', openUrl || url || ''));
// 注意:这里只能用 url。早先本函数第 4 个参数叫 openUrl,改成 note 之后
// 本分支漏改,导致每次被拒内嵌都抛 ReferenceError,后面的弹层根本执行不到。
notice.append(wsText('navi-ws-notice-url', url || ''));
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'navi-ws-open';
@ -298,7 +300,7 @@
btn.addEventListener('click', (evt) => {
evt.preventDefault();
evt.stopPropagation();
if (nativeWindowOpen) nativeWindowOpen(openUrl || url, '_blank');
if (nativeWindowOpen) nativeWindowOpen(url, '_blank');
});
notice.append(btn);
}
@ -357,6 +359,60 @@
return bar;
}
// 被拒内嵌时明确弹一个提示(不只是面板里的一行字):
// 用户点了却什么也没发生是最差的体验。
let wsModal = null;
function closeWorkspaceModal() {
if (wsModal && wsModal.overlay.parentNode) wsModal.overlay.parentNode.removeChild(wsModal.overlay);
wsModal = null;
}
function showWorkspaceBlockedModal(url, reason) {
closeWorkspaceModal();
const overlay = document.createElement('div');
overlay.className = 'navi-ws-modal';
const card = document.createElement('div');
card.className = 'navi-ws-modal-card';
const title = document.createElement('div');
title.className = 'navi-ws-modal-title';
title.textContent = '该应用不支持在工作区显示';
const host = document.createElement('div');
host.className = 'navi-ws-modal-host';
host.textContent = hostOf(url);
const text = document.createElement('div');
text.className = 'navi-ws-modal-text';
text.textContent = reason
? ('服务器返回:' + reason + ',所以它不允许被嵌在其它页面里。')
: '服务器返回了禁止内嵌的响应头,所以它不允许被嵌在其它页面里。';
const actions = document.createElement('div');
actions.className = 'navi-ws-modal-actions';
const openBtn = document.createElement('button');
openBtn.type = 'button';
openBtn.className = 'navi-ws-open';
openBtn.textContent = '在新标签页打开';
openBtn.addEventListener('click', () => {
closeWorkspaceModal();
if (nativeWindowOpen) nativeWindowOpen(url, '_blank');
});
const okBtn = document.createElement('button');
okBtn.type = 'button';
okBtn.className = 'navi-ws-modal-ok';
okBtn.textContent = '知道了';
okBtn.addEventListener('click', closeWorkspaceModal);
actions.append(openBtn, okBtn);
card.append(title, host, text, actions);
overlay.append(card);
overlay.addEventListener('click', (evt) => {
if (evt.target === overlay) closeWorkspaceModal();
});
document.addEventListener('keydown', (evt) => {
if (evt.key === 'Escape') closeWorkspaceModal();
});
document.body.append(overlay);
wsModal = { overlay };
}
async function frameable(url) {
if (wsFrameCache.has(url)) return wsFrameCache.get(url);
let info = { frameable: true, conclusive: false, reason: '' };
@ -393,6 +449,7 @@
wsLastUrl = '';
parts.frame.removeAttribute('src');
setWorkspaceState('blocked', url, info.reason, url);
showWorkspaceBlockedModal(url, info.reason);
return;
}
wsLastUrl = url;