dsh-remote-plugin 0.5.8 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1 -0
- package/README.md +1 -0
- package/apk/dsh-remote.apk +0 -0
- package/gateway.cjs +247 -0
- package/package.json +1 -1
- package/public/admin.html +91 -15
- package/public/admin.js +26 -0
- package/public/app.js +237 -8
- package/public/desktop/desktop.css +63 -0
- package/public/desktop/desktop.html +83 -0
- package/public/desktop/desktop.js +203 -5
- package/public/index.html +97 -3
- package/public/styles.css +72 -0
- package/public/update.json +4 -3
- package/public/version.json +1 -1
|
@@ -57,10 +57,14 @@ const state = {
|
|
|
57
57
|
questionModal: null,
|
|
58
58
|
streamsOk: { mux: false, host: false },
|
|
59
59
|
errCount: 0,
|
|
60
|
+
streamMode: 'ws', // 'ws' | 'poll'
|
|
61
|
+
pollSeq: { mux: 0, host: 0 },
|
|
60
62
|
fs: { path: null, initial: null, loaded: false },
|
|
61
63
|
view: 'sessions'
|
|
62
64
|
}
|
|
63
65
|
const streams = {}
|
|
66
|
+
let pollTimer = null
|
|
67
|
+
let wsRetryTimer = null
|
|
64
68
|
|
|
65
69
|
function esc(s) { return String(s ?? '').replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])) }
|
|
66
70
|
function short(id) { return '…' + String(id).slice(-8) }
|
|
@@ -92,6 +96,86 @@ function toast(text, kind = '') {
|
|
|
92
96
|
toast._t = setTimeout(() => el.classList.add('hidden'), 2600)
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
/* ---------------- 反馈 ---------------- */
|
|
100
|
+
const FEEDBACK_LINKS = {
|
|
101
|
+
githubIssues: 'https://github.com/Blank-not-black/dsh-Remote/issues',
|
|
102
|
+
giteeIssues: 'https://gitee.com/Blankneverfails/dsh-Remote/issues',
|
|
103
|
+
bili: 'https://space.bilibili.com/419009275/dynamic',
|
|
104
|
+
repo: 'https://github.com/Blank-not-black/dsh-Remote'
|
|
105
|
+
}
|
|
106
|
+
async function copyText(text) {
|
|
107
|
+
try { await navigator.clipboard.writeText(text); return true } catch {}
|
|
108
|
+
try {
|
|
109
|
+
const ta = document.createElement('textarea')
|
|
110
|
+
ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'
|
|
111
|
+
document.body.appendChild(ta); ta.focus(); ta.select()
|
|
112
|
+
const ok = document.execCommand('copy')
|
|
113
|
+
ta.remove(); return ok
|
|
114
|
+
} catch { return false }
|
|
115
|
+
}
|
|
116
|
+
function openFeedbackMenu() {
|
|
117
|
+
$('feedback-menu').classList.remove('hidden')
|
|
118
|
+
$('btn-feedback').setAttribute('aria-expanded', 'true')
|
|
119
|
+
const first = $('feedback-menu').querySelector('[role="menuitem"]')
|
|
120
|
+
if (first) first.focus()
|
|
121
|
+
}
|
|
122
|
+
function closeFeedbackMenu() {
|
|
123
|
+
$('feedback-menu').classList.add('hidden')
|
|
124
|
+
$('btn-feedback').setAttribute('aria-expanded', 'false')
|
|
125
|
+
}
|
|
126
|
+
function toggleFeedbackMenu() {
|
|
127
|
+
$('feedback-menu').classList.contains('hidden') ? openFeedbackMenu() : closeFeedbackMenu()
|
|
128
|
+
}
|
|
129
|
+
function openFeedbackModal() {
|
|
130
|
+
document.querySelectorAll('#fb-chips .ds-fb-chip').forEach(b => b.classList.toggle('current', b.dataset.fbType === 'bug'))
|
|
131
|
+
$('fb-msg').value = ''
|
|
132
|
+
$('fb-contact').value = ''
|
|
133
|
+
$('modal-feedback').classList.remove('hidden')
|
|
134
|
+
setTimeout(() => $('fb-msg').focus(), 50)
|
|
135
|
+
}
|
|
136
|
+
function closeFeedbackModal() { $('modal-feedback').classList.add('hidden') }
|
|
137
|
+
async function submitFeedback() {
|
|
138
|
+
const type = document.querySelector('#fb-chips .ds-fb-chip.current')?.dataset.fbType || 'bug'
|
|
139
|
+
const message = $('fb-msg').value.trim()
|
|
140
|
+
const contact = $('fb-contact').value.trim()
|
|
141
|
+
if (!message) { toast(t('ds.feedbackEmpty'), 'err'); return }
|
|
142
|
+
if (message.length > 2000) { toast(t('ds.feedbackTooLong'), 'err'); return }
|
|
143
|
+
const btn = $('fb-submit')
|
|
144
|
+
btn.disabled = true
|
|
145
|
+
try {
|
|
146
|
+
const res = await fetch(apiUrl('/feedback'), {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer ' + state.token },
|
|
149
|
+
body: JSON.stringify({ type, message, contact, appVersion: '' })
|
|
150
|
+
})
|
|
151
|
+
let json = {}
|
|
152
|
+
try { json = await res.json() } catch {}
|
|
153
|
+
if (res.ok && json.ok) { toast(t('ds.feedbackSubmitted'), 'ok'); closeFeedbackModal() }
|
|
154
|
+
else if (res.status === 429) { toast(t('ds.feedbackRateLimited'), 'err') }
|
|
155
|
+
else { toast(t('ds.feedbackSubmitFailed', { msg: json.error || res.status }), 'err') }
|
|
156
|
+
} catch {
|
|
157
|
+
toast(t('ds.feedbackSubmitFailed', { msg: t('ds.feedbackNetworkError') }), 'err')
|
|
158
|
+
} finally {
|
|
159
|
+
btn.disabled = false
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function showTip(text, anchorRect) {
|
|
163
|
+
const tip = $('ds-tip')
|
|
164
|
+
if (!tip) return
|
|
165
|
+
tip.textContent = text
|
|
166
|
+
tip.classList.remove('hidden')
|
|
167
|
+
const margin = 8
|
|
168
|
+
const tw = tip.offsetWidth
|
|
169
|
+
const th = tip.offsetHeight
|
|
170
|
+
let left = anchorRect.left + anchorRect.width / 2 - tw / 2
|
|
171
|
+
left = Math.max(margin, Math.min(left, window.innerWidth - tw - margin))
|
|
172
|
+
let top = anchorRect.top - th - 10
|
|
173
|
+
if (top < margin) top = anchorRect.bottom + 10
|
|
174
|
+
tip.style.left = left + 'px'
|
|
175
|
+
tip.style.top = top + 'px'
|
|
176
|
+
}
|
|
177
|
+
function hideTip() { const tip = $('ds-tip'); if (tip) tip.classList.add('hidden') }
|
|
178
|
+
|
|
95
179
|
/* ---------------- API ---------------- */
|
|
96
180
|
function apiUrl(path) { return (state.server || '') + path }
|
|
97
181
|
async function rpc(method, payload = {}) {
|
|
@@ -403,9 +487,11 @@ function deleteGroup(name) {
|
|
|
403
487
|
if (state.token) selectFastestServer({ silent: true })
|
|
404
488
|
}
|
|
405
489
|
|
|
406
|
-
/* ---------------- 事件流 ---------------- */
|
|
490
|
+
/* ---------------- 事件流 (WebSocket + 轮询降级) ---------------- */
|
|
407
491
|
function openStreams() {
|
|
408
492
|
if (!state.token) return
|
|
493
|
+
if (state.streamMode === 'poll') stopPolling()
|
|
494
|
+
state.streamMode = 'ws'
|
|
409
495
|
openStream('mux', onMuxFrame, true)
|
|
410
496
|
openStream('host', onHostFrame, false)
|
|
411
497
|
}
|
|
@@ -420,12 +506,14 @@ function openStream(kind, handler, refreshOnOpen) {
|
|
|
420
506
|
ws.onopen = () => {
|
|
421
507
|
state.streamsOk[kind] = true
|
|
422
508
|
state.errCount = 0
|
|
509
|
+
if (state.streamMode === 'poll') { stopPolling(); state.streamMode = 'ws' }
|
|
423
510
|
updateConn()
|
|
424
511
|
if (kind === 'mux') { state.approvals = []; state.questions = []; renderNotifStack() }
|
|
425
512
|
if (refreshOnOpen) refreshSessions()
|
|
426
513
|
}
|
|
427
514
|
ws.onmessage = (msg) => {
|
|
428
515
|
state.streamsOk[kind] = true
|
|
516
|
+
state.errCount = 0
|
|
429
517
|
updateConn()
|
|
430
518
|
try { handler(JSON.parse(msg.data)) } catch {}
|
|
431
519
|
}
|
|
@@ -433,11 +521,84 @@ function openStream(kind, handler, refreshOnOpen) {
|
|
|
433
521
|
state.streamsOk[kind] = false
|
|
434
522
|
state.errCount++
|
|
435
523
|
updateConn()
|
|
524
|
+
if (state.streamMode === 'poll') return
|
|
525
|
+
if (state.errCount >= 3) { enterPollMode(); return }
|
|
436
526
|
if (state.servers.length && state.errCount % 5 === 0) setTimeout(() => selectFastestServer({ silent: true }), 300)
|
|
437
527
|
if (streams[kind] === ws) setTimeout(() => openStream(kind, handler, refreshOnOpen), 1200)
|
|
438
528
|
}
|
|
439
529
|
ws.onerror = () => { try { ws.close() } catch {} }
|
|
440
530
|
}
|
|
531
|
+
|
|
532
|
+
/* ---------------- 轮询降级模式 ---------------- */
|
|
533
|
+
function enterPollMode() {
|
|
534
|
+
if (state.streamMode === 'poll') return
|
|
535
|
+
state.streamMode = 'poll'
|
|
536
|
+
state.pollSeq = { mux: 0, host: 0 }
|
|
537
|
+
state.streamsOk = { mux: false, host: false }
|
|
538
|
+
try { streams.mux?.close() } catch {}
|
|
539
|
+
try { streams.host?.close() } catch {}
|
|
540
|
+
streams.mux = null
|
|
541
|
+
streams.host = null
|
|
542
|
+
refreshSessions()
|
|
543
|
+
startPolling()
|
|
544
|
+
updateConn()
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function stopPolling() {
|
|
548
|
+
clearInterval(pollTimer)
|
|
549
|
+
pollTimer = null
|
|
550
|
+
clearTimeout(wsRetryTimer)
|
|
551
|
+
wsRetryTimer = null
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function startPolling() {
|
|
555
|
+
stopPolling()
|
|
556
|
+
pollTimer = setInterval(pollOnce, 4000)
|
|
557
|
+
wsRetryTimer = setInterval(tryRestoreWs, 30000)
|
|
558
|
+
pollOnce()
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
let pollInFlight = false
|
|
562
|
+
async function pollOnce() {
|
|
563
|
+
if (state.streamMode !== 'poll' || pollInFlight) return
|
|
564
|
+
pollInFlight = true
|
|
565
|
+
try {
|
|
566
|
+
await Promise.all([pollKind('mux'), pollKind('host')])
|
|
567
|
+
} finally {
|
|
568
|
+
pollInFlight = false
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async function pollKind(kind) {
|
|
573
|
+
if (state.streamMode !== 'poll') return
|
|
574
|
+
const since = state.pollSeq[kind] || 0
|
|
575
|
+
let res
|
|
576
|
+
try {
|
|
577
|
+
const signal = typeof AbortSignal?.timeout === 'function' ? AbortSignal.timeout(5000) : undefined
|
|
578
|
+
const headers = { authorization: 'Bearer ' + state.token, 'x-dsh-remote-client': 'web' }
|
|
579
|
+
res = signal ? await fetch(apiUrl(`/api/events.poll?kind=${kind}&since=${since}`), { signal, headers }) : await fetch(apiUrl(`/api/events.poll?kind=${kind}&since=${since}`), { headers })
|
|
580
|
+
} catch { return }
|
|
581
|
+
if (res.status === 401) { toast(t('ds.toastAuth'), 'err'); return }
|
|
582
|
+
if (!res.ok) return
|
|
583
|
+
let data
|
|
584
|
+
try { data = await res.json() } catch { return }
|
|
585
|
+
if (!data || !Array.isArray(data.events)) return
|
|
586
|
+
if (typeof data.latestSeq === 'number' && data.latestSeq < since) state.pollSeq[kind] = 0
|
|
587
|
+
for (const item of data.events) {
|
|
588
|
+
if (item.seq > (state.pollSeq[kind] || 0)) {
|
|
589
|
+
state.pollSeq[kind] = item.seq
|
|
590
|
+
if (kind === 'mux') onMuxFrame(item.event)
|
|
591
|
+
else onHostFrame(item.event)
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function tryRestoreWs() {
|
|
597
|
+
if (state.streamMode !== 'poll' || !state.token) return
|
|
598
|
+
// 轮询继续跑,等 WS 真正 onopen 后再切回,避免重连窗口丢事件
|
|
599
|
+
openStream('mux', onMuxFrame, true)
|
|
600
|
+
openStream('host', onHostFrame, false)
|
|
601
|
+
}
|
|
441
602
|
function onMuxFrame(full) {
|
|
442
603
|
const f = full.payload
|
|
443
604
|
if (!f) return
|
|
@@ -814,7 +975,8 @@ function renderStats(days) {
|
|
|
814
975
|
const peakH = cost > 0 ? Math.round((d.peak.cost || 0) / cost * 100) : 0
|
|
815
976
|
const offH = cost > 0 ? Math.max(0, 100 - peakH) : 0
|
|
816
977
|
const totalH = cost > 0 ? Math.max(3, Math.round(cost / maxCost * 100)) : 0
|
|
817
|
-
|
|
978
|
+
const tip = `${d.date}\n${t('ds.statsPeak')} ${fmtCost(d.peak.cost)}\n${t('ds.statsOff')} ${fmtCost(d.off.cost)}`
|
|
979
|
+
return `<div class="ds-stats-bar" data-tip="${esc(tip)}">
|
|
818
980
|
<div class="bars" style="height:${totalH}%"><div class="seg peak" style="height:${peakH}%"></div><div class="seg off" style="height:${offH}%"></div></div>
|
|
819
981
|
<div class="val">${cost > 0 ? fmtCost(cost) : ''}</div>
|
|
820
982
|
<div class="lbl">${d.date.slice(5)}</div>
|
|
@@ -834,14 +996,21 @@ function showView(id) {
|
|
|
834
996
|
}
|
|
835
997
|
function updateConn() {
|
|
836
998
|
const el = $('conn-badge')
|
|
999
|
+
const cur = state.servers.find(s => s.url === state.server)
|
|
1000
|
+
const group = cur ? cur.group : state.activeGroup
|
|
1001
|
+
const label = cur ? (cur.note || cur.url) : (state.server || t('ds.origin'))
|
|
1002
|
+
if (state.streamMode === 'poll') {
|
|
1003
|
+
el.textContent = '●'
|
|
1004
|
+
el.className = 'ds-conn off'
|
|
1005
|
+
el.title = t('ds.connPollTitle')
|
|
1006
|
+
$('server-badge').textContent = t('ds.currentServer', { group, url: label })
|
|
1007
|
+
return
|
|
1008
|
+
}
|
|
837
1009
|
const any = Object.values(state.streamsOk).some(Boolean)
|
|
838
1010
|
const all = state.streamsOk.mux && state.streamsOk.host
|
|
839
1011
|
el.textContent = '●'
|
|
840
1012
|
el.className = 'ds-conn ' + (all ? 'on' : any ? 'ing' : '')
|
|
841
1013
|
el.title = all ? t('ds.connOn') : any ? t('ds.connIng') : t('ds.connOff')
|
|
842
|
-
const cur = state.servers.find(s => s.url === state.server)
|
|
843
|
-
const group = cur ? cur.group : state.activeGroup
|
|
844
|
-
const label = cur ? (cur.note || cur.url) : (state.server || t('ds.origin'))
|
|
845
1014
|
$('server-badge').textContent = t('ds.currentServer', { group, url: label })
|
|
846
1015
|
}
|
|
847
1016
|
|
|
@@ -866,6 +1035,35 @@ function bindUi() {
|
|
|
866
1035
|
})
|
|
867
1036
|
$('btn-stats-top').addEventListener('click', toggleStatsDrawer)
|
|
868
1037
|
$('stats-drawer-close').addEventListener('click', toggleStatsDrawer)
|
|
1038
|
+
// 反馈
|
|
1039
|
+
$('btn-feedback').addEventListener('click', (e) => { e.stopPropagation(); toggleFeedbackMenu() })
|
|
1040
|
+
$('feedback-menu').addEventListener('click', (e) => {
|
|
1041
|
+
if (e.target.closest('a[role="menuitem"]')) closeFeedbackMenu()
|
|
1042
|
+
})
|
|
1043
|
+
$('btn-copy-link').addEventListener('click', async () => {
|
|
1044
|
+
const ok = await copyText(FEEDBACK_LINKS.repo)
|
|
1045
|
+
toast(t(ok ? 'ds.feedbackCopied' : 'ds.feedbackCopyFailed'), ok ? 'ok' : 'err')
|
|
1046
|
+
closeFeedbackMenu()
|
|
1047
|
+
})
|
|
1048
|
+
$('btn-write-feedback').addEventListener('click', () => { closeFeedbackMenu(); openFeedbackModal() })
|
|
1049
|
+
$('fb-cancel').addEventListener('click', closeFeedbackModal)
|
|
1050
|
+
$('fb-submit').addEventListener('click', submitFeedback)
|
|
1051
|
+
document.querySelectorAll('#fb-chips .ds-fb-chip').forEach(btn =>
|
|
1052
|
+
btn.addEventListener('click', () => {
|
|
1053
|
+
document.querySelectorAll('#fb-chips .ds-fb-chip').forEach(b => b.classList.toggle('current', b === btn))
|
|
1054
|
+
}))
|
|
1055
|
+
document.addEventListener('click', (e) => {
|
|
1056
|
+
if (!e.target.closest('.ds-feedback')) closeFeedbackMenu()
|
|
1057
|
+
})
|
|
1058
|
+
document.addEventListener('keydown', (e) => {
|
|
1059
|
+
if (e.key === 'Escape' && !$('feedback-menu').classList.contains('hidden')) { closeFeedbackMenu(); $('btn-feedback').focus() }
|
|
1060
|
+
})
|
|
1061
|
+
// 统计柱状图悬停提示: 自定义 tooltip, 限制在视口内, 避免原生 title 溢出抽屉
|
|
1062
|
+
$('stats-chart').addEventListener('mouseover', (e) => {
|
|
1063
|
+
const bar = e.target.closest('.ds-stats-bar')
|
|
1064
|
+
if (bar && bar.dataset.tip) showTip(bar.dataset.tip, bar.getBoundingClientRect())
|
|
1065
|
+
})
|
|
1066
|
+
$('stats-chart').addEventListener('mouseleave', hideTip)
|
|
869
1067
|
|
|
870
1068
|
$('btn-server-speed').addEventListener('click', () => selectFastestServer({ silent: false }))
|
|
871
1069
|
$('btn-server-add').addEventListener('click', addServer)
|
package/public/index.html
CHANGED
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
<span class="brand-name">DSH Remote</span>
|
|
21
21
|
</div>
|
|
22
22
|
<div class="topbar-right">
|
|
23
|
-
<button id="btn-
|
|
24
|
-
<button id="btn-refresh" class="icon-btn" data-i18n-title="a11y.refresh" data-i18n-aria="a11y.refresh">⟳</button>
|
|
25
|
-
<span id="conn-badge" class="conn-badge off" data-i18n="conn.off">未连接</span>
|
|
23
|
+
<button id="btn-feedback" class="topbar-btn icon-btn" data-i18n-title="feedback.title" data-i18n-aria="feedback.title" aria-haspopup="menu" aria-expanded="false">💬</button>
|
|
24
|
+
<button id="btn-refresh" class="topbar-btn icon-btn" data-i18n-title="a11y.refresh" data-i18n-aria="a11y.refresh">⟳</button>
|
|
25
|
+
<span id="conn-badge" class="topbar-btn conn-badge off" data-i18n="conn.off">未连接</span>
|
|
26
26
|
</div>
|
|
27
27
|
</header>
|
|
28
28
|
|
|
@@ -199,6 +199,25 @@
|
|
|
199
199
|
<button id="btn-check-update" class="mini-btn" data-i18n="settings.check">检查</button>
|
|
200
200
|
</div>
|
|
201
201
|
</div>
|
|
202
|
+
<div class="settings-group feedback-card">
|
|
203
|
+
<div class="setting-row">
|
|
204
|
+
<div><div class="setting-name" data-i18n="settings.feedbackTitle">反馈渠道</div><div class="setting-desc" data-i18n="settings.feedbackDesc">GitHub / Gitee / B站:反馈 bug、提建议、唠嗑</div></div>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="feedback-links">
|
|
207
|
+
<a class="feedback-btn primary" href="https://github.com/Blank-not-black/dsh-Remote" target="_blank" rel="noopener">
|
|
208
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8a8.01 8.01 0 0 0 5.47 7.59c.4.08.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg>
|
|
209
|
+
<span>GitHub</span>
|
|
210
|
+
</a>
|
|
211
|
+
<a class="feedback-btn" href="https://gitee.com/Blankneverfails/dsh-Remote" target="_blank" rel="noopener">
|
|
212
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="9" r="6"/><path d="M7.5 13.5 7 19a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l-.5-5.5"/><circle cx="10" cy="8.6" r=".5" fill="currentColor" stroke="none"/><circle cx="14" cy="8.6" r=".5" fill="currentColor" stroke="none"/><path d="M11 10.8c.6.5 1.4.5 2 0"/></svg>
|
|
213
|
+
<span>Gitee</span>
|
|
214
|
+
</a>
|
|
215
|
+
<a class="feedback-btn" href="https://space.bilibili.com/419009275/dynamic" target="_blank" rel="noopener">
|
|
216
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="6" width="17" height="11" rx="2"/><path d="M8.5 3.5v3M15.5 3.5v3"/><circle cx="9.2" cy="10.6" r=".5" fill="currentColor" stroke="none"/><circle cx="14.8" cy="10.6" r=".5" fill="currentColor" stroke="none"/><path d="M9.5 14c1.4 1 3.6 1 5 0"/></svg>
|
|
217
|
+
<span>B站</span>
|
|
218
|
+
</a>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
202
221
|
<div class="settings-group">
|
|
203
222
|
<div class="setting-row">
|
|
204
223
|
<div><div class="setting-name" data-i18n="settings.resetTitle">清空本地数据</div><div class="setting-desc" data-i18n="settings.resetDesc">令牌与缓存</div></div>
|
|
@@ -209,6 +228,33 @@
|
|
|
209
228
|
</section>
|
|
210
229
|
</main>
|
|
211
230
|
|
|
231
|
+
<!-- 反馈底部菜单(非模态) -->
|
|
232
|
+
<div id="feedback-backdrop" class="sheet-backdrop hidden"></div>
|
|
233
|
+
<div id="feedback-sheet" class="sheet hidden" role="menu" data-i18n-aria="feedback.title">
|
|
234
|
+
<div class="sheet-handle" aria-hidden="true"></div>
|
|
235
|
+
<div class="sheet-title" data-i18n="feedback.title">反馈</div>
|
|
236
|
+
<button class="sheet-item" id="btn-write-feedback" role="menuitem">
|
|
237
|
+
<span class="sheet-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg></span>
|
|
238
|
+
<span class="sheet-item-body"><span class="sheet-item-name" data-i18n="feedback.write">写反馈</span><span class="sheet-item-desc" data-i18n="feedback.writeDesc">App 内直接提交</span></span>
|
|
239
|
+
</button>
|
|
240
|
+
<a class="sheet-item primary" href="https://github.com/Blank-not-black/dsh-Remote/issues" target="_blank" rel="noopener" role="menuitem">
|
|
241
|
+
<span class="sheet-ico"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8a8.01 8.01 0 0 0 5.47 7.59c.4.08.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></span>
|
|
242
|
+
<span class="sheet-item-body"><span class="sheet-item-name">GitHub Issues</span><span class="sheet-item-desc" data-i18n="feedback.githubDesc">反馈 bug / 提建议</span></span>
|
|
243
|
+
</a>
|
|
244
|
+
<a class="sheet-item" href="https://gitee.com/Blankneverfails/dsh-Remote/issues" target="_blank" rel="noopener" role="menuitem">
|
|
245
|
+
<span class="sheet-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="9" r="6"/><path d="M7.5 13.5 7 19a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l-.5-5.5"/><circle cx="10" cy="8.6" r=".5" fill="currentColor" stroke="none"/><circle cx="14" cy="8.6" r=".5" fill="currentColor" stroke="none"/><path d="M11 10.8c.6.5 1.4.5 2 0"/></svg></span>
|
|
246
|
+
<span class="sheet-item-body"><span class="sheet-item-name">Gitee 反馈</span><span class="sheet-item-desc" data-i18n="feedback.giteeDesc">国内镜像,无需代理</span></span>
|
|
247
|
+
</a>
|
|
248
|
+
<a class="sheet-item" href="https://space.bilibili.com/419009275/dynamic" target="_blank" rel="noopener" role="menuitem">
|
|
249
|
+
<span class="sheet-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="6" width="17" height="11" rx="2"/><path d="M8.5 3.5v3M15.5 3.5v3"/><circle cx="9.2" cy="10.6" r=".5" fill="currentColor" stroke="none"/><circle cx="14.8" cy="10.6" r=".5" fill="currentColor" stroke="none"/><path d="M9.5 14c1.4 1 3.6 1 5 0"/></svg></span>
|
|
250
|
+
<span class="sheet-item-body"><span class="sheet-item-name">B站交流</span><span class="sheet-item-desc" data-i18n="feedback.biliDesc">UP 动态页交流</span></span>
|
|
251
|
+
</a>
|
|
252
|
+
<button class="sheet-item" id="btn-copy-link" role="menuitem">
|
|
253
|
+
<span class="sheet-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="8" y="3" width="12" height="12" rx="2"/><path d="M16 8h3a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1v-3"/></svg></span>
|
|
254
|
+
<span class="sheet-item-body"><span class="sheet-item-name" data-i18n="feedback.copyLink">复制项目链接</span><span class="sheet-item-desc" data-i18n="feedback.copyDesc">手动分享给朋友</span></span>
|
|
255
|
+
</button>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
212
258
|
<nav class="bottom-nav">
|
|
213
259
|
<button data-view="view-home" class="nav-btn active"><span class="nav-ico">▤</span><span data-i18n="nav.sessions">会话</span></button>
|
|
214
260
|
<button data-view="view-files" class="nav-btn"><span class="nav-ico">⇅</span><span data-i18n="nav.files">文件</span></button>
|
|
@@ -241,6 +287,26 @@
|
|
|
241
287
|
</div>
|
|
242
288
|
</div>
|
|
243
289
|
|
|
290
|
+
<!-- 写反馈模态 -->
|
|
291
|
+
<div id="modal-feedback" class="modal hidden">
|
|
292
|
+
<div class="modal-card">
|
|
293
|
+
<div class="modal-title" data-i18n="feedback.modalTitle">写反馈</div>
|
|
294
|
+
<div class="modal-body">
|
|
295
|
+
<div class="fb-chips" id="fb-chips">
|
|
296
|
+
<button class="fb-chip current" data-fb-type="bug" data-i18n="feedback.typeBug">Bug</button>
|
|
297
|
+
<button class="fb-chip" data-fb-type="suggestion" data-i18n="feedback.typeSuggestion">建议</button>
|
|
298
|
+
<button class="fb-chip" data-fb-type="other" data-i18n="feedback.typeOther">其他</button>
|
|
299
|
+
</div>
|
|
300
|
+
<textarea id="fb-msg" class="fb-textarea" rows="5" maxlength="2000" data-i18n-placeholder="feedback.messagePlaceholder" placeholder="请描述遇到的问题或建议(必填,≤2000 字)"></textarea>
|
|
301
|
+
<input id="fb-contact" class="fb-input" maxlength="200" data-i18n-placeholder="feedback.contactPlaceholder" placeholder="联系方式(可选):邮箱 / 微信 / B站 ID">
|
|
302
|
+
</div>
|
|
303
|
+
<div class="modal-actions">
|
|
304
|
+
<button id="fb-cancel" class="btn subtle" data-i18n="feedback.cancel">取消</button>
|
|
305
|
+
<button id="fb-submit" class="btn primary" data-i18n="feedback.submit">提交</button>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
244
310
|
<!-- goal 模态 -->
|
|
245
311
|
<div id="modal-goal" class="modal hidden">
|
|
246
312
|
<div class="modal-card">
|
|
@@ -282,6 +348,7 @@
|
|
|
282
348
|
zh: {
|
|
283
349
|
'a11y.hostAdmin': '主机管理', 'a11y.refresh': '刷新', 'a11y.more': '更多操作', 'a11y.moreTitle': '指令/权限/模型',
|
|
284
350
|
'conn.on': '已连接', 'conn.off': '未连接', 'conn.reconnecting': '连接中断,正在重连…',
|
|
351
|
+
'conn.poll': '轮询', 'conn.pollTitle': '当前网络不支持实时推送,已降级为轮询(延迟数秒)',
|
|
285
352
|
'conn.titleGroup': '{group} · {url}({ms})',
|
|
286
353
|
'common.refreshing': '刷新中…',
|
|
287
354
|
'nav.sessions': '会话', 'nav.files': '文件', 'nav.pending': '待办', 'nav.stats': '统计', 'nav.settings': '设置',
|
|
@@ -384,6 +451,7 @@
|
|
|
384
451
|
'update.latestV': '已是最新 v{version}', 'update.latestRemote': '最新版本 v{version}', 'update.latestToast': '已是最新版本',
|
|
385
452
|
'update.checkFailedDesc': '检查失败:{msg}', 'update.checkFailed': '检查更新失败:{msg}',
|
|
386
453
|
'update.downloadStarted': '开始下载,完成后会弹出安装页', 'update.downloadFailed': '无法启动下载:{msg}',
|
|
454
|
+
'update.corrupted': '下载文件损坏,请重试', 'update.serverFileMissing': '服务器上还没有对应版本的文件,请稍后再试',
|
|
387
455
|
'update.installUnsupported': '当前版本不支持 App 内安装,已转浏览器下载',
|
|
388
456
|
'update.expand': '展开', 'update.collapse': '收起',
|
|
389
457
|
'scan.imageLoadFailed': '图片加载失败', 'scan.decodeUnsupported': '当前设备不支持图片解码',
|
|
@@ -411,6 +479,18 @@
|
|
|
411
479
|
'settings.hostTitle': 'DSH 状态', 'settings.hostProbing': '探测中…', 'settings.probe': '探测', 'settings.probeFailed': '探测失败',
|
|
412
480
|
'settings.hostDesc': 'DSH {version} · {cwd} · 附加会话 {n}',
|
|
413
481
|
'settings.updateTitle': '检查更新', 'settings.updateLoading': '加载中…', 'settings.downloadUpdate': '下载并安装更新', 'settings.check': '检查',
|
|
482
|
+
'settings.feedbackTitle': '反馈渠道', 'settings.feedbackDesc': 'GitHub / Gitee / B站:反馈 bug、提建议、唠嗑',
|
|
483
|
+
'feedback.title': '反馈', 'feedback.githubDesc': '反馈 bug / 提建议', 'feedback.giteeDesc': '国内镜像,无需代理',
|
|
484
|
+
'feedback.biliDesc': 'UP 动态页交流', 'feedback.copyLink': '复制项目链接', 'feedback.copyDesc': '手动分享给朋友',
|
|
485
|
+
'feedback.copied': '项目链接已复制', 'feedback.copyFailed': '复制失败,请手动复制',
|
|
486
|
+
'feedback.write': '写反馈', 'feedback.writeDesc': 'App 内直接提交', 'feedback.modalTitle': '写反馈',
|
|
487
|
+
'feedback.typeBug': 'Bug', 'feedback.typeSuggestion': '建议', 'feedback.typeOther': '其他',
|
|
488
|
+
'feedback.messagePlaceholder': '请描述遇到的问题或建议(必填,≤2000 字)',
|
|
489
|
+
'feedback.contactPlaceholder': '联系方式(可选):邮箱 / 微信 / B站 ID',
|
|
490
|
+
'feedback.cancel': '取消', 'feedback.submit': '提交',
|
|
491
|
+
'feedback.empty': '请填写描述内容', 'feedback.tooLong': '描述不能超过 2000 字',
|
|
492
|
+
'feedback.submitted': '已提交,感谢反馈',
|
|
493
|
+
'feedback.rateLimited': '提交太频繁,请稍后再试', 'feedback.rateLimitedAt': '提交太频繁,请 {n} 秒后再试', 'feedback.submitFailed': '提交失败:{msg}', 'feedback.networkError': '网络错误',
|
|
414
494
|
'settings.resetTitle': '清空本地数据', 'settings.resetDesc': '令牌与缓存', 'settings.reset': '重置',
|
|
415
495
|
'settings.confirmReset': '清除本地令牌、服务器与缓存?', 'settings.notifyDenied': '通知权限未开启',
|
|
416
496
|
'settings.toolsShown': '已显示工具调用', 'settings.toolsHidden': '已隐藏工具调用',
|
|
@@ -424,6 +504,7 @@
|
|
|
424
504
|
en: {
|
|
425
505
|
'a11y.hostAdmin': 'Host admin', 'a11y.refresh': 'Refresh', 'a11y.more': 'More actions', 'a11y.moreTitle': 'Commands / Permissions / Models',
|
|
426
506
|
'conn.on': 'Connected', 'conn.off': 'Offline', 'conn.reconnecting': 'Connection lost, reconnecting…',
|
|
507
|
+
'conn.poll': 'Polling', 'conn.pollTitle': 'Realtime push is unavailable on this network; degraded to polling (a few seconds delay)',
|
|
427
508
|
'conn.titleGroup': '{group} · {url} ({ms})',
|
|
428
509
|
'common.refreshing': 'Refreshing…',
|
|
429
510
|
'nav.sessions': 'Sessions', 'nav.files': 'Files', 'nav.pending': 'Inbox', 'nav.stats': 'Stats', 'nav.settings': 'Settings',
|
|
@@ -526,6 +607,7 @@
|
|
|
526
607
|
'update.latestV': 'Up to date v{version}', 'update.latestRemote': 'Latest version v{version}', 'update.latestToast': 'Already up to date',
|
|
527
608
|
'update.checkFailedDesc': 'Check failed: {msg}', 'update.checkFailed': 'Update check failed: {msg}',
|
|
528
609
|
'update.downloadStarted': 'Downloading, the install page will open when ready', 'update.downloadFailed': 'Could not start download: {msg}',
|
|
610
|
+
'update.corrupted': 'Downloaded file is corrupted, please retry', 'update.serverFileMissing': 'The file for this version is not on the server yet, please try again later',
|
|
529
611
|
'update.installUnsupported': 'This version cannot install in-app, opening browser download',
|
|
530
612
|
'update.expand': 'Expand', 'update.collapse': 'Collapse',
|
|
531
613
|
'scan.imageLoadFailed': 'Image failed to load', 'scan.decodeUnsupported': 'This device cannot decode images',
|
|
@@ -553,6 +635,18 @@
|
|
|
553
635
|
'settings.hostTitle': 'DSH status', 'settings.hostProbing': 'Probing…', 'settings.probe': 'Probe', 'settings.probeFailed': 'Probe failed',
|
|
554
636
|
'settings.hostDesc': 'DSH {version} · {cwd} · {n} attached sessions',
|
|
555
637
|
'settings.updateTitle': 'Check for updates', 'settings.updateLoading': 'Loading…', 'settings.downloadUpdate': 'Download & install update', 'settings.check': 'Check',
|
|
638
|
+
'settings.feedbackTitle': 'Feedback', 'settings.feedbackDesc': 'GitHub / Gitee / Bilibili: report bugs, suggest features, or just chat',
|
|
639
|
+
'feedback.title': 'Feedback', 'feedback.githubDesc': 'Report bugs · suggest features', 'feedback.giteeDesc': 'Mirror in China, no proxy needed',
|
|
640
|
+
'feedback.biliDesc': 'Chat on the UP\'s Bilibili page', 'feedback.copyLink': 'Copy project link', 'feedback.copyDesc': 'Share it manually',
|
|
641
|
+
'feedback.copied': 'Project link copied', 'feedback.copyFailed': 'Copy failed, copy manually',
|
|
642
|
+
'feedback.write': 'Write feedback', 'feedback.writeDesc': 'Submit from the app', 'feedback.modalTitle': 'Write feedback',
|
|
643
|
+
'feedback.typeBug': 'Bug', 'feedback.typeSuggestion': 'Suggestion', 'feedback.typeOther': 'Other',
|
|
644
|
+
'feedback.messagePlaceholder': 'Describe the bug or suggestion (required, ≤2000 chars)',
|
|
645
|
+
'feedback.contactPlaceholder': 'Contact (optional): email / WeChat / Bilibili ID',
|
|
646
|
+
'feedback.cancel': 'Cancel', 'feedback.submit': 'Submit',
|
|
647
|
+
'feedback.empty': 'Please fill in the description', 'feedback.tooLong': 'Description must be ≤2000 characters',
|
|
648
|
+
'feedback.submitted': 'Submitted — thanks!',
|
|
649
|
+
'feedback.rateLimited': 'Too frequent, try again later', 'feedback.rateLimitedAt': 'Too frequent, try again in {n}s', 'feedback.submitFailed': 'Submit failed: {msg}', 'feedback.networkError': 'Network error',
|
|
556
650
|
'settings.resetTitle': 'Clear local data', 'settings.resetDesc': 'Token and caches', 'settings.reset': 'Reset',
|
|
557
651
|
'settings.confirmReset': 'Clear local token, servers and caches?', 'settings.notifyDenied': 'Notification permission not granted',
|
|
558
652
|
'settings.toolsShown': 'Tool calls shown', 'settings.toolsHidden': 'Tool calls hidden',
|
package/public/styles.css
CHANGED
|
@@ -59,6 +59,14 @@ button {
|
|
|
59
59
|
font-size: 17px; display: grid; place-items: center;
|
|
60
60
|
}
|
|
61
61
|
.icon-btn:active { background: var(--dsr-panel-2); }
|
|
62
|
+
/* 顶栏按钮统一: 反馈/刷新/连接徽章同高同基线 */
|
|
63
|
+
.topbar-btn {
|
|
64
|
+
height: 44px; box-sizing: border-box;
|
|
65
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
66
|
+
line-height: 1; vertical-align: middle; flex-shrink: 0;
|
|
67
|
+
}
|
|
68
|
+
.topbar-right .topbar-btn.icon-btn { width: 44px; padding: 0; font-size: 19px; border-radius: 12px; }
|
|
69
|
+
.topbar-right .topbar-btn.conn-badge { padding: 0 14px; border-radius: 999px; font-size: 12.5px; }
|
|
62
70
|
.mini-btn {
|
|
63
71
|
padding: 5px 11px; border-radius: 9px; font-size: 13px;
|
|
64
72
|
background: var(--dsr-panel); border: 1px solid var(--dsr-line); color: var(--dsr-accent-strong);
|
|
@@ -66,6 +74,19 @@ button {
|
|
|
66
74
|
flex-shrink: 0;
|
|
67
75
|
}
|
|
68
76
|
.mini-btn:active { background: var(--dsr-panel-2); }
|
|
77
|
+
a.mini-btn { text-decoration: none; display: inline-flex; align-items: center; }
|
|
78
|
+
.feedback-links { display: flex; gap: 8px; flex-wrap: wrap; padding: 0 14px 14px; }
|
|
79
|
+
.feedback-card { background: var(--dsr-accent-soft); border-color: var(--dsr-accent-line); }
|
|
80
|
+
.feedback-btn {
|
|
81
|
+
flex: 1 1 0; min-width: 92px; min-height: 44px;
|
|
82
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 7px;
|
|
83
|
+
padding: 9px 12px; border-radius: 12px; font-size: 13.5px; font-weight: 600;
|
|
84
|
+
background: var(--dsr-panel); border: 1px solid var(--dsr-line); color: var(--dsr-accent-strong);
|
|
85
|
+
text-decoration: none; cursor: pointer; white-space: nowrap;
|
|
86
|
+
}
|
|
87
|
+
.feedback-btn svg { width: 17px; height: 17px; fill: currentColor; flex-shrink: 0; }
|
|
88
|
+
.feedback-btn.primary { background: var(--dsr-accent-strong); border-color: var(--dsr-accent-strong); color: var(--dsr-on-accent); }
|
|
89
|
+
.feedback-btn:active { filter: brightness(.94); }
|
|
69
90
|
.btn {
|
|
70
91
|
flex: 1; padding: 11px 14px; border-radius: 12px; font-size: 15px; font-weight: 600;
|
|
71
92
|
border: 1px solid var(--dsr-line);
|
|
@@ -521,6 +542,57 @@ body.in-session .main { padding-bottom: 84px; }
|
|
|
521
542
|
font-size: 10px; font-weight: 800; padding: 0 6px; line-height: 16px;
|
|
522
543
|
}
|
|
523
544
|
|
|
545
|
+
/* ---------- 反馈底部菜单 ---------- */
|
|
546
|
+
.sheet-backdrop {
|
|
547
|
+
position: fixed; inset: 0; z-index: 45;
|
|
548
|
+
background: var(--dsr-overlay);
|
|
549
|
+
animation: sheet-fade .18s ease;
|
|
550
|
+
}
|
|
551
|
+
.sheet {
|
|
552
|
+
position: fixed; left: 0; right: 0; bottom: 0; z-index: 46;
|
|
553
|
+
max-height: 72vh; overflow-y: auto; overscroll-behavior: contain;
|
|
554
|
+
background: var(--dsr-bg-2); border-top: 1px solid var(--dsr-line); border-radius: 20px 20px 0 0;
|
|
555
|
+
padding: 8px 12px calc(12px + env(safe-area-inset-bottom, 0px));
|
|
556
|
+
box-shadow: 0 -10px 34px var(--dsr-shadow);
|
|
557
|
+
animation: sheet-up .22s cubic-bezier(.2,.8,.3,1);
|
|
558
|
+
}
|
|
559
|
+
.sheet-handle { width: 38px; height: 4px; border-radius: 999px; background: var(--dsr-line); margin: 2px auto 10px; }
|
|
560
|
+
.sheet-title { font-size: 14px; font-weight: 700; padding: 0 6px 8px; }
|
|
561
|
+
.sheet-item {
|
|
562
|
+
width: 100%; min-height: 52px; display: flex; align-items: center; gap: 11px;
|
|
563
|
+
padding: 9px 10px; border-radius: 13px; border: none; background: transparent; color: var(--dsr-text);
|
|
564
|
+
font: inherit; text-align: left; cursor: pointer; text-decoration: none;
|
|
565
|
+
}
|
|
566
|
+
.sheet-item:hover, .sheet-item:focus-visible { background: var(--dsr-bg); outline: none; }
|
|
567
|
+
.sheet-item.primary { background: var(--dsr-accent-soft); border: 1px solid var(--dsr-accent-line); }
|
|
568
|
+
.sheet-item.primary:hover { background: var(--dsr-accent-soft); filter: brightness(1.03); }
|
|
569
|
+
.sheet-ico {
|
|
570
|
+
width: 38px; height: 38px; border-radius: 11px; flex-shrink: 0;
|
|
571
|
+
display: grid; place-items: center; background: var(--dsr-panel); border: 1px solid var(--dsr-line); color: var(--dsr-accent-strong);
|
|
572
|
+
}
|
|
573
|
+
.sheet-item.primary .sheet-ico { background: var(--dsr-accent-strong); border-color: var(--dsr-accent-strong); color: var(--dsr-on-accent); }
|
|
574
|
+
.sheet-ico svg { width: 18px; height: 18px; fill: currentColor; }
|
|
575
|
+
.sheet-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
|
576
|
+
.sheet-item-name { font-size: 14px; font-weight: 600; }
|
|
577
|
+
.sheet-item-desc { font-size: 12px; color: var(--dsr-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
578
|
+
@keyframes sheet-fade { from { opacity: 0 } to { opacity: 1 } }
|
|
579
|
+
@keyframes sheet-up { from { transform: translateY(24px); opacity: .6 } to { transform: translateY(0); opacity: 1 } }
|
|
580
|
+
|
|
581
|
+
/* 写反馈弹层 */
|
|
582
|
+
.fb-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
|
|
583
|
+
.fb-chip {
|
|
584
|
+
min-height: 36px; padding: 7px 14px; border-radius: 999px; font-size: 13px;
|
|
585
|
+
background: var(--dsr-panel); border: 1px solid var(--dsr-line); color: var(--dsr-text); cursor: pointer;
|
|
586
|
+
}
|
|
587
|
+
.fb-chip.current { background: var(--dsr-accent-soft); border-color: var(--dsr-accent-line); color: var(--dsr-accent-strong); font-weight: 600; }
|
|
588
|
+
.fb-textarea, .fb-input {
|
|
589
|
+
width: 100%; box-sizing: border-box; margin-bottom: 10px;
|
|
590
|
+
background: var(--dsr-panel); color: var(--dsr-text); border: 1px solid var(--dsr-line); border-radius: 12px;
|
|
591
|
+
padding: 10px 12px; font: inherit; font-size: 14px; outline: none; resize: vertical;
|
|
592
|
+
}
|
|
593
|
+
.fb-textarea:focus, .fb-input:focus { border-color: var(--dsr-accent-line); }
|
|
594
|
+
.fb-input { min-height: 42px; }
|
|
595
|
+
|
|
524
596
|
/* ---------- 模态 ---------- */
|
|
525
597
|
.modal {
|
|
526
598
|
position: fixed; inset: 0; z-index: 40; display: grid; place-items: center;
|
package/public/update.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.6.0",
|
|
3
3
|
"apkUrl": "dsh-remote.apk",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
4
|
+
"sha256": "ebab5d1ce9c669c50742a121f23d29ca4d8e38d60bee8fe95ba112db6761b788",
|
|
5
|
+
"releasedAt": "2026-08-18T13:08:00.384Z",
|
|
6
|
+
"notes": "新增事件轮询降级(公网隧道网络下自动切换,消息不丢、延迟数秒);更新下载完整性校验(SHA-256);网关稳定性加固(设备自动清理、发布流程修复);文档重构(能力对比表 / FAQ)"
|
|
6
7
|
}
|
package/public/version.json
CHANGED