diff --git a/src/api/home.js b/src/api/home.js new file mode 100644 index 0000000..dd3049d --- /dev/null +++ b/src/api/home.js @@ -0,0 +1,13 @@ +import request from '@/utils/request'; + +/** + * 按天统计号池已提取(售卖)数量,依据 extracted_time + * @param {{ days?: number }} params days 默认 14,最大 90 + */ +export function getAccountPoolDailyExtract(params) { + return request({ + url: '/platform/home/accountPoolDailyExtract', + method: 'get', + params, + }); +} diff --git a/src/views/accountpool/components/patch.vue b/src/views/accountpool/components/patch.vue index d976505..a6bdcd1 100644 --- a/src/views/accountpool/components/patch.vue +++ b/src/views/accountpool/components/patch.vue @@ -54,6 +54,8 @@ function normalizeRow(raw) { const p = (v) => String(v).padStart(2, '0'); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`; }; + const st = Number(pick('is_extracted', 'isExtracted', 'IsExtracted')); + const extractStatus = Number.isFinite(st) ? st : 0; return { id: pick('id', 'Id', 'ID'), type: pick('data_type', 'dataType', 'type'), @@ -61,7 +63,8 @@ function normalizeRow(raw) { password: pick('password', 'Password'), token: pick('token', 'Token'), remark: pick('remark', 'Remark'), - extracted: Number(pick('is_extracted', 'isExtracted', 'IsExtracted')) === 1, + extractStatus, + extracted: extractStatus !== 0, extractedAt: formatTime(pickNullable('extracted_time', 'extractedAt')), extractedPlatform: pickNullable('extracted_platform', 'extractedPlatform'), createdAt: formatTime(pick('create_time', 'createdAt')), diff --git a/src/views/accountpool/cursor/components/detail.vue b/src/views/accountpool/cursor/components/detail.vue index 8fefb54..f99d4b5 100644 --- a/src/views/accountpool/cursor/components/detail.vue +++ b/src/views/accountpool/cursor/components/detail.vue @@ -100,7 +100,9 @@ function copyToken() { {{ row.token || '-' }} - {{ row.extracted ? '已提取' : '未提取' }} + {{ + row.extractStatus === 2 ? '补号' : row.extracted ? '已提取' : '未提取' + }} {{ row.extractedAt || '-' }} diff --git a/src/views/accountpool/cursor/components/extract.vue b/src/views/accountpool/cursor/components/extract.vue index bbf1d45..973d7e6 100644 --- a/src/views/accountpool/cursor/components/extract.vue +++ b/src/views/accountpool/cursor/components/extract.vue @@ -20,13 +20,23 @@ const props = defineProps({ type: String, default: '', }, + replenish: { + type: Boolean, + default: false, + }, platformMap: { type: Object, default: () => ({}), }, }); -const emit = defineEmits(['update:modelValue', 'update:platform', 'update:remark', 'confirm']); +const emit = defineEmits([ + 'update:modelValue', + 'update:platform', + 'update:remark', + 'update:replenish', + 'confirm', +]); function typeText(type) { if (type === 'account') return '账号密码'; @@ -47,6 +57,14 @@ function typeText(type) { + + + -import { computed, onMounted, onUnmounted, reactive, ref, watch } from "vue"; +import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue"; import { ElMessage } from "element-plus"; import Edit from "./components/edit.vue"; import DetailDialog from "./components/detail.vue"; @@ -40,6 +40,7 @@ const extractForm = reactive({ platform: "local", type: "account", remark: "", + replenish: false, }); const tableData = ref([]); @@ -54,6 +55,9 @@ const pagination = reactive({ pageSize: 20, }); +/** 跳转未提取末页时跳过 watcher,避免先被重置到第 1 页 */ +const skipWatchFetchDuringUnusedJump = ref(false); + const pagedList = computed(() => tableData.value); function resetQuery() { @@ -73,6 +77,7 @@ const typeTabs = computed(() => { watch( () => [query.keyword, query.status, activeTypeTab.value], () => { + if (skipWatchFetchDuringUnusedJump.value) return; pagination.page = 1; fetchList(); }, @@ -81,6 +86,7 @@ watch( watch( () => [pagination.page, pagination.pageSize], () => { + if (skipWatchFetchDuringUnusedJump.value) return; fetchList(); }, ); @@ -142,6 +148,7 @@ function openExtractDialog() { extractTargetRow.value = null; extractForm.platform = "local"; extractForm.type = "account"; + extractForm.replenish = false; extractVisible.value = true; } @@ -150,6 +157,7 @@ function openExtractByRow(row) { extractForm.platform = "local"; extractForm.type = row.type; extractForm.remark = row.remark || ""; + extractForm.replenish = false; extractVisible.value = true; } @@ -165,6 +173,14 @@ function buildCopyTextByRow(row) { return parts.join('\n'); } +function rowToText(row) { + const parts = []; + if (row?.account) parts.push(row.account); + if (row?.password) parts.push(row.password); + if (row?.token) parts.push(row.token); + return parts.join(' / '); +} + async function copyToClipboard(text) { if (!text) { ElMessage.warning('无可复制内容'); @@ -193,6 +209,7 @@ async function handleExtract() { type: target.type, platform: extractForm.platform, remark: extractForm.remark || "", + replenish: !!extractForm.replenish, }); if (res?.code !== 200) { ElMessage.error(res?.msg || "提取失败"); @@ -270,12 +287,46 @@ async function handleBatchExtract() { } } +async function handleReplenish() { + loading.value = true; + try { + const res = await replenishAccountPool(moduleKey, { + type: replenishForm.type, + platform: replenishForm.platform, + remark: replenishForm.remark || "", + }); + if (res?.code !== 200) { + ElMessage.error(res?.msg || "补号失败"); + return; + } + ElMessage.success("补号成功,已复制到剪贴板"); + replenishVisible.value = false; + const row = normalizeRow(res.data || {}); + navigator.clipboard.writeText(rowToText(row)).catch(() => {}); + await fetchList(); + } finally { + loading.value = false; + } +} + function typeText(type) { if (type === "account") return "账号密码"; if (type === "account_tk") return "账号密码+Token"; return "Token"; } +function extractStatusLabel(row) { + if (row?.extractStatus === 2) return "补号"; + if (row?.extracted) return "已提取"; + return "未提取"; +} + +function extractStatusTagType(row) { + if (row?.extractStatus === 2) return "warning"; + if (row?.extracted) return "success"; + return "info"; +} + const tooltipOpts = { popperClass: 'pool-tooltip', popperStyle: { maxWidth: '600px', wordBreak: 'break-all', whiteSpace: 'pre-wrap' }, @@ -318,6 +369,8 @@ function normalizeRow(raw) { const p = (v) => String(v).padStart(2, "0"); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`; }; + const st = Number(pick("is_extracted", "isExtracted", "IsExtracted")); + const extractStatus = Number.isFinite(st) ? st : 0; return { id: pick("id", "Id", "ID"), type: pick("data_type", "dataType", "type"), @@ -325,7 +378,8 @@ function normalizeRow(raw) { password: pick("password", "Password"), token: pick("token", "Token"), remark: pick("remark", "Remark"), - extracted: Number(pick("is_extracted", "isExtracted", "IsExtracted")) === 1, + extractStatus, + extracted: extractStatus !== 0, extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")), extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"), createdAt: formatTime(pick("create_time", "createdAt")), @@ -354,6 +408,35 @@ async function fetchList() { } } +/** 筛选「未提取」并翻到该条件下的最后一页(当前关键词、账号类型 tab 不变) */ +async function jumpToLastUnusedPage() { + const type = activeTypeTab.value === "all" ? undefined : activeTypeTab.value; + const res = await getAccountPoolList(moduleKey, { + page: 1, + pageSize: pagination.pageSize, + keyword: query.keyword || undefined, + status: "unused", + type, + }); + if (res?.code !== 200) { + ElMessage.error(res?.msg || "获取列表失败"); + return; + } + const cnt = Number(res?.data?.total || 0); + if (cnt === 0) { + ElMessage.warning("暂无未提取数据"); + return; + } + const lastPage = Math.max(1, Math.ceil(cnt / pagination.pageSize)); + skipWatchFetchDuringUnusedJump.value = true; + pagination.page = lastPage; + query.status = "unused"; + await nextTick(); + skipWatchFetchDuringUnusedJump.value = false; + await fetchList(); + ElMessage.success(`已跳转未提取第 ${lastPage} 页(共 ${cnt} 条)`); +} + function updateDeviceType() { isMobile.value = window.innerWidth <= 768; } @@ -470,6 +553,14 @@ function copyCardInfo(row) { + + 未提取末页 + 重置
@@ -517,8 +608,8 @@ function copyCardInfo(row) { @@ -541,9 +632,9 @@ function copyCardInfo(row) { >详情 提取 @@ -586,9 +677,11 @@ function copyCardInfo(row) { :type="extractForm.type" :platform="extractForm.platform" :remark="extractForm.remark" + :replenish="extractForm.replenish" :platform-map="PLATFORM_MAP" @update:platform="(v) => (extractForm.platform = v)" @update:remark="(v) => (extractForm.remark = v)" + @update:replenish="(v) => (extractForm.replenish = v)" @confirm="handleExtract" /> diff --git a/src/views/accountpool/kiro/components/detail.vue b/src/views/accountpool/kiro/components/detail.vue index d2ad3fe..1874d14 100644 --- a/src/views/accountpool/kiro/components/detail.vue +++ b/src/views/accountpool/kiro/components/detail.vue @@ -90,7 +90,9 @@ function copyToken() { {{ row.token || '-' }} - {{ row.extracted ? '已提取' : '未提取' }} + {{ + row.extractStatus === 2 ? '补号' : row.extracted ? '已提取' : '未提取' + }} {{ row.extractedAt || '-' }} {{ platformLabel }} diff --git a/src/views/accountpool/kiro/components/extract.vue b/src/views/accountpool/kiro/components/extract.vue index ac9b7fe..476efb2 100644 --- a/src/views/accountpool/kiro/components/extract.vue +++ b/src/views/accountpool/kiro/components/extract.vue @@ -5,10 +5,17 @@ const props = defineProps({ type: { type: String, default: 'account' }, platform: { type: String, default: 'local' }, remark: { type: String, default: '' }, + replenish: { type: Boolean, default: false }, platformMap: { type: Object, default: () => ({}) }, }); -const emit = defineEmits(['update:modelValue', 'update:platform', 'update:remark', 'confirm']); +const emit = defineEmits([ + 'update:modelValue', + 'update:platform', + 'update:remark', + 'update:replenish', + 'confirm', +]); function typeText(type) { if (type === 'account') return '账号密码'; @@ -29,6 +36,14 @@ function typeText(type) { + + + -import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'; +import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue'; import { ElMessage } from 'element-plus'; import Edit from './components/edit.vue'; import DetailDialog from './components/detail.vue'; @@ -34,7 +34,7 @@ const patchVisible = ref(false); const query = reactive({ keyword: "", status: "" }); const activeTypeTab = ref("all"); -const extractForm = reactive({ platform: 'local', type: 'account', remark: '' }); +const extractForm = reactive({ platform: 'local', type: 'account', remark: '', replenish: false }); const tableData = ref([]); const total = ref(0); @@ -44,6 +44,8 @@ const detailRemarkSaving = ref(false); const isMobile = ref(false); const pagination = reactive({ page: 1, pageSize: 30 }); +const skipWatchFetchDuringUnusedJump = ref(false); + const pagedList = computed(() => tableData.value); function resetQuery() { @@ -61,6 +63,7 @@ const typeTabs = computed(() => [ watch( () => [query.keyword, query.status, activeTypeTab.value], () => { + if (skipWatchFetchDuringUnusedJump.value) return; pagination.page = 1; fetchList(); }, @@ -68,6 +71,7 @@ watch( watch( () => [pagination.page, pagination.pageSize], () => { + if (skipWatchFetchDuringUnusedJump.value) return; fetchList(); }, ); @@ -124,6 +128,7 @@ function openExtractByRow(row) { extractForm.platform = "local"; extractForm.type = row.type; extractForm.remark = row.remark || ''; + extractForm.replenish = false; extractVisible.value = true; } @@ -157,7 +162,11 @@ async function handleExtract() { const target = extractTargetRow.value; if (!target) { ElMessage.warning("未找到提取目标"); return; } const res = await extractAccountPool(moduleKey, { - id: target.id, type: target.type, platform: extractForm.platform, remark: extractForm.remark || '', + id: target.id, + type: target.type, + platform: extractForm.platform, + remark: extractForm.remark || '', + replenish: !!extractForm.replenish, }); if (res?.code !== 200) { ElMessage.error(res?.msg || "提取失败"); return; } ElMessage.success("提取成功"); @@ -294,6 +303,8 @@ function normalizeRow(raw) { const p = (v) => String(v).padStart(2, "0"); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`; }; + const st = Number(pick("is_extracted", "isExtracted", "IsExtracted")); + const extractStatus = Number.isFinite(st) ? st : 0; return { id: pick("id", "Id", "ID"), type: pick("data_type", "dataType", "type"), @@ -301,13 +312,26 @@ function normalizeRow(raw) { password: pick("password", "Password"), token: pick("token", "Token"), remark: pick("remark", "Remark"), - extracted: Number(pick("is_extracted", "isExtracted", "IsExtracted")) === 1, + extractStatus, + extracted: extractStatus !== 0, extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")), extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"), createdAt: formatTime(pick("create_time", "createdAt")), }; } +function extractStatusLabel(row) { + if (row?.extractStatus === 2) return "补号"; + if (row?.extracted) return "已提取"; + return "未提取"; +} + +function extractStatusTagType(row) { + if (row?.extractStatus === 2) return "warning"; + if (row?.extracted) return "success"; + return "info"; +} + async function fetchList() { loading.value = true; try { @@ -330,6 +354,34 @@ async function fetchList() { } } +async function jumpToLastUnusedPage() { + const type = activeTypeTab.value === "all" ? undefined : activeTypeTab.value; + const res = await getAccountPoolList(moduleKey, { + page: 1, + pageSize: pagination.pageSize, + keyword: query.keyword || undefined, + status: "unused", + type, + }); + if (res?.code !== 200) { + ElMessage.error(res?.msg || "获取列表失败"); + return; + } + const cnt = Number(res?.data?.total || 0); + if (cnt === 0) { + ElMessage.warning("暂无未提取数据"); + return; + } + const lastPage = Math.max(1, Math.ceil(cnt / pagination.pageSize)); + skipWatchFetchDuringUnusedJump.value = true; + pagination.page = lastPage; + query.status = "unused"; + await nextTick(); + skipWatchFetchDuringUnusedJump.value = false; + await fetchList(); + ElMessage.success(`已跳转未提取第 ${lastPage} 页(共 ${cnt} 条)`); +} + onMounted(() => { fetchList(); }); // ---- 接口说明数据 ---- @@ -428,6 +480,14 @@ function copyCardInfo(row) { + + 未提取末页 + 重置
@@ -465,8 +525,8 @@ function copyCardInfo(row) { @@ -489,9 +549,9 @@ function copyCardInfo(row) { >详情 提取 @@ -534,9 +594,11 @@ function copyCardInfo(row) { :type="extractForm.type" :platform="extractForm.platform" :remark="extractForm.remark" + :replenish="extractForm.replenish" :platform-map="PLATFORM_MAP" @update:platform="(v) => (extractForm.platform = v)" @update:remark="(v) => (extractForm.remark = v)" + @update:replenish="(v) => (extractForm.replenish = v)" @confirm="handleExtract" /> diff --git a/src/views/accountpool/windsurf/components/detail.vue b/src/views/accountpool/windsurf/components/detail.vue index d2ad3fe..1874d14 100644 --- a/src/views/accountpool/windsurf/components/detail.vue +++ b/src/views/accountpool/windsurf/components/detail.vue @@ -90,7 +90,9 @@ function copyToken() { {{ row.token || '-' }} - {{ row.extracted ? '已提取' : '未提取' }} + {{ + row.extractStatus === 2 ? '补号' : row.extracted ? '已提取' : '未提取' + }} {{ row.extractedAt || '-' }} {{ platformLabel }} diff --git a/src/views/accountpool/windsurf/components/extract.vue b/src/views/accountpool/windsurf/components/extract.vue index ac9b7fe..476efb2 100644 --- a/src/views/accountpool/windsurf/components/extract.vue +++ b/src/views/accountpool/windsurf/components/extract.vue @@ -5,10 +5,17 @@ const props = defineProps({ type: { type: String, default: 'account' }, platform: { type: String, default: 'local' }, remark: { type: String, default: '' }, + replenish: { type: Boolean, default: false }, platformMap: { type: Object, default: () => ({}) }, }); -const emit = defineEmits(['update:modelValue', 'update:platform', 'update:remark', 'confirm']); +const emit = defineEmits([ + 'update:modelValue', + 'update:platform', + 'update:remark', + 'update:replenish', + 'confirm', +]); function typeText(type) { if (type === 'account') return '账号密码'; @@ -29,6 +36,14 @@ function typeText(type) { + + + -import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'; +import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue'; import { ElMessage } from 'element-plus'; import Edit from './components/edit.vue'; import DetailDialog from './components/detail.vue'; @@ -33,7 +33,7 @@ const patchVisible = ref(false); const query = reactive({ keyword: "", status: "" }); const activeTypeTab = ref("all"); -const extractForm = reactive({ platform: 'local', type: 'account', remark: '' }); +const extractForm = reactive({ platform: 'local', type: 'account', remark: '', replenish: false }); const tableData = ref([]); const total = ref(0); @@ -43,6 +43,8 @@ const detailRemarkSaving = ref(false); const isMobile = ref(false); const pagination = reactive({ page: 1, pageSize: 30 }); +const skipWatchFetchDuringUnusedJump = ref(false); + const pagedList = computed(() => tableData.value); function resetQuery() { @@ -60,6 +62,7 @@ const typeTabs = computed(() => [ watch( () => [query.keyword, query.status, activeTypeTab.value], () => { + if (skipWatchFetchDuringUnusedJump.value) return; pagination.page = 1; fetchList(); }, @@ -67,6 +70,7 @@ watch( watch( () => [pagination.page, pagination.pageSize], () => { + if (skipWatchFetchDuringUnusedJump.value) return; fetchList(); }, ); @@ -123,6 +127,7 @@ function openExtractByRow(row) { extractForm.platform = "local"; extractForm.type = row.type; extractForm.remark = row.remark || ''; + extractForm.replenish = false; extractVisible.value = true; } @@ -156,7 +161,11 @@ async function handleExtract() { const target = extractTargetRow.value; if (!target) { ElMessage.warning("未找到提取目标"); return; } const res = await extractAccountPool(moduleKey, { - id: target.id, type: target.type, platform: extractForm.platform, remark: extractForm.remark || '', + id: target.id, + type: target.type, + platform: extractForm.platform, + remark: extractForm.remark || '', + replenish: !!extractForm.replenish, }); if (res?.code !== 200) { ElMessage.error(res?.msg || "提取失败"); return; } ElMessage.success("提取成功"); @@ -293,6 +302,8 @@ function normalizeRow(raw) { const p = (v) => String(v).padStart(2, "0"); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`; }; + const st = Number(pick("is_extracted", "isExtracted", "IsExtracted")); + const extractStatus = Number.isFinite(st) ? st : 0; return { id: pick("id", "Id", "ID"), type: pick("data_type", "dataType", "type"), @@ -300,13 +311,26 @@ function normalizeRow(raw) { password: pick("password", "Password"), token: pick("token", "Token"), remark: pick("remark", "Remark"), - extracted: Number(pick("is_extracted", "isExtracted", "IsExtracted")) === 1, + extractStatus, + extracted: extractStatus !== 0, extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")), extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"), createdAt: formatTime(pick("create_time", "createdAt")), }; } +function extractStatusLabel(row) { + if (row?.extractStatus === 2) return "补号"; + if (row?.extracted) return "已提取"; + return "未提取"; +} + +function extractStatusTagType(row) { + if (row?.extractStatus === 2) return "warning"; + if (row?.extracted) return "success"; + return "info"; +} + async function fetchList() { loading.value = true; try { @@ -329,6 +353,34 @@ async function fetchList() { } } +async function jumpToLastUnusedPage() { + const type = activeTypeTab.value === 'all' ? undefined : activeTypeTab.value; + const res = await getAccountPoolList(moduleKey, { + page: 1, + pageSize: pagination.pageSize, + keyword: query.keyword || undefined, + status: 'unused', + type, + }); + if (res?.code !== 200) { + ElMessage.error(res?.msg || '获取列表失败'); + return; + } + const cnt = Number(res?.data?.total || 0); + if (cnt === 0) { + ElMessage.warning('暂无未提取数据'); + return; + } + const lastPage = Math.max(1, Math.ceil(cnt / pagination.pageSize)); + skipWatchFetchDuringUnusedJump.value = true; + pagination.page = lastPage; + query.status = 'unused'; + await nextTick(); + skipWatchFetchDuringUnusedJump.value = false; + await fetchList(); + ElMessage.success(`已跳转未提取第 ${lastPage} 页(共 ${cnt} 条)`); +} + onMounted(() => { fetchList(); }); // ---- 接口说明数据 ---- @@ -427,6 +479,14 @@ function copyCardInfo(row) { + + 未提取末页 + 重置
@@ -464,8 +524,8 @@ function copyCardInfo(row) { @@ -488,9 +548,9 @@ function copyCardInfo(row) { >详情 提取 @@ -533,9 +593,11 @@ function copyCardInfo(row) { :type="extractForm.type" :platform="extractForm.platform" :remark="extractForm.remark" + :replenish="extractForm.replenish" :platform-map="PLATFORM_MAP" @update:platform="(v) => (extractForm.platform = v)" @update:remark="(v) => (extractForm.remark = v)" + @update:replenish="(v) => (extractForm.replenish = v)" @confirm="handleExtract" /> diff --git a/src/views/home/index.vue b/src/views/home/index.vue index a2a4b4e..21ef340 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -22,7 +22,7 @@ - +
@@ -36,9 +36,11 @@