Files
creator-hub/web/src/utils/helpers.ts
T
rogee 7dea052e55 feat(web): 账号即环境 UI 收敛——删 /browsers 页面,环境并入账号详情
- 删除 /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 通过
2026-09-28 20:30:23 +08:00

106 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 公共展示工具:字段语义逐一对照 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'
);
}