(() => {
'use strict';
const root = document.getElementById('workspace');
const login = document.getElementById('login');
const paths = {
monitor: '',
settings: '',
sun: '',
moon: '',
user: '',
star: '',
search: '',
refresh: '',
arrow: '',
back: '',
network: '',
info: '',
logout: '',
check: '',
close: ''
};
const icon = name => ``;
const escape = value => String(value).replace(/[&<>"']/g, char => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' }[char]));
const defaults = [
{ id:'windows', os:'Windows 10', version:'64 位 · Windows', name:'设计工作站', resolution:'2560x1440', fps:60, endpoint:'192.168.1.50', app:'Desktop', note:'图形设计与日常办公', favorite:true },
{ id:'ubuntu', os:'Ubuntu', version:'24.04 LTS · Linux', name:'研发环境', resolution:'2560x1440', fps:30, endpoint:'192.168.1.51', app:'Desktop', note:'开发工具与研发工作环境', favorite:false },
{ id:'kylin', os:'银河麒麟', version:'桌面操作系统 · Linux', name:'办公桌面', resolution:'1920x1080', fps:30, endpoint:'', app:'Desktop', note:'暂未配置服务器,请联系管理员', favorite:false }
];
const read = () => { try { return JSON.parse(localStorage.getItem('yuexin-terminal-ui-v3') || 'null'); } catch { return null; } };
const saved = read();
let desktops = defaults.map(d => {
const local = Array.isArray(saved) && saved.find(v => v.id === d.id);
return local ? { ...d, endpoint:String(local.endpoint ?? d.endpoint), app:String(local.app || d.app), resolution:['1920x1080','2560x1440','3840x2160','1280x720'].includes(local.resolution) ? local.resolution : d.resolution, fps:[24,30,60].includes(local.fps) ? local.fps : d.fps, favorite:!!local.favorite } : { ...d };
});
const persist = () => { try { localStorage.setItem('yuexin-terminal-ui-v3', JSON.stringify(desktops)); return true; } catch { return false; } };
let tab = 'all', search = '', view = 'desktops', settingsTab = 'desktop', selectedId = 'windows', sessionState = 'connecting';
let timer, toastTimer;
let identity = {name:'终端用户',mode:'preview',service:'cloud',organization:'越欣科技'};
const selected = () => desktops.find(d => d.id === selectedId) || desktops[0];
const mark = d => {
const art = d.id === 'windows'
? ''
: `
`;
return `
${art}
`;
};
const prettyResolution = d => d.resolution.replace('x', ' × ');
const themeLabel = () => document.body.dataset.theme === 'dark' ? '切换浅色' : '切换深色';
const header = () => ``;
const footer = () => ``;
function card(d) {
const available = !!d.endpoint;
return `${mark(d)}
${escape(d.os)}
${escape(d.version)}
${escape(d.name)}
${available ? '可连接' : '待配置'}- 分辨率
- ${prettyResolution(d)}
- 帧率
- ${d.fps} FPS
${available ? escape(d.id === 'kylin' ? '国产办公工作环境' : d.note) : '暂未配置服务器,请联系管理员'}
`;
}
function list() {
return `${desktops.filter(d => d.endpoint).length} 台可连接 / 共 ${desktops.length} 台
`;
}
function renderGrid() {
const rows = desktops.filter(d => (tab !== 'favorites' || d.favorite) && (d.os + d.name).toLowerCase().includes(search.trim().toLowerCase()));
root.querySelector('#ws-grid').innerHTML = rows.length ? rows.map(card).join('') : `${icon('search')}
${search ? '没有找到匹配的桌面' : '还没有收藏的桌面'}
${search ? '试试系统名称,或清除搜索条件。' : '点击桌面卡片右上角的星标即可收藏。'}
`;
}
const option = (value, label, current) => ``;
function desktopSettings() {
const d = selected();
return `桌面连接设置
配置当前桌面的连接地址和显示参数。
`;
}
function settings() {
let panel;
if (settingsTab === 'desktop') panel = desktopSettings();
if (settingsTab === 'network') panel = `网络状态
连接远程桌面前,请确认终端已接入工作网络。
- 连接方式
- 有线网络
- 网络状态
- 已连接
- 地址获取
- 自动获取(DHCP)
此处为设备状态示例。实际网络配置由终端系统管理。
`;
if (settingsTab === 'device') panel = `设备信息
联系管理员时,可提供设备序列号协助定位。
- 设备序列号
- VDI-8F42A1
- 设备型号
- RK3528 云终端
- 处理器架构
- ARM64
- 管理快捷键
- Ctrl + Alt + Shift + M
`;
return `终端设置
`;
}
function session() {
const d = selected(), failure = sessionState === 'error', recovery = sessionState === 'reconnect';
const title = failure ? '暂时无法连接' : recovery ? '正在恢复连接' : '正在连接桌面';
const steps = recovery ? ['检测网络连接','恢复桌面会话','恢复桌面画面'] : ['检查连接配置','建立远程会话','准备桌面画面'];
return `${mark(d)}
${title}
${escape(d.os)} · ${escape(d.name)}
${failure ? '
' : '
'}${failure ? `
尚未接入远程桌面服务
当前为交互原型,未向服务器发送连接请求。实际终端需由桌面服务返回连接结果。
` : `
${steps.map((step,i) => `- ${i === 0 ? '✓' : i+1}${step}${i === 1 ? '' : ''}
`).join('')}
${recovery ? '连接中断后将尝试恢复原会话,请保持设备联网。' : '请保持设备联网。连接完成后,桌面将全屏打开。'}
当前预览不发起真实远程连接。
`}
${failure ? `` : ''}
`;
}
function render() {
root.innerHTML = header() + `${view === 'desktops' ? list() : view === 'settings' ? settings() : session()}` + footer();
if (view === 'desktops') renderGrid();
}
function showWorkspace(next = 'desktops') {
clearTimeout(timer);
document.dispatchEvent(new CustomEvent('terminal:open-workspace'));
view = next;
login.classList.remove('active');
root.hidden = false;
document.body.classList.add('in-workspace');
render();
window.scrollTo(0,0);
}
function home() { showWorkspace(); }
function leave() {
clearTimeout(timer);
root.hidden = true;
document.body.classList.remove('in-workspace');
login.classList.add('active');
identity = {name:'终端用户',mode:'preview',service:'cloud',organization:'越欣科技'};
tab='all';search='';
document.dispatchEvent(new CustomEvent('terminal:logout'));
window.scrollTo(0,0);
}
function notify(message) {
clearTimeout(toastTimer);
root.querySelector('.ws-toast')?.remove();
const toast = document.createElement('div');
toast.className = 'ws-toast'; toast.setAttribute('role','status'); toast.textContent = message; root.append(toast);
toastTimer = setTimeout(() => toast.remove(), 3000);
}
function start(id, state = 'connecting') {
selectedId = id;
if (!selected().endpoint) return;
sessionState = state;
showWorkspace('session');
if (state !== 'error') timer = setTimeout(() => { if (view === 'session') { sessionState = 'error'; render(); } }, 12000);
}
function toggleTheme() {
const dark = document.body.dataset.theme === 'dark';
document.body.dataset.theme = dark ? 'light' : 'dark';
const oldTheme = document.getElementById('theme');
oldTheme.textContent = dark ? '◉ 浅色外观' : '▣ 深色外观';
if (!root.hidden) {
const b = root.querySelector('[data-action="theme"]');
b.innerHTML = icon(dark ? 'moon' : 'sun');b.setAttribute('aria-label',themeLabel());b.title = themeLabel();
}
}
// Authentication interactions own login entry; direct preview URLs remain available for design review.
document.addEventListener('terminal:login', event => {
identity = {...identity,...event.detail};tab='all';search='';home();
const title = root.querySelector('h1');
if (title) { title.tabIndex = -1; title.focus({preventScroll:true}); }
});
document.getElementById('theme').addEventListener('click', toggleTheme);
root.addEventListener('input', event => {
if (event.target.id === 'ws-search') { search = event.target.value; renderGrid(); }
});
root.addEventListener('change', event => {
if (event.target.id === 'ws-target') { selectedId = event.target.value; render(); }
});
root.addEventListener('click', event => {
const b = event.target.closest('[data-action]');
if (!b || b.disabled) return;
switch (b.dataset.action) {
case 'home': home(); break;
case 'logout': leave(); break;
case 'theme': toggleTheme(); break;
case 'settings': settingsTab = 'desktop'; showWorkspace('settings'); break;
case 'edit': selectedId = b.dataset.id; settingsTab = 'desktop'; showWorkspace('settings'); break;
case 'settings-tab': settingsTab = b.dataset.tab; render(); break;
case 'connect': start(b.dataset.id); break;
case 'favorite': { const d = desktops.find(v => v.id === b.dataset.id); d.favorite = !d.favorite; persist(); render(); break; }
case 'tab': tab = b.dataset.tab; render(); break;
case 'clear-search': search = ''; tab = 'all'; render(); break;
case 'refresh': render(); notify('示例桌面列表已刷新'); break;
}
});
root.addEventListener('submit', event => {
if (event.target.id !== 'ws-settings-form') return;
event.preventDefault();
const form = event.target, values = new FormData(form), endpoint = String(values.get('endpoint')).trim(), app = String(values.get('app')).trim();
const field = form.querySelector('[name="endpoint"]');
if (endpoint && !/^(?:[a-z\d](?:[a-z\d.-]*[a-z\d])?)(?::\d{1,5})?$/i.test(endpoint)) {
form.querySelector('#ws-form-error').textContent = '请输入 IP 地址或域名,可附加端口号。';field.setAttribute('aria-invalid','true');field.focus();return;
}
if (!app) { form.querySelector('#ws-form-error').textContent = '请填写应用名,例如 Desktop。';form.querySelector('[name="app"]').focus();return; }
const d = selected();
Object.assign(d,{endpoint,app,resolution:String(values.get('resolution')),fps:Number(values.get('fps'))});
const stored = persist();home();notify(stored ? '设置已保存到本机预览' : '设置已应用,本次会话内有效');
});
document.addEventListener('keydown', event => {
if (event.ctrlKey && event.altKey && event.shiftKey && event.key.toLowerCase() === 'm') { event.preventDefault();settingsTab='desktop';showWorkspace('settings'); }
if (event.key === 'Escape' && !root.hidden && view !== 'desktops') home();
});
const params = new URLSearchParams(location.search);
const preview = params.get('preview');
if (preview) {
if (['dark','light'].includes(params.get('theme'))) document.body.dataset.theme = params.get('theme');
if (['connecting','reconnect','error'].includes(preview)) start('windows',preview);
else if (preview === 'settings') showWorkspace('settings');
else showWorkspace();
}
})();