diff --git a/src/api/accountPool.js b/src/api/accountPool.js index bd7b82b..3e3ff82 100644 --- a/src/api/accountPool.js +++ b/src/api/accountPool.js @@ -50,3 +50,11 @@ export function updateAccountPoolRemark(module, data) { data, }); } + +export function replenishAccountPool(module, data) { + return request({ + url: `${base(module)}/replenish`, + method: 'post', + data, + }); +} diff --git a/src/components/CommonAside.vue b/src/components/CommonAside.vue index e212853..fc2d5f4 100644 --- a/src/components/CommonAside.vue +++ b/src/components/CommonAside.vue @@ -27,7 +27,10 @@ :default-active="route.path" > -

{{ isCollapse ? "管理" : asideTitle }}

+

+ {{ isCollapse ? "管理" : asideTitle }} + +

@@ -150,6 +153,10 @@ + + +
+ + + diff --git a/src/views/accountpool/cursor/index.vue b/src/views/accountpool/cursor/index.vue index de96c16..20d5248 100644 --- a/src/views/accountpool/cursor/index.vue +++ b/src/views/accountpool/cursor/index.vue @@ -1,10 +1,10 @@ @@ -402,14 +461,18 @@ function copyCardInfo(row) { clearable class="w-260" /> - + 重置
- 补卡 添加账号 批量添加 批量标记提取 @@ -438,33 +501,58 @@ function copyCardInfo(row) { > - + - + + + + + + + + + - + + - - - + @@ -503,18 +591,15 @@ function copyCardInfo(row) { @confirm="handleExtract" /> - - - +
- + 该接口为对外公开接口,无需登录认证,每次调用自动提取一条未使用的卡密并标记为已提取(不可重复)。 @@ -528,10 +613,15 @@ function copyCardInfo(row) {
请求参数
- + @@ -546,7 +636,13 @@ function copyCardInfo(row) {
{{ ex.label }}
{{ ex.url }} - 复制 + 复制
@@ -756,4 +852,44 @@ function copyCardInfo(row) { margin: 0; } +.extract-result-item { + margin-bottom: 4px; +} + +.result-row { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 6px 0; + font-size: 13px; +} + +.result-label { + flex-shrink: 0; + width: 44px; + color: #909399; +} + +.result-val { + flex: 1; + word-break: break-all; + color: #303133; +} + +.token-val { + font-family: monospace; + font-size: 12px; + color: #409eff; +} + + \ No newline at end of file diff --git a/src/views/accountpool/kiro/components/replenish.vue b/src/views/accountpool/kiro/components/replenish.vue new file mode 100644 index 0000000..a7ce6d2 --- /dev/null +++ b/src/views/accountpool/kiro/components/replenish.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/views/accountpool/kiro/index.vue b/src/views/accountpool/kiro/index.vue index f658faa..0a6866d 100644 --- a/src/views/accountpool/kiro/index.vue +++ b/src/views/accountpool/kiro/index.vue @@ -4,6 +4,7 @@ import { ElMessage } from 'element-plus'; import Edit from './components/edit.vue'; import DetailDialog from './components/detail.vue'; import ExtractDialog from './components/extract.vue'; +import ReplenishDialog from './components/replenish.vue'; import PatchDialog from '../components/patch.vue'; import { addAccountPool, @@ -12,21 +13,26 @@ import { getAccountPoolDetail, getAccountPoolList, updateAccountPoolRemark, + replenishAccountPool, } from '@/api/accountPool'; -const moduleKey = 'krio'; +const moduleKey = "krio"; const loading = ref(false); const editVisible = ref(false); -const editMode = ref('single'); +const editMode = ref("single"); const detailVisible = ref(false); const extractVisible = ref(false); const extractTargetRow = ref(null); +const batchExtractVisible = ref(false); +const batchExtractForm = reactive({ platform: 'local', remark: '' }); +const replenishVisible = ref(false); +const replenishForm = reactive({ type: 'tk', platform: 'local', remark: '' }); const apiDocVisible = ref(false); const patchVisible = ref(false); -const query = reactive({ keyword: '', status: '' }); -const activeTypeTab = ref('all'); +const query = reactive({ keyword: "", status: "" }); +const activeTypeTab = ref("all"); const extractForm = reactive({ platform: 'local', type: 'account', remark: '' }); @@ -41,31 +47,42 @@ const pagination = reactive({ page: 1, pageSize: 30 }); const pagedList = computed(() => tableData.value); function resetQuery() { - query.keyword = ''; - query.status = ''; + query.keyword = ""; + query.status = ""; } const typeTabs = computed(() => [ - { label: '全部', value: 'all' }, - { label: '账号密码', value: 'account' }, - { label: '账号密码+Token', value: 'account_tk' }, - { label: 'Token', value: 'tk' }, + { label: "全部", value: "all" }, + { label: "账号密码", value: "account" }, + { label: "账号密码+Token", value: "account_tk" }, + { label: "Token", value: "tk" }, ]); -watch(() => [query.keyword, query.status, activeTypeTab.value], () => { - pagination.page = 1; - fetchList(); -}); -watch(() => [pagination.page, pagination.pageSize], () => { fetchList(); }); +watch( + () => [query.keyword, query.status, activeTypeTab.value], + () => { + pagination.page = 1; + fetchList(); + }, +); +watch( + () => [pagination.page, pagination.pageSize], + () => { + fetchList(); + }, +); -function openAddDialog(mode = 'single') { +function openAddDialog(mode = "single") { editMode.value = mode; editVisible.value = true; } async function saveRows(rows) { if (!rows.length) return; - if (rows.length === 1) { await addAccountPool(moduleKey, rows[0]); return; } + if (rows.length === 1) { + await addAccountPool(moduleKey, rows[0]); + return; + } await batchAddAccountPool(moduleKey, rows); } @@ -73,27 +90,38 @@ async function handleEditSubmit(payload) { loading.value = true; try { await saveRows(payload.rows || []); - ElMessage.success(payload.mode === 'batch' ? '批量添加成功' : '账号添加成功'); + ElMessage.success( + payload.mode === "batch" ? "批量添加成功" : "账号添加成功", + ); await fetchList(); - } finally { loading.value = false; } + } finally { + loading.value = false; + } } -function handleSelectionChange(rows) { selectedRows.value = rows; } +function handleSelectionChange(rows) { + selectedRows.value = rows; +} function openDetail(row) { loading.value = true; getAccountPoolDetail(moduleKey, row.id) .then((res) => { - if (res?.code !== 200) { ElMessage.error(res?.msg || '获取详情失败'); return; } + if (res?.code !== 200) { + ElMessage.error(res?.msg || "获取详情失败"); + return; + } detailRow.value = normalizeRow(res.data || {}); detailVisible.value = true; }) - .finally(() => { loading.value = false; }); + .finally(() => { + loading.value = false; + }); } function openExtractByRow(row) { extractTargetRow.value = row; - extractForm.platform = 'local'; + extractForm.platform = "local"; extractForm.type = row.type; extractForm.remark = row.remark || ''; extractVisible.value = true; @@ -127,15 +155,19 @@ async function handleExtract() { loading.value = true; try { const target = extractTargetRow.value; - if (!target) { ElMessage.warning('未找到提取目标'); return; } + if (!target) { ElMessage.warning("未找到提取目标"); return; } const res = await extractAccountPool(moduleKey, { id: target.id, type: target.type, platform: extractForm.platform, remark: extractForm.remark || '', }); - if (res?.code !== 200) { ElMessage.error(res?.msg || '提取失败'); return; } - ElMessage.success('提取成功'); + if (res?.code !== 200) { ElMessage.error(res?.msg || "提取失败"); return; } + ElMessage.success("提取成功"); extractVisible.value = false; + const row = normalizeRow(res.data || {}); + navigator.clipboard.writeText(rowToText(row)).catch(() => {}); await fetchList(); - } finally { loading.value = false; } + } finally { + loading.value = false; + } } async function handleSaveRemark(payload) { @@ -153,41 +185,101 @@ async function handleSaveRemark(payload) { } function markExtractForSelected() { - if (!selectedRows.value.length) { ElMessage.warning('请先选择数据'); return; } + if (!selectedRows.value.length) { ElMessage.warning("请先选择数据"); return; } + batchExtractForm.platform = 'local'; + batchExtractForm.remark = ''; + batchExtractVisible.value = true; +} + +async function handleBatchExtract() { loading.value = true; - Promise.all( - selectedRows.value.map((row) => - extractAccountPool(moduleKey, { id: row.id, type: row.type, platform: 'local' }) - ) - ).then(() => { ElMessage.success('批量提取成功'); fetchList(); }) - .finally(() => { loading.value = false; }); + try { + const results = await Promise.all( + selectedRows.value.map((row) => + extractAccountPool(moduleKey, { + id: row.id, type: row.type, + platform: batchExtractForm.platform, + remark: batchExtractForm.remark || '', + }), + ), + ); + const succeeded = results.filter((r) => r?.code === 200).map((r) => normalizeRow(r.data || {})); + const failCount = results.length - succeeded.length; + if (failCount > 0) { + ElMessage.warning(`${succeeded.length} 条成功,${failCount} 条失败`); + } else { + ElMessage.success("批量提取成功"); + } + batchExtractVisible.value = false; + if (succeeded.length) { + const text = succeeded.map(rowToText).filter(Boolean).join('\n'); + navigator.clipboard.writeText(text).catch(() => {}); + } + fetchList(); + } finally { + loading.value = false; + } +} + +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 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'; + if (type === "account") return "账号密码"; + if (type === "account_tk") return "账号密码+Token"; + return "Token"; } +const tooltipOpts = { + popperClass: 'pool-tooltip', + popperStyle: { maxWidth: '600px', wordBreak: 'break-all', whiteSpace: 'pre-wrap' }, +}; + const PLATFORM_MAP = { local: { label: '本地', type: 'info' }, xianyu: { label: '闲鱼', type: 'warning' }, - taobao: { label: '淘宝', type: 'info' }, pinduoduo: { label: '拼多多', type: 'danger' }, jingdong: { label: '京东', type: 'primary' }, douyin: { label: '抖音', type: 'success' }, - ziyoushangcheng: { label: '自有商城', type: 'warning' }, }; -function platformText(platform) { return PLATFORM_MAP[platform]?.label || (platform || '-'); } -function platformTagType(platform) { return PLATFORM_MAP[platform]?.type || 'info'; } +function platformText(platform) { + return PLATFORM_MAP[platform]?.label || platform || "-"; +} +function platformTagType(platform) { + return PLATFORM_MAP[platform]?.type || "info"; +} function normalizeRow(raw) { const pick = (...keys) => { for (const key of keys) { if (raw?.[key] !== undefined && raw?.[key] !== null) return raw[key]; } - return ''; + return ""; }; const pickNullable = (...keys) => { for (const key of keys) { @@ -196,23 +288,23 @@ function normalizeRow(raw) { return null; }; const formatTime = (val) => { - if (!val) return ''; + if (!val) return ""; const d = new Date(val); if (isNaN(d)) return val; - 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 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())}`; }; return { - id: pick('id', 'Id', 'ID'), - type: pick('data_type', 'dataType', 'type'), - account: pick('account', 'Account'), - password: pick('password', 'Password'), - token: pick('token', 'Token'), - remark: pick('remark', 'Remark'), - extracted: Number(pick('is_extracted', 'isExtracted', 'IsExtracted')) === 1, - extractedAt: formatTime(pickNullable('extracted_time', 'extractedAt')), - extractedPlatform: pickNullable('extracted_platform', 'extractedPlatform'), - createdAt: formatTime(pick('create_time', 'createdAt')), + id: pick("id", "Id", "ID"), + type: pick("data_type", "dataType", "type"), + account: pick("account", "Account"), + password: pick("password", "Password"), + token: pick("token", "Token"), + remark: pick("remark", "Remark"), + extracted: Number(pick("is_extracted", "isExtracted", "IsExtracted")) === 1, + extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")), + extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"), + createdAt: formatTime(pick("create_time", "createdAt")), }; } @@ -220,62 +312,66 @@ async function fetchList() { loading.value = true; try { const res = await getAccountPoolList(moduleKey, { - page: pagination.page, pageSize: pagination.pageSize, + page: pagination.page, + pageSize: pagination.pageSize, keyword: query.keyword || undefined, status: query.status || undefined, - type: activeTypeTab.value === 'all' ? undefined : activeTypeTab.value, + type: activeTypeTab.value === "all" ? undefined : activeTypeTab.value, }); - if (res?.code !== 200) { ElMessage.error(res?.msg || '获取列表失败'); return; } + if (res?.code !== 200) { + ElMessage.error(res?.msg || "获取列表失败"); + return; + } const list = Array.isArray(res?.data?.list) ? res.data.list : []; tableData.value = list.map(normalizeRow); total.value = Number(res?.data?.total || 0); - } finally { loading.value = false; } + } finally { + loading.value = false; + } } -function updateDeviceType() { - isMobile.value = window.innerWidth <= 768; -} - -onMounted(() => { - updateDeviceType(); - window.addEventListener('resize', updateDeviceType); - fetchList(); -}); - -onUnmounted(() => { - window.removeEventListener('resize', updateDeviceType); -}); +onMounted(() => { fetchList(); }); // ---- 接口说明数据 ---- -const BASE_URL = 'https://api.yunzer.cn'; +const BASE_URL = "https://api.yunzer.cn"; const paramDocs = [ - { name: 'type', required: true, desc: '来源平台,用于标记本次提取来自哪个渠道', values: 'xianyu / taobao / pinduoduo / jingdong / douyin / ziyoushangcheng / local' }, + { name: 'type', required: true, desc: '来源平台,用于标记本次提取来自哪个渠道', values: 'xianyu / pinduoduo / jingdong / douyin / local' }, { name: 'module', required: true, desc: '号池模块,指定从哪个产品的号池提取', values: 'cursor / windsurf / krio' }, { name: 'data_type', required: false, desc: '账号类型,不传则提取任意类型', values: 'account / tk / account_tk' }, ]; const platformDocs = [ { value: 'xianyu', label: '闲鱼', desc: '闲鱼平台发货调用' }, - { value: 'taobao', label: '淘宝', desc: '淘宝平台发货调用' }, { value: 'pinduoduo', label: '拼多多', desc: '拼多多平台发货调用' }, { value: 'jingdong', label: '京东', desc: '京东平台发货调用' }, { value: 'douyin', label: '抖音', desc: '抖音平台发货调用' }, - { value: 'ziyoushangcheng', label: '自有商城', desc: '自有商城平台发货调用' }, { value: 'local', label: '本地', desc: '本地手动调用' }, ]; const moduleDocs = [ - { value: 'cursor', label: 'Cursor', desc: 'Cursor 号池' }, - { value: 'windsurf', label: 'Windsurf', desc: 'Windsurf 号池' }, - { value: 'krio', label: 'Krio', desc: 'Krio 号池' }, + { value: "cursor", label: "Cursor", desc: "Cursor 号池" }, + { value: "windsurf", label: "Windsurf", desc: "Windsurf 号池" }, + { value: "krio", label: "Krio", desc: "Krio 号池" }, ]; const examples = [ - { label: '闲鱼 · 提取 Krio Token', url: `${BASE_URL}/api/getcard?type=xianyu&module=krio&data_type=tk` }, - { label: '拼多多 · 提取 Krio 账号密码', url: `${BASE_URL}/api/getcard?type=pinduoduo&module=krio&data_type=account` }, - { label: '京东 · 提取 Cursor 任意类型', url: `${BASE_URL}/api/getcard?type=jingdong&module=cursor` }, - { label: '抖音 · 提取 Windsurf Token', url: `${BASE_URL}/api/getcard?type=douyin&module=windsurf&data_type=tk` }, + { + label: "闲鱼 · 提取 Krio Token", + url: `${BASE_URL}/api/getcard?type=xianyu&module=krio&data_type=tk`, + }, + { + label: "拼多多 · 提取 Krio 账号密码", + url: `${BASE_URL}/api/getcard?type=pinduoduo&module=krio&data_type=account`, + }, + { + label: "京东 · 提取 Cursor 任意类型", + url: `${BASE_URL}/api/getcard?type=jingdong&module=cursor`, + }, + { + label: "抖音 · 提取 Windsurf Token", + url: `${BASE_URL}/api/getcard?type=douyin&module=windsurf&data_type=tk`, + }, ]; const successResp = `// 纯 Token 类型(data_type=tk) @@ -294,11 +390,16 @@ const errorResp = `// 无可用卡密 { "code": 400, "msg": "缺少参数 type(来源平台)" }`; function copyText(text) { - copyToClipboard(text); + navigator.clipboard.writeText(text).then(() => { ElMessage.success('已复制'); }); } function copyCardInfo(row) { - copyToClipboard(buildCopyTextByRow(row)); + const parts = []; + if (row.account) parts.push(row.account); + if (row.password) parts.push(row.password); + if (row.token) parts.push(row.token); + if (!parts.length) { ElMessage.warning('无可复制内容'); return; } + navigator.clipboard.writeText(parts.join('\n')).then(() => { ElMessage.success('已复制'); }); } @@ -312,15 +413,24 @@ function copyCardInfo(row) {
- - + + 重置
- 补卡 添加账号 批量添加 批量标记提取 @@ -329,36 +439,67 @@ function copyCardInfo(row) {
- + -
- + - + - + + + + + + + + + - + + - - - + @@ -397,18 +538,15 @@ function copyCardInfo(row) { @confirm="handleExtract" /> - - - +
- + 该接口为对外公开接口,无需登录认证,每次调用自动提取一条未使用的卡密并标记为已提取(不可重复)。
@@ -420,9 +558,16 @@ function copyCardInfo(row) {
请求参数
- + @@ -435,7 +580,13 @@ function copyCardInfo(row) {
{{ ex.label }}
{{ ex.url }} - 复制 + 复制
@@ -504,3 +655,11 @@ function copyCardInfo(row) { .example-url { flex: 1; font-size: 12px; color: #409eff; word-break: break-all; } .code-block { background: #1e1e1e; color: #d4d4d4; padding: 12px 16px; border-radius: 6px; font-size: 12px; line-height: 1.6; overflow-x: auto; white-space: pre-wrap; word-break: break-all; margin: 0; } + + \ No newline at end of file diff --git a/src/views/accountpool/windsurf/components/replenish.vue b/src/views/accountpool/windsurf/components/replenish.vue new file mode 100644 index 0000000..a7ce6d2 --- /dev/null +++ b/src/views/accountpool/windsurf/components/replenish.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/views/accountpool/windsurf/index.vue b/src/views/accountpool/windsurf/index.vue index 0290e8a..a1650db 100644 --- a/src/views/accountpool/windsurf/index.vue +++ b/src/views/accountpool/windsurf/index.vue @@ -4,7 +4,7 @@ import { ElMessage } from 'element-plus'; import Edit from './components/edit.vue'; import DetailDialog from './components/detail.vue'; import ExtractDialog from './components/extract.vue'; -import PatchDialog from '../components/patch.vue'; +import ReplenishDialog from './components/replenish.vue';import PatchDialog from '../components/patch.vue'; import { addAccountPool, batchAddAccountPool, @@ -12,21 +12,26 @@ import { getAccountPoolDetail, getAccountPoolList, updateAccountPoolRemark, + replenishAccountPool, } from '@/api/accountPool'; -const moduleKey = 'windsurf'; +const moduleKey = "windsurf"; const loading = ref(false); const editVisible = ref(false); -const editMode = ref('single'); +const editMode = ref("single"); const detailVisible = ref(false); const extractVisible = ref(false); const extractTargetRow = ref(null); +const batchExtractVisible = ref(false); +const batchExtractForm = reactive({ platform: 'local', remark: '' }); +const replenishVisible = ref(false); +const replenishForm = reactive({ type: 'tk', platform: 'local', remark: '' }); const apiDocVisible = ref(false); const patchVisible = ref(false); -const query = reactive({ keyword: '', status: '' }); -const activeTypeTab = ref('all'); +const query = reactive({ keyword: "", status: "" }); +const activeTypeTab = ref("all"); const extractForm = reactive({ platform: 'local', type: 'account', remark: '' }); @@ -41,31 +46,42 @@ const pagination = reactive({ page: 1, pageSize: 30 }); const pagedList = computed(() => tableData.value); function resetQuery() { - query.keyword = ''; - query.status = ''; + query.keyword = ""; + query.status = ""; } const typeTabs = computed(() => [ - { label: '全部', value: 'all' }, - { label: '账号密码', value: 'account' }, - { label: '账号密码+Token', value: 'account_tk' }, - { label: 'Token', value: 'tk' }, + { label: "全部", value: "all" }, + { label: "账号密码", value: "account" }, + { label: "账号密码+Token", value: "account_tk" }, + { label: "Token", value: "tk" }, ]); -watch(() => [query.keyword, query.status, activeTypeTab.value], () => { - pagination.page = 1; - fetchList(); -}); -watch(() => [pagination.page, pagination.pageSize], () => { fetchList(); }); +watch( + () => [query.keyword, query.status, activeTypeTab.value], + () => { + pagination.page = 1; + fetchList(); + }, +); +watch( + () => [pagination.page, pagination.pageSize], + () => { + fetchList(); + }, +); -function openAddDialog(mode = 'single') { +function openAddDialog(mode = "single") { editMode.value = mode; editVisible.value = true; } async function saveRows(rows) { if (!rows.length) return; - if (rows.length === 1) { await addAccountPool(moduleKey, rows[0]); return; } + if (rows.length === 1) { + await addAccountPool(moduleKey, rows[0]); + return; + } await batchAddAccountPool(moduleKey, rows); } @@ -73,27 +89,38 @@ async function handleEditSubmit(payload) { loading.value = true; try { await saveRows(payload.rows || []); - ElMessage.success(payload.mode === 'batch' ? '批量添加成功' : '账号添加成功'); + ElMessage.success( + payload.mode === "batch" ? "批量添加成功" : "账号添加成功", + ); await fetchList(); - } finally { loading.value = false; } + } finally { + loading.value = false; + } } -function handleSelectionChange(rows) { selectedRows.value = rows; } +function handleSelectionChange(rows) { + selectedRows.value = rows; +} function openDetail(row) { loading.value = true; getAccountPoolDetail(moduleKey, row.id) .then((res) => { - if (res?.code !== 200) { ElMessage.error(res?.msg || '获取详情失败'); return; } + if (res?.code !== 200) { + ElMessage.error(res?.msg || "获取详情失败"); + return; + } detailRow.value = normalizeRow(res.data || {}); detailVisible.value = true; }) - .finally(() => { loading.value = false; }); + .finally(() => { + loading.value = false; + }); } function openExtractByRow(row) { extractTargetRow.value = row; - extractForm.platform = 'local'; + extractForm.platform = "local"; extractForm.type = row.type; extractForm.remark = row.remark || ''; extractVisible.value = true; @@ -127,15 +154,19 @@ async function handleExtract() { loading.value = true; try { const target = extractTargetRow.value; - if (!target) { ElMessage.warning('未找到提取目标'); return; } + if (!target) { ElMessage.warning("未找到提取目标"); return; } const res = await extractAccountPool(moduleKey, { id: target.id, type: target.type, platform: extractForm.platform, remark: extractForm.remark || '', }); - if (res?.code !== 200) { ElMessage.error(res?.msg || '提取失败'); return; } - ElMessage.success('提取成功'); + if (res?.code !== 200) { ElMessage.error(res?.msg || "提取失败"); return; } + ElMessage.success("提取成功"); extractVisible.value = false; + const row = normalizeRow(res.data || {}); + navigator.clipboard.writeText(rowToText(row)).catch(() => {}); await fetchList(); - } finally { loading.value = false; } + } finally { + loading.value = false; + } } async function handleSaveRemark(payload) { @@ -153,41 +184,101 @@ async function handleSaveRemark(payload) { } function markExtractForSelected() { - if (!selectedRows.value.length) { ElMessage.warning('请先选择数据'); return; } + if (!selectedRows.value.length) { ElMessage.warning("请先选择数据"); return; } + batchExtractForm.platform = 'local'; + batchExtractForm.remark = ''; + batchExtractVisible.value = true; +} + +async function handleBatchExtract() { loading.value = true; - Promise.all( - selectedRows.value.map((row) => - extractAccountPool(moduleKey, { id: row.id, type: row.type, platform: 'local' }) - ) - ).then(() => { ElMessage.success('批量提取成功'); fetchList(); }) - .finally(() => { loading.value = false; }); + try { + const results = await Promise.all( + selectedRows.value.map((row) => + extractAccountPool(moduleKey, { + id: row.id, type: row.type, + platform: batchExtractForm.platform, + remark: batchExtractForm.remark || '', + }), + ), + ); + const succeeded = results.filter((r) => r?.code === 200).map((r) => normalizeRow(r.data || {})); + const failCount = results.length - succeeded.length; + if (failCount > 0) { + ElMessage.warning(`${succeeded.length} 条成功,${failCount} 条失败`); + } else { + ElMessage.success("批量提取成功"); + } + batchExtractVisible.value = false; + if (succeeded.length) { + const text = succeeded.map(rowToText).filter(Boolean).join('\n'); + navigator.clipboard.writeText(text).catch(() => {}); + } + fetchList(); + } finally { + loading.value = false; + } +} + +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 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'; + if (type === "account") return "账号密码"; + if (type === "account_tk") return "账号密码+Token"; + return "Token"; } +const tooltipOpts = { + popperClass: 'pool-tooltip', + popperStyle: { maxWidth: '600px', wordBreak: 'break-all', whiteSpace: 'pre-wrap' }, +}; + const PLATFORM_MAP = { local: { label: '本地', type: 'info' }, xianyu: { label: '闲鱼', type: 'warning' }, - taobao: { label: '淘宝', type: 'info' }, pinduoduo: { label: '拼多多', type: 'danger' }, jingdong: { label: '京东', type: 'primary' }, douyin: { label: '抖音', type: 'success' }, - ziyoushangcheng: { label: '自有商城', type: 'warning' }, }; -function platformText(platform) { return PLATFORM_MAP[platform]?.label || (platform || '-'); } -function platformTagType(platform) { return PLATFORM_MAP[platform]?.type || 'info'; } +function platformText(platform) { + return PLATFORM_MAP[platform]?.label || platform || "-"; +} +function platformTagType(platform) { + return PLATFORM_MAP[platform]?.type || "info"; +} function normalizeRow(raw) { const pick = (...keys) => { for (const key of keys) { if (raw?.[key] !== undefined && raw?.[key] !== null) return raw[key]; } - return ''; + return ""; }; const pickNullable = (...keys) => { for (const key of keys) { @@ -196,23 +287,23 @@ function normalizeRow(raw) { return null; }; const formatTime = (val) => { - if (!val) return ''; + if (!val) return ""; const d = new Date(val); if (isNaN(d)) return val; - 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 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())}`; }; return { - id: pick('id', 'Id', 'ID'), - type: pick('data_type', 'dataType', 'type'), - account: pick('account', 'Account'), - password: pick('password', 'Password'), - token: pick('token', 'Token'), - remark: pick('remark', 'Remark'), - extracted: Number(pick('is_extracted', 'isExtracted', 'IsExtracted')) === 1, - extractedAt: formatTime(pickNullable('extracted_time', 'extractedAt')), - extractedPlatform: pickNullable('extracted_platform', 'extractedPlatform'), - createdAt: formatTime(pick('create_time', 'createdAt')), + id: pick("id", "Id", "ID"), + type: pick("data_type", "dataType", "type"), + account: pick("account", "Account"), + password: pick("password", "Password"), + token: pick("token", "Token"), + remark: pick("remark", "Remark"), + extracted: Number(pick("is_extracted", "isExtracted", "IsExtracted")) === 1, + extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")), + extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"), + createdAt: formatTime(pick("create_time", "createdAt")), }; } @@ -220,62 +311,66 @@ async function fetchList() { loading.value = true; try { const res = await getAccountPoolList(moduleKey, { - page: pagination.page, pageSize: pagination.pageSize, + page: pagination.page, + pageSize: pagination.pageSize, keyword: query.keyword || undefined, status: query.status || undefined, - type: activeTypeTab.value === 'all' ? undefined : activeTypeTab.value, + type: activeTypeTab.value === "all" ? undefined : activeTypeTab.value, }); - if (res?.code !== 200) { ElMessage.error(res?.msg || '获取列表失败'); return; } + if (res?.code !== 200) { + ElMessage.error(res?.msg || "获取列表失败"); + return; + } const list = Array.isArray(res?.data?.list) ? res.data.list : []; tableData.value = list.map(normalizeRow); total.value = Number(res?.data?.total || 0); - } finally { loading.value = false; } + } finally { + loading.value = false; + } } -function updateDeviceType() { - isMobile.value = window.innerWidth <= 768; -} - -onMounted(() => { - updateDeviceType(); - window.addEventListener('resize', updateDeviceType); - fetchList(); -}); - -onUnmounted(() => { - window.removeEventListener('resize', updateDeviceType); -}); +onMounted(() => { fetchList(); }); // ---- 接口说明数据 ---- -const BASE_URL = 'https://api.yunzer.cn'; +const BASE_URL = "https://api.yunzer.cn"; const paramDocs = [ - { name: 'type', required: true, desc: '来源平台,用于标记本次提取来自哪个渠道', values: 'xianyu / taobao / pinduoduo / jingdong / douyin / ziyoushangcheng / local' }, + { name: 'type', required: true, desc: '来源平台,用于标记本次提取来自哪个渠道', values: 'xianyu / pinduoduo / jingdong / douyin / local' }, { name: 'module', required: true, desc: '号池模块,指定从哪个产品的号池提取', values: 'cursor / windsurf / krio' }, { name: 'data_type', required: false, desc: '账号类型,不传则提取任意类型', values: 'account / tk / account_tk' }, ]; const platformDocs = [ { value: 'xianyu', label: '闲鱼', desc: '闲鱼平台发货调用' }, - { value: 'taobao', label: '淘宝', desc: '淘宝平台发货调用' }, { value: 'pinduoduo', label: '拼多多', desc: '拼多多平台发货调用' }, { value: 'jingdong', label: '京东', desc: '京东平台发货调用' }, { value: 'douyin', label: '抖音', desc: '抖音平台发货调用' }, - { value: 'ziyoushangcheng', label: '自有商城', desc: '自有商城平台发货调用' }, { value: 'local', label: '本地', desc: '本地手动调用' }, ]; const moduleDocs = [ - { value: 'cursor', label: 'Cursor', desc: 'Cursor 号池' }, - { value: 'windsurf', label: 'Windsurf', desc: 'Windsurf 号池' }, - { value: 'krio', label: 'Krio', desc: 'Krio 号池' }, + { value: "cursor", label: "Cursor", desc: "Cursor 号池" }, + { value: "windsurf", label: "Windsurf", desc: "Windsurf 号池" }, + { value: "krio", label: "Krio", desc: "Krio 号池" }, ]; const examples = [ - { label: '闲鱼 · 提取 Windsurf Token', url: `${BASE_URL}/api/getcard?type=xianyu&module=windsurf&data_type=tk` }, - { label: '拼多多 · 提取 Windsurf 账号密码', url: `${BASE_URL}/api/getcard?type=pinduoduo&module=windsurf&data_type=account` }, - { label: '京东 · 提取 Cursor 任意类型', url: `${BASE_URL}/api/getcard?type=jingdong&module=cursor` }, - { label: '抖音 · 提取 Krio Token', url: `${BASE_URL}/api/getcard?type=douyin&module=krio&data_type=tk` }, + { + label: "闲鱼 · 提取 Windsurf Token", + url: `${BASE_URL}/api/getcard?type=xianyu&module=windsurf&data_type=tk`, + }, + { + label: "拼多多 · 提取 Windsurf 账号密码", + url: `${BASE_URL}/api/getcard?type=pinduoduo&module=windsurf&data_type=account`, + }, + { + label: "京东 · 提取 Cursor 任意类型", + url: `${BASE_URL}/api/getcard?type=jingdong&module=cursor`, + }, + { + label: "抖音 · 提取 Krio Token", + url: `${BASE_URL}/api/getcard?type=douyin&module=krio&data_type=tk`, + }, ]; const successResp = `// 纯 Token 类型(data_type=tk) @@ -294,11 +389,16 @@ const errorResp = `// 无可用卡密 { "code": 400, "msg": "缺少参数 type(来源平台)" }`; function copyText(text) { - copyToClipboard(text); + navigator.clipboard.writeText(text).then(() => { ElMessage.success('已复制'); }); } function copyCardInfo(row) { - copyToClipboard(buildCopyTextByRow(row)); + const parts = []; + if (row.account) parts.push(row.account); + if (row.password) parts.push(row.password); + if (row.token) parts.push(row.token); + if (!parts.length) { ElMessage.warning('无可复制内容'); return; } + navigator.clipboard.writeText(parts.join('\n')).then(() => { ElMessage.success('已复制'); }); } @@ -312,15 +412,24 @@ function copyCardInfo(row) {
- - + + 重置
- 补卡 添加账号 批量添加 批量标记提取 @@ -329,36 +438,67 @@ function copyCardInfo(row) {
- + -
- + - + - + + + + + + + + + - + + - - - + @@ -397,18 +537,15 @@ function copyCardInfo(row) { @confirm="handleExtract" /> - - - +
- + 该接口为对外公开接口,无需登录认证,每次调用自动提取一条未使用的卡密并标记为已提取(不可重复)。
@@ -420,9 +557,16 @@ function copyCardInfo(row) {
请求参数
- + @@ -435,7 +579,13 @@ function copyCardInfo(row) {
{{ ex.label }}
{{ ex.url }} - 复制 + 复制
@@ -504,3 +654,11 @@ function copyCardInfo(row) { .example-url { flex: 1; font-size: 12px; color: #409eff; word-break: break-all; } .code-block { background: #1e1e1e; color: #d4d4d4; padding: 12px 16px; border-radius: 6px; font-size: 12px; line-height: 1.6; overflow-x: auto; white-space: pre-wrap; word-break: break-all; margin: 0; } + + \ No newline at end of file diff --git a/src/views/analytics/users/index.vue b/src/views/analytics/users/index.vue index 93397c5..a2a4b4e 100644 --- a/src/views/analytics/users/index.vue +++ b/src/views/analytics/users/index.vue @@ -1,7 +1,7 @@