决策 M6-1–M6-10(docs/architecture/m6-plan.md、docs/decisions.md)。 用户端: - 额度与套餐页:本期额度、按功能分布、扣点规则、流水(逐条余额、关联项目和任务、导出 CSV) - 开通 / 续费申请(对公转账,运营确认到账后开通),80% / 100% 提醒 - 专业版到期:整个账号只读、额度清零,到期前 30 / 7 / 0 天提醒(worker 每小时 billing_tick) - 删除项目(彻底删除、销毁数据密钥),注销账号(企业名称 + 短信验证码) - 通知铃铛、账号页 运营后台: - 独立进程 bid-doctor ops(/ops/v1)和 apps/admin;用户名 + 密码 + TOTP + IP 白名单 - 开通申请(到账 / 开通 / 退回 / 发票)、企业查询和调额度、误报 / 漏报、失败任务、审计;不看业务内容 - 左侧菜单(标准 logo、可展开收起的分组、图标、待办数、环境标签) - 运营人员角色只留结构:ops_admin.role、权限点、每个接口的检查点、菜单按权限过滤 数据库:迁移 0010(排队的订阅、申请单号、通知类型、billing_due)、0011(bd_ops 权限)、0012(运营角色)。 另修:取消解析 / 检查接口报 500;uuid7 进程内单调;data_key 销毁后不再新建;审计 None 存 SQL NULL。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
49 lines
2.3 KiB
Vue
49 lines
2.3 KiB
Vue
<script setup lang="ts">
|
||
/** 失败任务:解析、检查、报告。错误类别、错误码、阶段、耗时、模型调用次数;不含文档内容。失败的都已自动退回额度(红线 9)。 */
|
||
import { onMounted, ref } from "vue";
|
||
import { RouterLink } from "vue-router";
|
||
import { fmt, JOB_KIND, ops, OpsError, type FailedJob } from "../api";
|
||
|
||
const items = ref<FailedJob[]>([]);
|
||
const err = ref("");
|
||
onMounted(async () => {
|
||
try {
|
||
items.value = (await ops<{ items: FailedJob[] }>("/failed-jobs")).items;
|
||
} catch (e) {
|
||
err.value = e instanceof OpsError ? e.message : "读取失败";
|
||
}
|
||
});
|
||
const CLASS: Record<string, string> = { input: "文件问题", infra: "系统异常", cancelled: "取消" };
|
||
</script>
|
||
|
||
<template>
|
||
<div class="page-title">
|
||
<div>
|
||
<h1>失败任务</h1>
|
||
<p>失败的任务都已自动退回额度。系统异常(infra)要排查,文件问题(input)一般是用户的文件读不了。</p>
|
||
</div>
|
||
</div>
|
||
<p v-if="err" class="hint-warn" role="alert"><span class="ico w" aria-hidden="true">!</span>{{ err }}</p>
|
||
<section class="card flush">
|
||
<table class="data">
|
||
<thead><tr><th>时间</th><th>任务</th><th>企业</th><th>阶段</th><th>错误</th><th class="r">耗时</th><th class="r">模型调用</th><th>编号</th></tr></thead>
|
||
<tbody>
|
||
<tr v-for="j in items" :key="j.id">
|
||
<td class="num nowrap">{{ fmt(j.created_at) }}</td>
|
||
<td>{{ JOB_KIND[j.kind] ?? j.kind }}</td>
|
||
<td><RouterLink class="lnk" :to="{ name: 'tenant', params: { id: j.tenant.id } }">{{ j.tenant.name }}</RouterLink></td>
|
||
<td>{{ j.stage ?? "—" }}</td>
|
||
<td>
|
||
<span :class="{ 'err-text': j.error_class === 'infra' }">{{ CLASS[j.error_class ?? ""] ?? j.error_class ?? "" }}</span> {{ j.error_code ?? "" }}
|
||
<p v-if="j.error_detail" class="inv">{{ j.error_detail }}</p>
|
||
</td>
|
||
<td class="num r">{{ j.duration_s != null ? `${Math.round(j.duration_s)} 秒` : "—" }}</td>
|
||
<td class="num r">{{ j.ai_calls }}</td>
|
||
<td class="num cap muted">{{ j.id.slice(0, 8) }}</td>
|
||
</tr>
|
||
<tr v-if="!items.length"><td colspan="8" class="muted">没有失败的任务</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
</template>
|