From 26b5e94023941f688c889f88d1de6fe450ecf976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=AB=E5=9C=B0=E5=83=A7?= <357099073@qq.com> Date: Sun, 14 Jun 2026 00:45:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/accountPool.js | 8 + src/components/CommonAside.vue | 40 ++- .../accountpool/cursor/components/detail.vue | 53 +++ src/views/accountpool/cursor/index.vue | 332 ++++++++++++++++-- vite.config.js | 1 + 5 files changed, 405 insertions(+), 29 deletions(-) diff --git a/src/api/accountPool.js b/src/api/accountPool.js index 719af51..bf874a3 100644 --- a/src/api/accountPool.js +++ b/src/api/accountPool.js @@ -59,6 +59,14 @@ export function setAccountPoolUnavailable(module, data) { }); } +export function updateAccountPoolUsable(module, data) { + return request({ + url: `${base(module)}/updateUsable`, + method: 'post', + data, + }); +} + export function updateAccountPoolPlatform(module, data) { return request({ url: `${base(module)}/updatePlatform`, diff --git a/src/components/CommonAside.vue b/src/components/CommonAside.vue index c9bfec3..8f3f19c 100644 --- a/src/components/CommonAside.vue +++ b/src/components/CommonAside.vue @@ -14,7 +14,6 @@ @@ -284,10 +284,29 @@ const currentModule = computed(() => { }); const displayMenus = computed(() => { - // 侧边栏始终展示完整菜单树,不随当前路由切换为“子菜单视图” + // 侧边栏始终展示完整菜单树,不随当前路由切换为"子菜单视图" return list.value; }); +const findOpenMenuPaths = (menus, targetPath, ancestors = []) => { + for (const menu of menus) { + const currentPath = menu.path || menu.id.toString(); + if (menu.path && (targetPath === menu.path || targetPath.startsWith(menu.path + "/"))) { + return [...ancestors, currentPath]; + } + if (menu.children && menu.children.length > 0) { + const found = findOpenMenuPaths(menu.children, targetPath, [...ancestors, currentPath]); + if (found) return found; + } + } + return null; +}; + +const defaultOpeneds = computed(() => { + const result = findOpenMenuPaths(displayMenus.value, route.path); + return result || []; +}); + const asideTitle = computed(() => { if (isCollapse.value) return "管理"; return "菜单"; @@ -311,7 +330,7 @@ const processMenus = (menus) => { .map((menu) => ({ id: menu.id, path: menu.path, - icon: menu.icon || "Document", + icon: menu.icon || null, title: menu.title, route: menu.path, component_path: menu.component_path, @@ -541,13 +560,13 @@ h3 { // 高亮样式 .el-menu-item.is-active { html:not(.dark) & { - background-color: rgba(57, 115, 255, 0.3) !important; + background-color: rgba(255, 255, 255, 0.2) !important; + border-left: 3px solid #ffffff; } html.dark & { background-color: rgba(60, 60, 60, 0.8) !important; } color: #ffffff !important; - border-left: 3px solid #4f84ff; margin-left: -3px; .menu-icon { @@ -574,12 +593,17 @@ h3 { } &.is-opened .el-sub-menu__title { - background: rgba(255, 255, 255, 0.08) !important; + background: rgba(255, 255, 255, 0.12) !important; + margin-left: -3px; } .el-menu-item { padding-left: 48px !important; font-size: 13px; + + &.is-active { + background: rgba(255, 255, 255, 0.18) !important; + } } } @@ -604,6 +628,10 @@ h3 { .el-sub-menu.is-opened .el-sub-menu__title { background: rgba(64, 158, 255, 0.08) !important; } + + .el-sub-menu .el-menu-item.is-active { + background: rgba(64, 158, 255, 0.15) !important; + } } } diff --git a/src/views/accountpool/cursor/components/detail.vue b/src/views/accountpool/cursor/components/detail.vue index 37bed82..21505ac 100644 --- a/src/views/accountpool/cursor/components/detail.vue +++ b/src/views/accountpool/cursor/components/detail.vue @@ -22,8 +22,10 @@ const remarkText = ref(""); const remarkDialogVisible = ref(false); const platformDialogVisible = ref(false); const unavailableDialogVisible = ref(false); +const usableDialogVisible = ref(false); const unextractDialogVisible = ref(false); const platformForm = reactive({ platform: "local" }); +const usableForm = reactive({ usable: 1 }); const TYPE_MAP = { account: { label: "账号密码", type: "success" }, @@ -82,6 +84,12 @@ watch( (row) => { remarkText.value = row?.remark || ""; platformForm.platform = row?.extractedPlatform || "local"; + const raw = row?.isUsed; + if (raw === null || raw === undefined || raw === "") { + usableForm.usable = 1; + } else { + usableForm.usable = Number(raw) === 0 ? 0 : 1; + } }, { immediate: true }, ); @@ -107,6 +115,26 @@ function onSetUnavailable() { unavailableDialogVisible.value = false; } +function openUsableDialog() { + const raw = props.row?.isUsed; + if (raw === null || raw === undefined || raw === "") { + usableForm.usable = 1; + } else { + usableForm.usable = Number(raw) === 0 ? 0 : 1; + } + usableDialogVisible.value = true; +} + +function onUpdateUsable() { + if (!props.row?.id) return; + emit("detail-action", { + action: "usable", + id: props.row.id, + usable: usableForm.usable, + }); + usableDialogVisible.value = false; +} + function onUpdatePlatform() { if (!props.row?.id) return; emit("detail-action", { @@ -299,6 +327,9 @@ function copyAll() {
+ + 改可用状态 + + + + + + 可用 + 不可用 + + + + + + import { computed, h, nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue"; import { ElMessage, ElMessageBox } from "element-plus"; +import { Loading } from "@element-plus/icons-vue"; import Edit from "./components/edit.vue"; import DetailDialog from "./components/detail.vue"; import ExtractDialog from "./components/extract.vue"; @@ -13,6 +14,7 @@ import { getAccountPoolList, updateAccountPoolRemark, setAccountPoolUnavailable, + updateAccountPoolUsable, updateAccountPoolPlatform, unextractAccountPool, replenishAccountPool, @@ -20,6 +22,35 @@ import { } from "@/api/accountPool"; const moduleKey = "cursor"; +const PAGINATION_STORAGE_KEY = `accountPool:${moduleKey}:pagination`; + +function loadStoredPagination() { + try { + const raw = sessionStorage.getItem(PAGINATION_STORAGE_KEY); + if (!raw) return { page: 1, pageSize: 20 }; + const saved = JSON.parse(raw); + const pageSize = Number(saved.pageSize); + const page = Number(saved.page); + return { + page: Number.isFinite(page) && page >= 1 ? page : 1, + pageSize: [20, 50, 100].includes(pageSize) ? pageSize : 20, + }; + } catch { + return { page: 1, pageSize: 20 }; + } +} + +function savePagination() { + sessionStorage.setItem( + PAGINATION_STORAGE_KEY, + JSON.stringify({ + page: pagination.page, + pageSize: pagination.pageSize, + }), + ); +} + +const storedPagination = loadStoredPagination(); const loading = ref(false); const editVisible = ref(false); @@ -58,10 +89,24 @@ const selectedRows = ref([]); const detailRow = ref(null); const detailRemarkSaving = ref(false); const probeLoadingId = ref(null); +const batchProbeDialogVisible = ref(false); +const batchProbePhase = ref("running"); +const batchProbeProgress = reactive({ + total: 0, + current: 0, + currentId: null, + percent: 0, +}); +const batchProbeSummary = reactive({ + total: 0, + available: 0, + unavailable: 0, + skipped: 0, +}); const isMobile = ref(false); const pagination = reactive({ - page: 1, - pageSize: 20, + page: storedPagination.page, + pageSize: storedPagination.pageSize, }); /** 跳转未提取末页时跳过 watcher,避免先被重置到第 1 页 */ @@ -99,6 +144,7 @@ watch( watch( () => [pagination.page, pagination.pageSize], () => { + savePagination(); if (skipWatchFetchDuringUnusedJump.value) return; fetchList(); }, @@ -272,6 +318,11 @@ async function handleDetailAction(payload) { let res; if (payload.action === "unavailable") { res = await setAccountPoolUnavailable(moduleKey, { id: payload.id }); + } else if (payload.action === "usable") { + res = await updateAccountPoolUsable(moduleKey, { + id: payload.id, + usable: payload.usable, + }); } else if (payload.action === "platform") { res = await updateAccountPoolPlatform(moduleKey, { id: payload.id, @@ -538,6 +589,10 @@ async function fetchList() { const list = Array.isArray(res?.data?.list) ? res.data.list : []; tableData.value = list.map(normalizeRow); total.value = Number(res?.data?.total || 0); + const maxPage = Math.max(1, Math.ceil(total.value / pagination.pageSize)); + if (pagination.page > maxPage) { + pagination.page = maxPage; + } } finally { loading.value = false; } @@ -683,6 +738,35 @@ function formatCursorProbeDialogText(d) { return '该TOKEN可用'; } +function formatCursorProbeDetail(d) { + if (!d) return ''; + + const parts = []; + + // 提取关键信息 + if (d.httpStatus) parts.push(`HTTP状态: ${d.httpStatus}`); + if (d.endpoint) parts.push(`接口: ${d.endpoint}`); + if (d.probeMessage) parts.push(`探测方式: ${d.probeMessage}`); + if (d.bytesRead) parts.push(`响应大小: ${d.bytesRead} 字节`); + if (d.streamProtocol) parts.push(`协议: ${d.streamProtocol}`); + + // 提取检测结论 + if (d.detail) { + // 解析流匹配信息 + const matchPrefix = '流中匹配:'; + if (d.detail.includes(matchPrefix)) { + const matchStart = d.detail.indexOf(matchPrefix); + const matchEnd = d.detail.indexOf(';', matchStart); + const matchText = matchEnd > 0 ? d.detail.substring(matchStart, matchEnd) : d.detail.substring(matchStart); + parts.push(`检测结论: ${matchText}`); + } else { + parts.push(`检测结论: ${d.detail}`); + } + } + + return parts.join('\n'); +} + async function handleProbeToken(row) { if (!row?.token) { ElMessage.warning('该行无 Token'); @@ -700,10 +784,43 @@ async function handleProbeToken(row) { } const d = res?.data || {}; const text = formatCursorProbeDialogText(d); + const isOk = d.ok === true; + + // 构建详细信息 + const detailItems = []; + if (d.httpStatus) detailItems.push(`HTTP状态: ${d.httpStatus}`); + if (d.endpoint) detailItems.push(`接口: ${d.endpoint}`); + if (d.probeMessage) detailItems.push(`探测方式: ${d.probeMessage}`); + if (d.bytesRead) detailItems.push(`响应大小: ${d.bytesRead} 字节`); + if (d.streamProtocol) detailItems.push(`协议: ${d.streamProtocol}`); + + // 提取检测结论(从 detail 字段) + if (d.detail) { + const matchPrefix = '流中匹配:'; + if (d.detail.includes(matchPrefix)) { + const matchStart = d.detail.indexOf(matchPrefix); + const matchEnd = d.detail.indexOf(';', matchStart); + const matchText = matchEnd > 0 ? d.detail.substring(matchStart, matchEnd) : d.detail.substring(matchStart); + detailItems.push(`检测结论: ${matchText}`); + } else { + detailItems.push(`检测结论: ${d.detail}`); + } + } + try { await ElMessageBox({ - title: '检测结果', - message: h('div', { class: 'cursor-probe-result' }, text), + title: isOk ? '检测结果 - 可用' : '检测结果 - 不可用', + message: h('div', { class: 'cursor-probe-result cursor-expire-result' }, [ + h('div', { + style: `text-align:center;font-size:18px;font-weight:700;margin-bottom:12px;color:${isOk ? '#67c23a' : '#f56c6c'}` + }, text), + detailItems.length > 0 ? h('div', { + style: 'text-align:left;font-size:13px;color:#606266;margin-bottom:8px;line-height:1.8' + }, detailItems.map(item => h('div', null, `• ${item}`))) : null, + d.streamNote ? h('div', { + style: 'text-align:left;font-size:12px;color:#909399;margin-top:8px;padding:8px;background:#f5f7fa;border-radius:4px;white-space:pre-wrap;line-height:1.6;' + }, d.streamNote) : null, + ]), confirmButtonText: '关闭', customClass: 'cursor-probe-dialog', closeOnClickModal: true, @@ -741,36 +858,61 @@ async function handleBatchProbe() { } catch { return; } - loading.value = true; - let ok = 0; - let fail = 0; + + let available = 0; + let unavailable = 0; + + batchProbePhase.value = "running"; + batchProbeProgress.total = rows.length; + batchProbeProgress.current = 0; + batchProbeProgress.currentId = null; + batchProbeProgress.percent = 0; + batchProbeDialogVisible.value = true; + await nextTick(); + try { - for (const row of rows) { + for (let i = 0; i < rows.length; i += 1) { + const row = rows[i]; + batchProbeProgress.current = i + 1; + batchProbeProgress.currentId = row.id; + batchProbeProgress.percent = Math.round((i / rows.length) * 100); + await nextTick(); + try { const res = await probeAccountPoolToken(moduleKey, { id: row.id, accessToken: row.token, }); - if (res?.code === 200) { - ok += 1; + if (res?.code === 200 && res?.data?.ok === true) { + available += 1; } else { - fail += 1; + unavailable += 1; } } catch { - fail += 1; + unavailable += 1; } + + batchProbeProgress.percent = Math.round(((i + 1) / rows.length) * 100); + await nextTick(); } - if (fail > 0) { - ElMessage.warning(`批量检测完成:成功 ${ok} 条,失败 ${fail} 条`); - } else { - ElMessage.success(`批量检测完成:共 ${ok} 条`); - } + + batchProbeSummary.total = rows.length; + batchProbeSummary.available = available; + batchProbeSummary.unavailable = unavailable; + batchProbeSummary.skipped = skipped; + batchProbePhase.value = "done"; await fetchList(); - } finally { - loading.value = false; + } catch { + batchProbeDialogVisible.value = false; + ElMessage.error("批量检测异常"); } } +function closeBatchProbeDialog() { + batchProbeDialogVisible.value = false; + batchProbePhase.value = "running"; +} + // async function handleBatchProbeExpireTime() { // if (!selectedRows.value.length) { // ElMessage.warning("请先选择数据"); @@ -954,13 +1096,13 @@ async function handleBatchProbe() { - + + +
+
+ + + +
+ +
+ 正在检测第 {{ batchProbeProgress.current }} / {{ batchProbeProgress.total }} 条 +
+
+ 当前 ID:{{ batchProbeProgress.currentId }} +
+
+
+
检测完成
+
+
+ 共检测 + {{ batchProbeSummary.total }} 条 +
+
+ 可用 + {{ batchProbeSummary.available }} 条 +
+
+ 失效 + {{ batchProbeSummary.unavailable }} 条 +
+
+
+ 已跳过无 Token {{ batchProbeSummary.skipped }} 条 +
+
+ +
+