增加token检测
This commit is contained in:
parent
6170d5a619
commit
e05ac23cc3
@ -58,3 +58,12 @@ export function replenishAccountPool(module, data) {
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/** 使用厂商 Token 探测是否可用(服务端转发)。Cursor 传 { id, accessToken }(会话 JWT)以便回写 is_used;仅传 accessToken 也可探测但不更新库;Windsurf/Kiro 传 { id } */
|
||||
export function probeAccountPoolToken(module, data) {
|
||||
return request({
|
||||
url: `${base(module)}/probeToken`,
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
@ -104,6 +104,17 @@ function copyToken() {
|
||||
row.extractStatus === 2 ? '补号' : row.extracted ? '已提取' : '未提取'
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="探测可用">
|
||||
{{
|
||||
row.isUsed === null || row.isUsed === undefined
|
||||
? '未探测'
|
||||
: Number(row.isUsed) === 1
|
||||
? '可用'
|
||||
: Number(row.isUsed) === 0
|
||||
? '已用完'
|
||||
: String(row.isUsed)
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="提取时间">{{ row.extractedAt || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提取平台">
|
||||
<el-tag v-if="row.extractedPlatform" :type="platformType" size="small">
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { computed, h, nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import Edit from "./components/edit.vue";
|
||||
import DetailDialog from "./components/detail.vue";
|
||||
import ExtractDialog from "./components/extract.vue";
|
||||
@ -13,6 +13,7 @@ import {
|
||||
getAccountPoolList,
|
||||
updateAccountPoolRemark,
|
||||
replenishAccountPool,
|
||||
probeAccountPoolToken,
|
||||
} from "@/api/accountPool";
|
||||
|
||||
const moduleKey = "cursor";
|
||||
@ -33,6 +34,7 @@ const patchVisible = ref(false);
|
||||
const query = reactive({
|
||||
keyword: "",
|
||||
status: "",
|
||||
usable: "",
|
||||
});
|
||||
const activeTypeTab = ref("all");
|
||||
|
||||
@ -49,6 +51,7 @@ const total = ref(0);
|
||||
const selectedRows = ref([]);
|
||||
const detailRow = ref(null);
|
||||
const detailRemarkSaving = ref(false);
|
||||
const probeLoadingId = ref(null);
|
||||
const isMobile = ref(false);
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
@ -63,6 +66,7 @@ const pagedList = computed(() => tableData.value);
|
||||
function resetQuery() {
|
||||
query.keyword = "";
|
||||
query.status = "";
|
||||
query.usable = "";
|
||||
}
|
||||
|
||||
const typeTabs = computed(() => {
|
||||
@ -75,7 +79,7 @@ const typeTabs = computed(() => {
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [query.keyword, query.status, activeTypeTab.value],
|
||||
() => [query.keyword, query.status, query.usable, activeTypeTab.value],
|
||||
() => {
|
||||
if (skipWatchFetchDuringUnusedJump.value) return;
|
||||
pagination.page = 1;
|
||||
@ -348,6 +352,21 @@ function platformTagType(platform) {
|
||||
return PLATFORM_MAP[platform]?.type || "info";
|
||||
}
|
||||
|
||||
function isUsedLabel(isUsed) {
|
||||
if (isUsed === null || isUsed === undefined) return "未探测";
|
||||
const n = Number(isUsed);
|
||||
if (n === 1) return "可用";
|
||||
if (n === 0) return "已用完";
|
||||
return String(isUsed);
|
||||
}
|
||||
|
||||
function isUsedTagType(isUsed) {
|
||||
const n = Number(isUsed);
|
||||
if (n === 1) return "success";
|
||||
if (n === 0) return "danger";
|
||||
return "info";
|
||||
}
|
||||
|
||||
function normalizeRow(raw) {
|
||||
const pick = (...keys) => {
|
||||
for (const key of keys) {
|
||||
@ -371,6 +390,11 @@ function normalizeRow(raw) {
|
||||
};
|
||||
const st = Number(pick("is_extracted", "isExtracted", "IsExtracted"));
|
||||
const extractStatus = Number.isFinite(st) ? st : 0;
|
||||
const isUsedRaw = pickNullable("is_used", "isUsed", "IsUsed");
|
||||
const isUsedNum =
|
||||
isUsedRaw === null || isUsedRaw === undefined || isUsedRaw === ""
|
||||
? null
|
||||
: Number(isUsedRaw);
|
||||
return {
|
||||
id: pick("id", "Id", "ID"),
|
||||
type: pick("data_type", "dataType", "type"),
|
||||
@ -380,6 +404,7 @@ function normalizeRow(raw) {
|
||||
remark: pick("remark", "Remark"),
|
||||
extractStatus,
|
||||
extracted: extractStatus !== 0,
|
||||
isUsed: Number.isFinite(isUsedNum) ? isUsedNum : null,
|
||||
extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")),
|
||||
extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"),
|
||||
createdAt: formatTime(pick("create_time", "createdAt")),
|
||||
@ -394,6 +419,7 @@ async function fetchList() {
|
||||
pageSize: pagination.pageSize,
|
||||
keyword: query.keyword || undefined,
|
||||
status: query.status || undefined,
|
||||
usable: query.usable === "1" || query.usable === "0" ? query.usable : undefined,
|
||||
type: activeTypeTab.value === "all" ? undefined : activeTypeTab.value,
|
||||
});
|
||||
if (res?.code !== 200) {
|
||||
@ -416,6 +442,7 @@ async function jumpToLastUnusedPage() {
|
||||
pageSize: pagination.pageSize,
|
||||
keyword: query.keyword || undefined,
|
||||
status: "unused",
|
||||
usable: query.usable === "1" || query.usable === "0" ? query.usable : undefined,
|
||||
type,
|
||||
});
|
||||
if (res?.code !== 200) {
|
||||
@ -525,6 +552,105 @@ function copyCardInfo(row) {
|
||||
});
|
||||
}
|
||||
|
||||
const CURSOR_PRO_LIMIT_TEXT = 'Get Cursor Pro for more Agent usage, unlimited Tab, and more.';
|
||||
|
||||
function formatCursorProbeDialogText(d) {
|
||||
const detail = String(d?.detail || '');
|
||||
const rawPreview = String(d?.rawPreview || '');
|
||||
if (detail.includes(CURSOR_PRO_LIMIT_TEXT) || rawPreview.includes(CURSOR_PRO_LIMIT_TEXT)) {
|
||||
return '该TOKEN已用完';
|
||||
}
|
||||
return '该TOKEN可用';
|
||||
}
|
||||
|
||||
async function handleProbeToken(row) {
|
||||
if (!row?.token) {
|
||||
ElMessage.warning('该行无 Token');
|
||||
return;
|
||||
}
|
||||
probeLoadingId.value = row.id;
|
||||
try {
|
||||
const res = await probeAccountPoolToken(moduleKey, {
|
||||
id: row.id,
|
||||
accessToken: row.token,
|
||||
});
|
||||
if (res?.code !== 200) {
|
||||
ElMessage.error(res?.msg || '探测失败');
|
||||
return;
|
||||
}
|
||||
const d = res?.data || {};
|
||||
const text = formatCursorProbeDialogText(d);
|
||||
try {
|
||||
await ElMessageBox({
|
||||
title: '检测结果',
|
||||
message: h('div', { class: 'cursor-probe-result' }, text),
|
||||
confirmButtonText: '关闭',
|
||||
customClass: 'cursor-probe-dialog',
|
||||
closeOnClickModal: true,
|
||||
});
|
||||
} catch {
|
||||
/* 用户关闭弹窗 */
|
||||
}
|
||||
await fetchList();
|
||||
} catch {
|
||||
ElMessage.error('探测请求失败');
|
||||
} finally {
|
||||
probeLoadingId.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleBatchProbe() {
|
||||
if (!selectedRows.value.length) {
|
||||
ElMessage.warning("请先选择数据");
|
||||
return;
|
||||
}
|
||||
const rows = selectedRows.value.filter((r) => r?.token);
|
||||
if (!rows.length) {
|
||||
ElMessage.warning("所选行均无 Token,无法检测");
|
||||
return;
|
||||
}
|
||||
const skipped = selectedRows.value.length - rows.length;
|
||||
const skipHint =
|
||||
skipped > 0 ? `(已跳过 ${skipped} 条无 Token)` : "";
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`将对 ${rows.length} 条 Token进行检测,是否继续?`,
|
||||
"批量检测",
|
||||
{ type: "info", confirmButtonText: "开始", cancelButtonText: "取消" },
|
||||
);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
let ok = 0;
|
||||
let fail = 0;
|
||||
try {
|
||||
for (const row of rows) {
|
||||
try {
|
||||
const res = await probeAccountPoolToken(moduleKey, {
|
||||
id: row.id,
|
||||
accessToken: row.token,
|
||||
});
|
||||
if (res?.code === 200) {
|
||||
ok += 1;
|
||||
} else {
|
||||
fail += 1;
|
||||
}
|
||||
} catch {
|
||||
fail += 1;
|
||||
}
|
||||
}
|
||||
if (fail > 0) {
|
||||
ElMessage.warning(`批量检测完成:成功 ${ok} 条,失败 ${fail} 条`);
|
||||
} else {
|
||||
ElMessage.success(`批量检测完成:共 ${ok} 条`);
|
||||
}
|
||||
await fetchList();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -553,6 +679,15 @@ function copyCardInfo(row) {
|
||||
<el-option label="未提取" value="unused" />
|
||||
<el-option label="已提取" value="extracted" />
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="query.usable"
|
||||
placeholder="探测可用"
|
||||
clearable
|
||||
class="w-140"
|
||||
>
|
||||
<el-option label="可用" value="1" />
|
||||
<el-option label="已用完" value="0" />
|
||||
</el-select>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@ -568,6 +703,7 @@ function copyCardInfo(row) {
|
||||
<el-button type="success" @click="openAddDialog('batch')">批量添加</el-button>
|
||||
<el-button type="warning" @click="replenishVisible = true">补号</el-button>
|
||||
<el-button @click="markExtractForSelected">批量标记提取</el-button>
|
||||
<el-button type="info" plain @click="handleBatchProbe">批量检测</el-button>
|
||||
<el-button @click="apiDocVisible = true">接口说明</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -613,6 +749,13 @@ function copyCardInfo(row) {
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="探测可用" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="isUsedTagType(row.isUsed)" size="small">
|
||||
{{ isUsedLabel(row.isUsed) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="extractedAt" label="提取时间" width="180" />
|
||||
<el-table-column label="提取平台" width="120">
|
||||
<template #default="{ row }">
|
||||
@ -626,8 +769,16 @@ function copyCardInfo(row) {
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||
<el-table-column label="操作" width="300" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="row.token"
|
||||
link
|
||||
type="info"
|
||||
:loading="probeLoadingId === row.id"
|
||||
@click="handleProbeToken(row)"
|
||||
>检测</el-button
|
||||
>
|
||||
<el-button link type="primary" @click="openDetail(row)"
|
||||
>详情</el-button
|
||||
>
|
||||
@ -999,4 +1150,17 @@ function copyCardInfo(row) {
|
||||
.pool-tooltip .el-popper__arrow {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Cursor 探测结果弹窗(teleport 到 body,需非 scoped) */
|
||||
.cursor-probe-dialog .el-message-box__message {
|
||||
padding: 12px 8px 4px;
|
||||
}
|
||||
.cursor-probe-dialog .cursor-probe-result {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
getAccountPoolList,
|
||||
updateAccountPoolRemark,
|
||||
replenishAccountPool,
|
||||
probeAccountPoolToken,
|
||||
} from '@/api/accountPool';
|
||||
|
||||
const moduleKey = "krio";
|
||||
@ -41,6 +42,7 @@ const total = ref(0);
|
||||
const selectedRows = ref([]);
|
||||
const detailRow = ref(null);
|
||||
const detailRemarkSaving = ref(false);
|
||||
const probeLoadingId = ref(null);
|
||||
const isMobile = ref(false);
|
||||
const pagination = reactive({ page: 1, pageSize: 30 });
|
||||
|
||||
@ -454,6 +456,31 @@ function copyCardInfo(row) {
|
||||
navigator.clipboard.writeText(parts.join('\n')).then(() => { ElMessage.success('已复制'); });
|
||||
}
|
||||
|
||||
async function handleProbeToken(row) {
|
||||
if (!row?.token) {
|
||||
ElMessage.warning('该行无 Token');
|
||||
return;
|
||||
}
|
||||
probeLoadingId.value = row.id;
|
||||
try {
|
||||
const res = await probeAccountPoolToken(moduleKey, { id: row.id });
|
||||
if (res?.code !== 200) {
|
||||
ElMessage.error(res?.msg || '探测失败');
|
||||
return;
|
||||
}
|
||||
const d = res?.data || {};
|
||||
if (d.ok) {
|
||||
ElMessage.success(d.detail || '官方接口响应正常');
|
||||
} else {
|
||||
ElMessage.error(d.detail || '不可用或校验失败');
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('探测请求失败');
|
||||
} finally {
|
||||
probeLoadingId.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -543,11 +570,19 @@ function copyCardInfo(row) {
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||
<el-table-column label="操作" width="300" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openDetail(row)"
|
||||
>详情</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="row.token"
|
||||
link
|
||||
type="info"
|
||||
:loading="probeLoadingId === row.id"
|
||||
@click="handleProbeToken(row)"
|
||||
>查可用</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="!row.extractedAt && !row.extracted"
|
||||
link
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
getAccountPoolList,
|
||||
updateAccountPoolRemark,
|
||||
replenishAccountPool,
|
||||
probeAccountPoolToken,
|
||||
} from '@/api/accountPool';
|
||||
|
||||
const moduleKey = "windsurf";
|
||||
@ -40,6 +41,7 @@ const total = ref(0);
|
||||
const selectedRows = ref([]);
|
||||
const detailRow = ref(null);
|
||||
const detailRemarkSaving = ref(false);
|
||||
const probeLoadingId = ref(null);
|
||||
const isMobile = ref(false);
|
||||
const pagination = reactive({ page: 1, pageSize: 30 });
|
||||
|
||||
@ -453,6 +455,31 @@ function copyCardInfo(row) {
|
||||
navigator.clipboard.writeText(parts.join('\n')).then(() => { ElMessage.success('已复制'); });
|
||||
}
|
||||
|
||||
async function handleProbeToken(row) {
|
||||
if (!row?.token) {
|
||||
ElMessage.warning('该行无 Token');
|
||||
return;
|
||||
}
|
||||
probeLoadingId.value = row.id;
|
||||
try {
|
||||
const res = await probeAccountPoolToken(moduleKey, { id: row.id });
|
||||
if (res?.code !== 200) {
|
||||
ElMessage.error(res?.msg || '探测失败');
|
||||
return;
|
||||
}
|
||||
const d = res?.data || {};
|
||||
if (d.ok) {
|
||||
ElMessage.success(d.detail || '官方接口响应正常');
|
||||
} else {
|
||||
ElMessage.error(d.detail || '不可用或校验失败');
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('探测请求失败');
|
||||
} finally {
|
||||
probeLoadingId.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -542,11 +569,19 @@ function copyCardInfo(row) {
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||
<el-table-column label="操作" width="300" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openDetail(row)"
|
||||
>详情</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="row.token"
|
||||
link
|
||||
type="info"
|
||||
:loading="probeLoadingId === row.id"
|
||||
@click="handleProbeToken(row)"
|
||||
>查可用</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="!row.extractedAt && !row.extracted"
|
||||
link
|
||||
|
||||
Loading…
Reference in New Issue
Block a user