- 删除 /browsers 列表/创建/详情页与菜单项,运行环境不再是独立资源 - 账号详情:GET /api/phase-a/accounts/:id 内联 environment 字段 (网关/出口/绑定版本/运行实例/不可调度原因),固定资源卡直接展示 - 状态卡两态开关:暂停→「启动账号」(start=resume+启动环境), 运行中→「暂停账号」(联动停环境);出口重绑卡片内完成 (POST /network-exits/:id/rebind 返回内联 environment,出口切换不再跳环境页) - 删除 creator/settings 残留的转写服务字段(后端 settings 无此列) - helpers.ts accountReadiness 改用 runtime_id/runtime_cleanup_pending - network-exits 列表绑定账号列改由账号列表内联数据驱动 - 全量验证:go test ./...(PG)全绿、tsc --noEmit 通过、max build 通过、 node --test 12/12 通过
106 lines
4.3 KiB
TypeScript
106 lines
4.3 KiB
TypeScript
// 公共展示工具:字段语义逐一对照 web.archived/src/shared/ui/ui.jsx
|
||
export function dateTime(value?: string | number | null): string {
|
||
if (!value) return '—';
|
||
const date = new Date(value);
|
||
return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleString();
|
||
}
|
||
|
||
// 对齐归档版 conflictMessage:优先后端 error 字段,409 冲突时附加提示。
|
||
export function conflictMessage(error: any, conflictHint?: string): string {
|
||
const status = error?.status ?? error?.body?.status;
|
||
const message = error?.message || error?.body?.error || '请求失败';
|
||
if (status === 409 && conflictHint) return `${message}:${conflictHint}`;
|
||
return message;
|
||
}
|
||
|
||
export const platforms = [
|
||
{ value: 'douyin', label: '抖音' },
|
||
];
|
||
|
||
export function platformLabel(value?: string): string {
|
||
return platforms.find((item) => item.value === value)?.label || value || '—';
|
||
}
|
||
|
||
export const shareJobStatus: Record<string, { label: string; tone: 'success' | 'warning' | 'error' | 'default' }> = {
|
||
queued: { label: '待解析', tone: 'warning' },
|
||
processing: { label: '解析中', tone: 'warning' },
|
||
succeeded: { label: '已加入监控', tone: 'success' },
|
||
failed: { label: '解析失败', tone: 'error' },
|
||
};
|
||
|
||
// 对齐归档版 extractShareURL / platformForShareURL
|
||
const shareURLPattern = /https?:\/\/[^\s<>"'`]+/giu;
|
||
const trailingShareURLPunctuation = /[,。;!?、)》)】\]}>,.?!:;]+$/u;
|
||
|
||
export function platformForShareURL(value: string): string {
|
||
try {
|
||
const hostname = new URL(value).hostname.toLowerCase();
|
||
if (['www.douyin.com', 'v.douyin.com'].includes(hostname)) return 'douyin';
|
||
} catch {
|
||
return '';
|
||
}
|
||
return '';
|
||
}
|
||
|
||
export function extractShareURL(value?: string): string {
|
||
const candidates = String(value || '')
|
||
.match(shareURLPattern)
|
||
?.map((candidate) => candidate.replace(trailingShareURLPunctuation, ''))
|
||
.filter(Boolean);
|
||
return candidates?.find((candidate) => platformForShareURL(candidate)) || candidates?.[0] || '';
|
||
}
|
||
|
||
// 账号可恢复性判定(对齐归档版 accountReadiness):授权、绑定、出口健康、账号暂停四层。
|
||
export const readinessReasonText: Record<string, string> = {
|
||
account_revoked: '授权已撤销',
|
||
account_paused: '账号已暂停',
|
||
binding_missing: '未绑定运行环境',
|
||
network_exit_missing: '未绑定固定出口',
|
||
network_exit_unhealthy: '固定出口不健康',
|
||
runtime_stop_pending: '运行环境停止结果待确认',
|
||
runtime_missing: '运行环境未启动',
|
||
runtime_active: '仍有活动运行实例',
|
||
account_conflict: '账号状态已变化,请刷新后重试',
|
||
environment_unavailable: '环境状态未知',
|
||
};
|
||
|
||
export interface AccountReadiness {
|
||
label: string;
|
||
reason: string;
|
||
canResume: boolean;
|
||
ready: boolean;
|
||
}
|
||
|
||
export function accountReadiness(account: any, binding: any, bindingError: any = null): AccountReadiness {
|
||
if (account.authorization_status !== 'authorized')
|
||
return { label: '授权已撤销', reason: 'account_revoked', canResume: false, ready: false };
|
||
if (bindingError) return { label: '环境状态未知', reason: 'environment_unavailable', canResume: false, ready: false };
|
||
if (!binding) return { label: '未绑定运行环境', reason: 'binding_missing', canResume: false, ready: false };
|
||
let blocked = '';
|
||
if (binding.runtime_cleanup_pending) {
|
||
blocked = 'runtime_stop_pending';
|
||
} else if (binding.network_exit_id && binding.network_exit_health !== 'healthy') {
|
||
blocked = 'network_exit_unhealthy';
|
||
}
|
||
if (blocked) return { label: readinessReasonText[blocked], reason: blocked, canResume: false, ready: false };
|
||
if (account.runtime_status === 'paused') {
|
||
const stopped = !binding.runtime_id;
|
||
return { label: stopped ? '资源就绪,可恢复' : '等待运行环境停止', reason: stopped ? '' : 'runtime_stop_pending', canResume: stopped, ready: false };
|
||
}
|
||
const schedule = binding.schedule_block_reason || '';
|
||
return {
|
||
label: schedule ? readinessReasonText[schedule] || `不可调度:${schedule}` : '可调度',
|
||
reason: schedule,
|
||
canResume: false,
|
||
ready: !schedule,
|
||
};
|
||
}
|
||
|
||
export function isCollectionAccount(account: any): boolean {
|
||
return (
|
||
account.authorization_status === 'authorized' &&
|
||
['normal', 'muted'].includes(account.business_status) &&
|
||
account.login_status === 'logged_in'
|
||
);
|
||
}
|