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 @@
+
+
+
+
+
+
+ emit('update:modelValue', v)"
+ >
+
+
+ emit('update:type', v)">
+
+
+
+
+
+
+ emit('update:platform', v)">
+
+
+
+
+ emit('update:remark', v)"
+ />
+
+
+
+ 取消
+ 确认补号
+
+
+
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"
/>
-
+
重置
@@ -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 @@
+
+
+
+ emit('update:modelValue', v)"
+ >
+
+
+ emit('update:type', v)">
+
+
+
+
+
+
+ emit('update:platform', v)">
+
+
+
+
+ emit('update:remark', v)"
+ />
+
+
+
+ 取消
+ 确认补号
+
+
+
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) {