feat(web): prioritize SMS operations workspace

This commit is contained in:
chick
2026-07-19 10:30:52 +08:00
parent 0112e3320a
commit f7d69a0fa7
10 changed files with 532 additions and 68 deletions
+28 -37
View File
@@ -28,10 +28,10 @@ export interface InstanceDetailProps {
}
export const INSTANCE_MODULE_LABELS: Readonly<Record<InstanceModule, string>> = {
overview: '概览',
overview: '设备信息',
cellular: '蜂窝网络',
'device-network': '设备网络',
messages: '消息',
messages: '短信',
calls: '通话',
esim: 'eSIM',
notifications: '通知',
@@ -39,7 +39,7 @@ export const INSTANCE_MODULE_LABELS: Readonly<Record<InstanceModule, string>> =
ota: 'OTA',
};
const MODULES = Object.keys(INSTANCE_MODULE_LABELS) as readonly InstanceModule[];
const PRIMARY_MODULES = ['messages', 'overview'] as const satisfies readonly InstanceModule[];
const DEFAULT_EXPLANATIONS: Readonly<Record<Exclude<CapabilityState, 'supported'>, string>> = {
degraded: '此模块可用,但功能受限。',
@@ -47,20 +47,18 @@ const DEFAULT_EXPLANATIONS: Readonly<Record<Exclude<CapabilityState, 'supported'
unknown: '能力状态未知。',
};
function capabilityFor(map: InstanceCapabilityMap, module: InstanceModule): InstanceCapability {
return map[module] ?? { state: 'unknown' };
}
function canRender(capability: InstanceCapability): boolean {
function canRender(capability: InstanceCapability | undefined): boolean {
if (!capability) return true;
return capability.state === 'supported' || capability.state === 'degraded';
}
const CORE_MODULES = new Set<InstanceModule>(['overview', 'messages']);
function canOpen(module: InstanceModule, capability: InstanceCapability): boolean {
return CORE_MODULES.has(module) || canRender(capability);
function canOpen(module: InstanceModule, capability: InstanceCapability | undefined): boolean {
return capability ? canRender(capability) : CORE_MODULES.has(module);
}
function explanation(capability: InstanceCapability): string | null {
function explanation(capability: InstanceCapability | undefined): string | null {
if (!capability) return null;
if (capability.state === 'supported') return null;
return capability.explanation?.trim() || DEFAULT_EXPLANATIONS[capability.state];
}
@@ -124,19 +122,21 @@ export function InstanceDetail({
const map = capabilities ?? loadedCapabilities ?? {};
const origin = canonicalHttpOrigin(instance.origin);
const activeCapability = capabilityFor(map, module);
const activeCapability = map[module];
return (
<section className="instance-detail">
<aside className="instance-context" aria-label="当前实例">
<strong>{instance.name}</strong>
<code>{instance.id}</code>
<dl>
<div>
<dt></dt>
<dd>{displayStatus(instance.status)}</dd>
</div>
</dl>
{instance.status !== 'unknown' ? (
<dl>
<div>
<dt></dt>
<dd>{displayStatus(instance.status)}</dd>
</div>
</dl>
) : null}
{origin ? (
<a href={origin} target="_blank" rel="noopener noreferrer">
@@ -145,24 +145,15 @@ export function InstanceDetail({
<a href={`/settings/instances/${encodeURIComponent(instanceId)}`}></a>
<nav aria-label="实例模块">
<ul>
{MODULES.map((item) => {
const capability = capabilityFor(map, item);
const reason = explanation(capability);
{PRIMARY_MODULES.map((item) => {
return (
<li key={item} data-capability-state={capability.state}>
{canOpen(item, capability) ? (
<a
href={`/instances/${encodeURIComponent(instanceId)}/${item}`}
aria-current={module === item ? 'page' : undefined}
>
{INSTANCE_MODULE_LABELS[item]}
</a>
) : (
<>
<span>{INSTANCE_MODULE_LABELS[item]}</span>
<small>{reason}</small>
</>
)}
<li key={item}>
<a
href={`/instances/${encodeURIComponent(instanceId)}/${item}`}
aria-current={module === item ? 'page' : undefined}
>
{INSTANCE_MODULE_LABELS[item]}
</a>
</li>
);
})}
@@ -175,14 +166,14 @@ export function InstanceDetail({
{loadError ? <p role="alert">{loadError}</p> : null}
{!loading && canOpen(module, activeCapability) ? (
<>
{activeCapability.state === 'degraded' ? (
{activeCapability?.state === 'degraded' ? (
<p data-capability-state="degraded">{explanation(activeCapability)}</p>
) : null}
{moduleContent ?? <p>{INSTANCE_MODULE_LABELS[module]}</p>}
</>
) : null}
{!loading && !canOpen(module, activeCapability) ? (
<p data-capability-state={activeCapability.state}>{explanation(activeCapability)}</p>
<p data-capability-state={activeCapability?.state}>{explanation(activeCapability)}</p>
) : null}
</div>
</section>