Compare commits
2 Commits
aecd161337
...
bbecc5650d
| Author | SHA1 | Date | |
|---|---|---|---|
| bbecc5650d | |||
| 9c9bbb00f2 |
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-aside :width="width" class="common-aside" :class="{ 'mobile-open': mobileOpen }">
|
<el-aside :width="width" :class="['common-aside', { 'mobile-open': isMobile && !isCollapse }]">
|
||||||
|
<!-- 加载状态 -->
|
||||||
<div v-if="loading" class="loading-spinner">
|
<div v-if="loading" class="loading-spinner">
|
||||||
<i class="el-icon-loading" style="font-size: 24px; color: #fff"></i>
|
<i class="el-icon-loading" style="font-size: 24px; color: #fff"></i>
|
||||||
</div>
|
</div>
|
||||||
@ -145,6 +146,12 @@
|
|||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
|
|
||||||
|
<div v-if="!loading && !hasError && !isCollapse" class="aside-toggle-bottom">
|
||||||
|
<el-button class="aside-toggle-btn" size="small" @click="handleCollapse">
|
||||||
|
<el-icon><Fold /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
|
|
||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
@ -155,7 +162,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
|
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import { Document, Warning } from "@element-plus/icons-vue";
|
import { Document, Warning, Fold } from "@element-plus/icons-vue";
|
||||||
import { useAllDataStore, useMenuStore } from "@/stores";
|
import { useAllDataStore, useMenuStore } from "@/stores";
|
||||||
|
|
||||||
const emit = defineEmits(["menu-click"]);
|
const emit = defineEmits(["menu-click"]);
|
||||||
@ -171,20 +178,19 @@ const store = useAllDataStore();
|
|||||||
const isCollapse = computed(() => store.state.isCollapse);
|
const isCollapse = computed(() => store.state.isCollapse);
|
||||||
const width = computed(() => (store.state.isCollapse ? "64px" : "200px"));
|
const width = computed(() => (store.state.isCollapse ? "64px" : "200px"));
|
||||||
|
|
||||||
const mobileOpen = ref(false);
|
|
||||||
|
|
||||||
function openMobile() { mobileOpen.value = true; }
|
|
||||||
function closeMobile() { mobileOpen.value = false; }
|
|
||||||
function toggleMobile() { mobileOpen.value = !mobileOpen.value; }
|
|
||||||
|
|
||||||
defineExpose({ openMobile, closeMobile, toggleMobile });
|
|
||||||
|
|
||||||
const asideBgColor = ref("#304156");
|
const asideBgColor = ref("#304156");
|
||||||
const asideTextColor = ref("#bfcbd9");
|
const asideTextColor = ref("#bfcbd9");
|
||||||
const activeColor = ref("#3973FF");
|
const activeColor = ref("#3973FF");
|
||||||
const activeBgColor = ref("#3973FF");
|
const activeBgColor = ref("#3973FF");
|
||||||
|
|
||||||
const currentModuleId = ref(null);
|
const currentModuleId = ref(null);
|
||||||
|
const updateDeviceType = () => {
|
||||||
|
isMobile.value = window.innerWidth <= 768;
|
||||||
|
// 手机端默认收起侧边栏
|
||||||
|
if (isMobile.value) {
|
||||||
|
store.state.isCollapse = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const findMenuItem = (menus, targetIndex) => {
|
const findMenuItem = (menus, targetIndex) => {
|
||||||
for (const menu of menus) {
|
for (const menu of menus) {
|
||||||
@ -344,6 +350,9 @@ const handleMenuSelect = (index) => {
|
|||||||
const menuItem = findMenuItem(list.value, index);
|
const menuItem = findMenuItem(list.value, index);
|
||||||
if (menuItem) {
|
if (menuItem) {
|
||||||
emit("menu-click", menuItem);
|
emit("menu-click", menuItem);
|
||||||
|
if (isMobile.value) {
|
||||||
|
store.state.isCollapse = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -353,6 +362,10 @@ const fetchMenus = async () => {
|
|||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCollapse = () => {
|
||||||
|
store.state.isCollapse = !store.state.isCollapse;
|
||||||
|
};
|
||||||
|
|
||||||
const handleMenuRefresh = () => {
|
const handleMenuRefresh = () => {
|
||||||
fetchMenus();
|
fetchMenus();
|
||||||
};
|
};
|
||||||
@ -366,6 +379,9 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
updateDeviceType();
|
||||||
|
window.addEventListener("resize", updateDeviceType);
|
||||||
|
|
||||||
if (!menuStore.menus || menuStore.menus.length === 0) {
|
if (!menuStore.menus || menuStore.menus.length === 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fetchMenus();
|
fetchMenus();
|
||||||
@ -376,6 +392,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener("resize", updateDeviceType);
|
||||||
window.removeEventListener("menu-cache-refreshed", handleMenuRefresh);
|
window.removeEventListener("menu-cache-refreshed", handleMenuRefresh);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -402,6 +419,10 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.common-aside.mobile-open {
|
||||||
|
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.35), 2px 0 12px rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -470,7 +491,7 @@ h3 {
|
|||||||
// 菜单样式
|
// 菜单样式
|
||||||
:deep(.el-menu) {
|
:deep(.el-menu) {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
height: calc(100% - 80px);
|
height: calc(100% - 128px);
|
||||||
padding: 16px 8px;
|
padding: 16px 8px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
||||||
@ -567,25 +588,44 @@ h3 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.aside-toggle-bottom {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px 8px 14px;
|
||||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside-toggle-btn {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 180px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.18);
|
||||||
|
border-color: rgba(255, 255, 255, 0.3);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside-toggle-btn:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.28);
|
||||||
|
border-color: rgba(255, 255, 255, 0.45);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
// 响应式设计
|
// 响应式设计
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.common-aside {
|
.common-aside {
|
||||||
position: fixed !important;
|
width: 100% !important;
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
height: 100vh !important;
|
|
||||||
transform: translateX(-100%);
|
|
||||||
transition: transform 0.3s ease, width 0.3s ease !important;
|
|
||||||
|
|
||||||
&.mobile-open {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu) {
|
:deep(.el-menu) {
|
||||||
padding: 12px 4px;
|
padding: 12px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.aside-toggle-bottom {
|
||||||
|
padding: 10px 8px 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="l-content">
|
<div class="l-content">
|
||||||
<el-button size="small" @click="handleCollapse">
|
<el-button v-if="showTopToggle" size="small" @click="handleCollapse">
|
||||||
<i class="fa fa-bars"></i>
|
<el-icon><Expand /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="r-content">
|
<div class="r-content">
|
||||||
@ -77,7 +77,7 @@ const emit = defineEmits(['collapse']);
|
|||||||
import { useAllDataStore, useMenuStore, useTabsStore } from "@/stores";
|
import { useAllDataStore, useMenuStore, useTabsStore } from "@/stores";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import { logout, getCurrentUser } from "@/api/login";
|
import { logout, getCurrentUser } from "@/api/login";
|
||||||
import { User, SwitchButton, Sunny, Moon, Refresh, Bell, HomeFilled } from '@element-plus/icons-vue';
|
import { User, SwitchButton, Sunny, Moon, Refresh, Bell, HomeFilled, Expand } from '@element-plus/icons-vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -207,6 +207,8 @@ const handleCollapse = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showTopToggle = computed(() => store.state.isCollapse);
|
||||||
|
|
||||||
const goHome = () => {
|
const goHome = () => {
|
||||||
tabsStore.closeAll();
|
tabsStore.closeAll();
|
||||||
router.push('/home');
|
router.push('/home');
|
||||||
@ -463,6 +465,46 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header {
|
||||||
|
padding: 0 10px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-content .el-button {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r-content {
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.refresh-cache-btn,
|
||||||
|
.home-btn,
|
||||||
|
.theme-toggle-btn,
|
||||||
|
.message-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
min-height: 30px;
|
||||||
|
min-width: 30px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dropdown-link {
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.user {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name,
|
||||||
|
.user-role-tag {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 下拉菜单样式 - 使用全局样式覆盖
|
// 下拉菜单样式 - 使用全局样式覆盖
|
||||||
:deep(.el-dropdown) {
|
:deep(.el-dropdown) {
|
||||||
.el-dropdown__popper {
|
.el-dropdown__popper {
|
||||||
|
|||||||
@ -677,6 +677,34 @@ const canCloseRight = computed(() => {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.common-layout,
|
||||||
|
.main-container {
|
||||||
|
.main-header {
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-main {
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
.multi-tabs-wrapper {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-extra-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.backtop-button {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|||||||
201
src/views/accountpool/components/patch.vue
Normal file
201
src/views/accountpool/components/patch.vue
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
<script setup>
|
||||||
|
import { reactive, ref, watch } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { extractAccountPool } from '@/api/accountPool';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/** cursor / windsurf / krio */
|
||||||
|
module: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
platformMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
/** 打开弹窗时的默认账号类型(与列表 Tab 对齐:全部时用 account) */
|
||||||
|
defaultAccountType: {
|
||||||
|
type: String,
|
||||||
|
default: 'account',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'success']);
|
||||||
|
|
||||||
|
const confirmLoading = ref(false);
|
||||||
|
const copiedText = ref('');
|
||||||
|
const form = reactive({
|
||||||
|
platform: 'local',
|
||||||
|
type: 'account',
|
||||||
|
remark: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
function normalizeRow(raw) {
|
||||||
|
const pick = (...keys) => {
|
||||||
|
for (const key of keys) {
|
||||||
|
if (raw?.[key] !== undefined && raw?.[key] !== null) return raw[key];
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
const pickNullable = (...keys) => {
|
||||||
|
for (const key of keys) {
|
||||||
|
if (raw?.[key] !== undefined) return raw[key] ?? null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const formatTime = (val) => {
|
||||||
|
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())}`;
|
||||||
|
};
|
||||||
|
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')),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function 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);
|
||||||
|
return parts.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyToClipboard(text) {
|
||||||
|
if (!text) {
|
||||||
|
ElMessage.warning('无可复制内容');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
ElMessage.success('已复制');
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('复制失败,请检查浏览器权限');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetWhenOpen() {
|
||||||
|
form.platform = 'local';
|
||||||
|
form.type = props.defaultAccountType || 'account';
|
||||||
|
form.remark = '';
|
||||||
|
copiedText.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(visible) => {
|
||||||
|
if (visible) resetWhenOpen();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
emit('update:modelValue', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleConfirm() {
|
||||||
|
confirmLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await extractAccountPool(props.module, {
|
||||||
|
id: 0,
|
||||||
|
type: form.type,
|
||||||
|
platform: form.platform,
|
||||||
|
remark: form.remark || '',
|
||||||
|
});
|
||||||
|
if (res?.code !== 200) {
|
||||||
|
ElMessage.error(res?.msg || '补卡失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const extractedRow = normalizeRow(res?.data || {});
|
||||||
|
const text = buildCopyTextByRow(extractedRow);
|
||||||
|
copiedText.value = text;
|
||||||
|
const copied = await copyToClipboard(text);
|
||||||
|
emit('success');
|
||||||
|
if (copied) close();
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:model-value="modelValue"
|
||||||
|
title="补卡"
|
||||||
|
width="520px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@update:model-value="emit('update:modelValue', $event)"
|
||||||
|
>
|
||||||
|
<el-form label-width="92px">
|
||||||
|
<el-form-item label="账号类型">
|
||||||
|
<el-select v-model="form.type" placeholder="请选择账号类型" class="field-full">
|
||||||
|
<el-option label="账号密码" value="account" />
|
||||||
|
<el-option label="账号密码+Token" value="account_tk" />
|
||||||
|
<el-option label="Token" value="tk" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="提取平台">
|
||||||
|
<el-select v-model="form.platform" placeholder="请选择平台" class="field-full">
|
||||||
|
<el-option
|
||||||
|
v-for="(meta, key) in platformMap"
|
||||||
|
:key="key"
|
||||||
|
:label="meta.label"
|
||||||
|
:value="key"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注">
|
||||||
|
<el-input
|
||||||
|
v-model="form.remark"
|
||||||
|
class="field-full"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="可选"
|
||||||
|
maxlength="200"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="copiedText" label="复制内容">
|
||||||
|
<el-input v-model="copiedText" type="textarea" :rows="4" readonly />
|
||||||
|
<div class="patch-copy-actions">
|
||||||
|
<el-button @click="copyToClipboard(copiedText)">复制</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="close">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="confirmLoading" @click="handleConfirm">
|
||||||
|
确定并复制
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.field-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patch-copy-actions {
|
||||||
|
margin-top: 8px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@ -28,9 +29,11 @@ function typeText(type) {
|
|||||||
const PLATFORM_MAP = {
|
const PLATFORM_MAP = {
|
||||||
local: { label: '本地', type: 'info' },
|
local: { label: '本地', type: 'info' },
|
||||||
xianyu: { label: '闲鱼', type: 'warning' },
|
xianyu: { label: '闲鱼', type: 'warning' },
|
||||||
|
taobao: { label: '淘宝', type: 'info' },
|
||||||
pinduoduo: { label: '拼多多', type: 'danger' },
|
pinduoduo: { label: '拼多多', type: 'danger' },
|
||||||
jingdong: { label: '京东', type: 'primary' },
|
jingdong: { label: '京东', type: 'primary' },
|
||||||
douyin: { label: '抖音', type: 'success' },
|
douyin: { label: '抖音', type: 'success' },
|
||||||
|
ziyoushangcheng: { label: '自有商城', type: 'warning' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const platformLabel = computed(() => {
|
const platformLabel = computed(() => {
|
||||||
@ -55,13 +58,37 @@ function onSaveRemark() {
|
|||||||
if (!props.row?.id) return;
|
if (!props.row?.id) return;
|
||||||
emit('save-remark', { id: props.row.id, remark: remarkText.value || '' });
|
emit('save-remark', { id: props.row.id, remark: remarkText.value || '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyAccountPassword() {
|
||||||
|
const account = props.row?.account || '';
|
||||||
|
const password = props.row?.password || '';
|
||||||
|
if (!account && !password) {
|
||||||
|
ElMessage.warning('暂无账号密码可复制');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.clipboard.writeText(`${account}\n${password}`.trim()).then(() => {
|
||||||
|
ElMessage.success('已复制账号+密码');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToken() {
|
||||||
|
const token = props.row?.token || '';
|
||||||
|
if (!token) {
|
||||||
|
ElMessage.warning('暂无 Token 可复制');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.clipboard.writeText(token).then(() => {
|
||||||
|
ElMessage.success('已复制 Token');
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
|
class="pool-detail-dialog"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
title="账号详情"
|
title="账号详情"
|
||||||
width="560px"
|
width="90%"
|
||||||
@update:model-value="(v) => emit('update:modelValue', v)"
|
@update:model-value="(v) => emit('update:modelValue', v)"
|
||||||
>
|
>
|
||||||
<el-descriptions :column="1" border v-if="row">
|
<el-descriptions :column="1" border v-if="row">
|
||||||
@ -82,6 +109,12 @@ function onSaveRemark() {
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="复制">
|
||||||
|
<div class="copy-actions">
|
||||||
|
<el-button size="small" @click="copyAccountPassword">账号+密码</el-button>
|
||||||
|
<el-button size="small" type="primary" @click="copyToken">Token</el-button>
|
||||||
|
</div>
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="备注">
|
<el-descriptions-item label="备注">
|
||||||
<div class="remark-edit-wrap">
|
<div class="remark-edit-wrap">
|
||||||
<el-input v-model="remarkText" type="textarea" :rows="3" placeholder="请输入备注" />
|
<el-input v-model="remarkText" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||||
@ -106,4 +139,42 @@ function onSaveRemark() {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-edit-wrap .el-button {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-detail-dialog) {
|
||||||
|
max-width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:deep(.pool-detail-dialog) {
|
||||||
|
width: calc(100vw - 24px) !important;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-detail-dialog .el-dialog__body) {
|
||||||
|
padding: 12px;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-detail-dialog .el-descriptions__label) {
|
||||||
|
width: 72px;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-edit-wrap .el-button {
|
||||||
|
width: 100%;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -37,9 +37,10 @@ function typeText(type) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
|
class="pool-extract-dialog"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
title="提取账号"
|
title="提取账号"
|
||||||
width="420px"
|
width="90%"
|
||||||
@update:model-value="(v) => emit('update:modelValue', v)"
|
@update:model-value="(v) => emit('update:modelValue', v)"
|
||||||
>
|
>
|
||||||
<el-form label-width="84px">
|
<el-form label-width="84px">
|
||||||
@ -78,3 +79,35 @@ function typeText(type) {
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.pool-extract-dialog) {
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:deep(.pool-extract-dialog) {
|
||||||
|
width: calc(100vw - 24px) !important;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-dialog__body) {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-form-item__label) {
|
||||||
|
width: 74px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-dialog__footer .el-button) {
|
||||||
|
width: calc(50% - 6px);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-dialog__footer) {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 12px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -28,6 +28,7 @@ const batchExtractForm = reactive({ platform: "local", remark: "" });
|
|||||||
const replenishVisible = ref(false);
|
const replenishVisible = ref(false);
|
||||||
const replenishForm = reactive({ type: "tk", platform: "local", remark: "" });
|
const replenishForm = reactive({ type: "tk", platform: "local", remark: "" });
|
||||||
const apiDocVisible = ref(false);
|
const apiDocVisible = ref(false);
|
||||||
|
const patchVisible = ref(false);
|
||||||
|
|
||||||
const query = reactive({
|
const query = reactive({
|
||||||
keyword: "",
|
keyword: "",
|
||||||
@ -47,6 +48,7 @@ const total = ref(0);
|
|||||||
const selectedRows = ref([]);
|
const selectedRows = ref([]);
|
||||||
const detailRow = ref(null);
|
const detailRow = ref(null);
|
||||||
const detailRemarkSaving = ref(false);
|
const detailRemarkSaving = ref(false);
|
||||||
|
const isMobile = ref(false);
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
@ -151,6 +153,33 @@ function openExtractByRow(row) {
|
|||||||
extractVisible.value = true;
|
extractVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openPatchDialog() {
|
||||||
|
patchVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function 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);
|
||||||
|
return parts.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyToClipboard(text) {
|
||||||
|
if (!text) {
|
||||||
|
ElMessage.warning('无可复制内容');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
ElMessage.success('已复制');
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('复制失败,请检查浏览器权限');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleExtract() {
|
async function handleExtract() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -248,20 +277,16 @@ function typeText(type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tooltipOpts = {
|
const tooltipOpts = {
|
||||||
popperClass: "pool-tooltip",
|
popperClass: 'pool-tooltip',
|
||||||
popperStyle: {
|
popperStyle: { maxWidth: '600px', wordBreak: 'break-all', whiteSpace: 'pre-wrap' },
|
||||||
maxWidth: "600px",
|
|
||||||
wordBreak: "break-all",
|
|
||||||
whiteSpace: "pre-wrap",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PLATFORM_MAP = {
|
const PLATFORM_MAP = {
|
||||||
local: { label: "本地", type: "info" },
|
local: { label: '本地', type: 'info' },
|
||||||
xianyu: { label: "闲鱼", type: "warning" },
|
xianyu: { label: '闲鱼', type: 'warning' },
|
||||||
pinduoduo: { label: "拼多多", type: "danger" },
|
pinduoduo: { label: '拼多多', type: 'danger' },
|
||||||
jingdong: { label: "京东", type: "primary" },
|
jingdong: { label: '京东', type: 'primary' },
|
||||||
douyin: { label: "抖音", type: "success" },
|
douyin: { label: '抖音', type: 'success' },
|
||||||
};
|
};
|
||||||
|
|
||||||
function platformText(platform) {
|
function platformText(platform) {
|
||||||
@ -329,40 +354,35 @@ async function fetchList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateDeviceType() {
|
||||||
|
isMobile.value = window.innerWidth <= 768;
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
updateDeviceType();
|
||||||
|
window.addEventListener('resize', updateDeviceType);
|
||||||
fetchList();
|
fetchList();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', updateDeviceType);
|
||||||
|
});
|
||||||
|
|
||||||
// ---- 接口说明数据 ----
|
// ---- 接口说明数据 ----
|
||||||
const BASE_URL = "https://api.yunzer.cn";
|
const BASE_URL = "https://api.yunzer.cn";
|
||||||
|
|
||||||
const paramDocs = [
|
const paramDocs = [
|
||||||
{
|
{ name: 'type', required: true, desc: '来源平台,用于标记本次提取来自哪个渠道', values: 'xianyu / taobao / pinduoduo / jingdong / local' },
|
||||||
name: "type",
|
{ name: 'module', required: true, desc: '号池模块,指定从哪个产品的号池提取', values: 'cursor / windsurf / krio' },
|
||||||
required: true,
|
{ name: 'data_type', required: false, desc: '账号类型,不传则提取任意类型', values: 'account / tk / account_tk' },
|
||||||
desc: "来源平台,用于标记本次提取来自哪个渠道",
|
|
||||||
values: "xianyu / taobao / pinduoduo / jingdong / local",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "module",
|
|
||||||
required: true,
|
|
||||||
desc: "号池模块,指定从哪个产品的号池提取",
|
|
||||||
values: "cursor / windsurf / krio",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "data_type",
|
|
||||||
required: false,
|
|
||||||
desc: "账号类型,不传则提取任意类型",
|
|
||||||
values: "account / tk / account_tk",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const platformDocs = [
|
const platformDocs = [
|
||||||
{ value: "xianyu", label: "闲鱼", desc: "闲鱼平台发货调用" },
|
{ value: 'xianyu', label: '闲鱼', desc: '闲鱼平台发货调用' },
|
||||||
{ value: "pinduoduo", label: "拼多多", desc: "拼多多平台发货调用" },
|
{ value: 'pinduoduo', label: '拼多多', desc: '拼多多平台发货调用' },
|
||||||
{ value: "jingdong", label: "京东", desc: "京东平台发货调用" },
|
{ value: 'jingdong', label: '京东', desc: '京东平台发货调用' },
|
||||||
{ value: "douyin", label: "抖音", desc: "抖音平台发货调用" },
|
{ value: 'douyin', label: '抖音', desc: '抖音平台发货调用' },
|
||||||
{ value: "local", label: "本地", desc: "本地手动调用" },
|
{ value: 'local', label: '本地', desc: '本地手动调用' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const moduleDocs = [
|
const moduleDocs = [
|
||||||
@ -416,44 +436,12 @@ function copyCardInfo(row) {
|
|||||||
if (row.account) parts.push(row.account);
|
if (row.account) parts.push(row.account);
|
||||||
if (row.password) parts.push(row.password);
|
if (row.password) parts.push(row.password);
|
||||||
if (row.token) parts.push(row.token);
|
if (row.token) parts.push(row.token);
|
||||||
if (!parts.length) {
|
if (!parts.length) { ElMessage.warning('无可复制内容'); return; }
|
||||||
ElMessage.warning("无可复制内容");
|
navigator.clipboard.writeText(parts.join('\n')).then(() => {
|
||||||
return;
|
ElMessage.success('已复制');
|
||||||
}
|
|
||||||
navigator.clipboard.writeText(parts.join("\n")).then(() => {
|
|
||||||
ElMessage.success("已复制");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -485,16 +473,9 @@ async function handleReplenish() {
|
|||||||
<el-button @click="resetQuery">重置</el-button>
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
<el-button type="warning" @click="replenishVisible = true"
|
<el-button type="primary" @click="openAddDialog('single')">添加账号</el-button>
|
||||||
>补号</el-button
|
<el-button type="success" @click="openAddDialog('batch')">批量添加</el-button>
|
||||||
>
|
<el-button @click="markExtractForSelected">批量标记提取</el-button>
|
||||||
<el-button type="primary" @click="openAddDialog('single')"
|
|
||||||
>添加账号</el-button
|
|
||||||
>
|
|
||||||
<el-button type="success" @click="openAddDialog('batch')"
|
|
||||||
>批量添加</el-button
|
|
||||||
>
|
|
||||||
<el-button @click="markExtractForSelected">批量提取</el-button>
|
|
||||||
<el-button @click="apiDocVisible = true">接口说明</el-button>
|
<el-button @click="apiDocVisible = true">接口说明</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -508,16 +489,31 @@ async function handleReplenish() {
|
|||||||
/>
|
/>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
<el-table
|
<div class="table-scroll">
|
||||||
:data="pagedList"
|
<el-table
|
||||||
border
|
class="pool-table"
|
||||||
stripe
|
:data="pagedList"
|
||||||
style="width: 100%"
|
border
|
||||||
:loading="loading"
|
stripe
|
||||||
@selection-change="handleSelectionChange"
|
style="width: 100%"
|
||||||
>
|
:loading="loading"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
<el-table-column type="selection" width="52" />
|
<el-table-column type="selection" width="52" />
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
|
<el-table-column label="账号类型" width="160" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag>{{ typeText(row.type) }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="account" label="账号" min-width="180" show-overflow-tooltip :tooltip-options="tooltipOpts" />
|
||||||
|
<el-table-column prop="password" label="密码" min-width="160" show-overflow-tooltip :tooltip-options="tooltipOpts">
|
||||||
|
<template #default="{ row }">{{ row.password || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Token" min-width="200" show-overflow-tooltip :tooltip-options="tooltipOpts">
|
||||||
|
<template #default="{ row }">{{ row.token || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip :tooltip-options="tooltipOpts" />
|
||||||
<el-table-column label="提取状态" width="100">
|
<el-table-column label="提取状态" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="row.extracted ? 'success' : 'info'">
|
<el-tag :type="row.extracted ? 'success' : 'info'">
|
||||||
@ -525,6 +521,7 @@ async function handleReplenish() {
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column prop="extractedAt" label="提取时间" width="180" />
|
||||||
<el-table-column label="提取平台" width="120">
|
<el-table-column label="提取平台" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag
|
<el-tag
|
||||||
@ -537,43 +534,6 @@ async function handleReplenish() {
|
|||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="账号类型" width="160" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag>{{ typeText(row.type) }}</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
prop="account"
|
|
||||||
label="账号"
|
|
||||||
min-width="180"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
prop="password"
|
|
||||||
label="密码"
|
|
||||||
min-width="160"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
>
|
|
||||||
<template #default="{ row }">{{ row.password || "-" }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Token"
|
|
||||||
min-width="200"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
>
|
|
||||||
<template #default="{ row }">{{ row.token || "-" }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
prop="remark"
|
|
||||||
label="备注"
|
|
||||||
min-width="140"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
/>
|
|
||||||
<el-table-column prop="extractedAt" label="提取时间" width="180" />
|
|
||||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button link type="primary" @click="openDetail(row)"
|
<el-button link type="primary" @click="openDetail(row)"
|
||||||
@ -595,14 +555,15 @@ async function handleReplenish() {
|
|||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pagination-wrap">
|
<div class="pager">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="pagination.page"
|
v-model:current-page="pagination.page"
|
||||||
v-model:page-size="pagination.pageSize"
|
v-model:page-size="pagination.pageSize"
|
||||||
background
|
background
|
||||||
layout="total, prev, pager, next, jumper"
|
:layout="isMobile ? 'prev, pager, next' : 'total, prev, pager, next, jumper'"
|
||||||
:page-sizes="[20, 50, 100]"
|
:page-sizes="[20, 50, 100]"
|
||||||
:total="total"
|
:total="total"
|
||||||
/>
|
/>
|
||||||
@ -630,54 +591,6 @@ async function handleReplenish() {
|
|||||||
@confirm="handleExtract"
|
@confirm="handleExtract"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 批量提取弹窗 -->
|
|
||||||
<el-dialog v-model="batchExtractVisible" title="批量提取" width="420px">
|
|
||||||
<el-form label-width="84px">
|
|
||||||
<el-form-item label="提取平台">
|
|
||||||
<el-select v-model="batchExtractForm.platform" style="width: 100%">
|
|
||||||
<el-option
|
|
||||||
v-for="(v, k) in PLATFORM_MAP"
|
|
||||||
:key="k"
|
|
||||||
:value="k"
|
|
||||||
:label="v.label"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="备注">
|
|
||||||
<el-input
|
|
||||||
v-model="batchExtractForm.remark"
|
|
||||||
type="textarea"
|
|
||||||
:rows="3"
|
|
||||||
placeholder="提取备注(可选)"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="batchExtractVisible = false">取消</el-button>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
:loading="loading"
|
|
||||||
@click="handleBatchExtract"
|
|
||||||
>
|
|
||||||
确认提取
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<!-- 补号弹窗 -->
|
|
||||||
<ReplenishDialog
|
|
||||||
v-model="replenishVisible"
|
|
||||||
:loading="loading"
|
|
||||||
:type="replenishForm.type"
|
|
||||||
:platform="replenishForm.platform"
|
|
||||||
:remark="replenishForm.remark"
|
|
||||||
:platform-map="PLATFORM_MAP"
|
|
||||||
@update:type="(v) => (replenishForm.type = v)"
|
|
||||||
@update:platform="(v) => (replenishForm.platform = v)"
|
|
||||||
@update:remark="(v) => (replenishForm.remark = v)"
|
|
||||||
@confirm="handleReplenish"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 接口说明抽屉 -->
|
<!-- 接口说明抽屉 -->
|
||||||
<el-drawer
|
<el-drawer
|
||||||
v-model="apiDocVisible"
|
v-model="apiDocVisible"
|
||||||
@ -810,12 +723,63 @@ async function handleReplenish() {
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-wrap {
|
.pager {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-scroll {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll :deep(.el-scrollbar__bar.is-horizontal) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pool-table {
|
||||||
|
min-width: 980px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.account-pool-page {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-left,
|
||||||
|
.toolbar-right {
|
||||||
|
width: 100%;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-260,
|
||||||
|
.w-140 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-right .el-button {
|
||||||
|
flex: 1 1 calc(50% - 8px);
|
||||||
|
min-width: 120px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-tabs :deep(.el-tabs__nav-wrap) {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/* 接口说明抽屉 */
|
/* 接口说明抽屉 */
|
||||||
.api-doc {
|
.api-doc {
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: Boolean, default: false },
|
modelValue: { type: Boolean, default: false },
|
||||||
@ -19,9 +20,11 @@ function typeText(type) {
|
|||||||
const PLATFORM_MAP = {
|
const PLATFORM_MAP = {
|
||||||
local: { label: '本地', type: 'info' },
|
local: { label: '本地', type: 'info' },
|
||||||
xianyu: { label: '闲鱼', type: 'warning' },
|
xianyu: { label: '闲鱼', type: 'warning' },
|
||||||
|
taobao: { label: '淘宝', type: 'info' },
|
||||||
pinduoduo: { label: '拼多多', type: 'danger' },
|
pinduoduo: { label: '拼多多', type: 'danger' },
|
||||||
jingdong: { label: '京东', type: 'primary' },
|
jingdong: { label: '京东', type: 'primary' },
|
||||||
douyin: { label: '抖音', type: 'success' },
|
douyin: { label: '抖音', type: 'success' },
|
||||||
|
ziyoushangcheng: { label: '自有商城', type: 'warning' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const platformLabel = computed(() => {
|
const platformLabel = computed(() => {
|
||||||
@ -46,13 +49,37 @@ function onSaveRemark() {
|
|||||||
if (!props.row?.id) return;
|
if (!props.row?.id) return;
|
||||||
emit('save-remark', { id: props.row.id, remark: remarkText.value || '' });
|
emit('save-remark', { id: props.row.id, remark: remarkText.value || '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyAccountPassword() {
|
||||||
|
const account = props.row?.account || '';
|
||||||
|
const password = props.row?.password || '';
|
||||||
|
if (!account && !password) {
|
||||||
|
ElMessage.warning('暂无账号密码可复制');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.clipboard.writeText(`${account}\n${password}`.trim()).then(() => {
|
||||||
|
ElMessage.success('已复制账号+密码');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToken() {
|
||||||
|
const token = props.row?.token || '';
|
||||||
|
if (!token) {
|
||||||
|
ElMessage.warning('暂无 Token 可复制');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.clipboard.writeText(token).then(() => {
|
||||||
|
ElMessage.success('已复制 Token');
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
|
class="pool-detail-dialog"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
title="账号详情"
|
title="账号详情"
|
||||||
width="560px"
|
width="90%"
|
||||||
@update:model-value="(v) => emit('update:modelValue', v)"
|
@update:model-value="(v) => emit('update:modelValue', v)"
|
||||||
>
|
>
|
||||||
<el-descriptions :column="1" border v-if="row">
|
<el-descriptions :column="1" border v-if="row">
|
||||||
@ -69,6 +96,12 @@ function onSaveRemark() {
|
|||||||
<el-tag v-if="row.extractedPlatform" :type="platformType" size="small">{{ platformLabel }}</el-tag>
|
<el-tag v-if="row.extractedPlatform" :type="platformType" size="small">{{ platformLabel }}</el-tag>
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="复制">
|
||||||
|
<div class="copy-actions">
|
||||||
|
<el-button size="small" @click="copyAccountPassword">账号+密码</el-button>
|
||||||
|
<el-button size="small" type="primary" @click="copyToken">Token</el-button>
|
||||||
|
</div>
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="备注">
|
<el-descriptions-item label="备注">
|
||||||
<div class="remark-edit-wrap">
|
<div class="remark-edit-wrap">
|
||||||
<el-input v-model="remarkText" type="textarea" :rows="3" placeholder="请输入备注" />
|
<el-input v-model="remarkText" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||||
@ -93,4 +126,42 @@ function onSaveRemark() {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-edit-wrap .el-button {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-detail-dialog) {
|
||||||
|
max-width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:deep(.pool-detail-dialog) {
|
||||||
|
width: calc(100vw - 24px) !important;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-detail-dialog .el-dialog__body) {
|
||||||
|
padding: 12px;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-detail-dialog .el-descriptions__label) {
|
||||||
|
width: 72px;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-edit-wrap .el-button {
|
||||||
|
width: 100%;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -19,9 +19,10 @@ function typeText(type) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
|
class="pool-extract-dialog"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
title="提取账号"
|
title="提取账号"
|
||||||
width="420px"
|
width="90%"
|
||||||
@update:model-value="(v) => emit('update:modelValue', v)"
|
@update:model-value="(v) => emit('update:modelValue', v)"
|
||||||
>
|
>
|
||||||
<el-form label-width="84px">
|
<el-form label-width="84px">
|
||||||
@ -53,3 +54,35 @@ function typeText(type) {
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.pool-extract-dialog) {
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:deep(.pool-extract-dialog) {
|
||||||
|
width: calc(100vw - 24px) !important;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-dialog__body) {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-form-item__label) {
|
||||||
|
width: 74px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-dialog__footer .el-button) {
|
||||||
|
width: calc(50% - 6px);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-dialog__footer) {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 12px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import Edit from './components/edit.vue';
|
import Edit from './components/edit.vue';
|
||||||
import DetailDialog from './components/detail.vue';
|
import DetailDialog from './components/detail.vue';
|
||||||
import ExtractDialog from './components/extract.vue';
|
import ExtractDialog from './components/extract.vue';
|
||||||
import ReplenishDialog from './components/replenish.vue';
|
import ReplenishDialog from './components/replenish.vue';
|
||||||
|
import PatchDialog from '../components/patch.vue';
|
||||||
import {
|
import {
|
||||||
addAccountPool,
|
addAccountPool,
|
||||||
batchAddAccountPool,
|
batchAddAccountPool,
|
||||||
@ -28,6 +29,7 @@ const batchExtractForm = reactive({ platform: 'local', remark: '' });
|
|||||||
const replenishVisible = ref(false);
|
const replenishVisible = ref(false);
|
||||||
const replenishForm = reactive({ type: 'tk', platform: 'local', remark: '' });
|
const replenishForm = reactive({ type: 'tk', platform: 'local', remark: '' });
|
||||||
const apiDocVisible = ref(false);
|
const apiDocVisible = ref(false);
|
||||||
|
const patchVisible = ref(false);
|
||||||
|
|
||||||
const query = reactive({ keyword: "", status: "" });
|
const query = reactive({ keyword: "", status: "" });
|
||||||
const activeTypeTab = ref("all");
|
const activeTypeTab = ref("all");
|
||||||
@ -39,6 +41,7 @@ const total = ref(0);
|
|||||||
const selectedRows = ref([]);
|
const selectedRows = ref([]);
|
||||||
const detailRow = ref(null);
|
const detailRow = ref(null);
|
||||||
const detailRemarkSaving = ref(false);
|
const detailRemarkSaving = ref(false);
|
||||||
|
const isMobile = ref(false);
|
||||||
const pagination = reactive({ page: 1, pageSize: 30 });
|
const pagination = reactive({ page: 1, pageSize: 30 });
|
||||||
|
|
||||||
const pagedList = computed(() => tableData.value);
|
const pagedList = computed(() => tableData.value);
|
||||||
@ -124,6 +127,30 @@ function openExtractByRow(row) {
|
|||||||
extractVisible.value = true;
|
extractVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openPatchDialog() {
|
||||||
|
patchVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function 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);
|
||||||
|
return parts.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyToClipboard(text) {
|
||||||
|
if (!text) { ElMessage.warning('无可复制内容'); return false; }
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
ElMessage.success('已复制');
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('复制失败,请检查浏览器权限');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleExtract() {
|
async function handleExtract() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -228,20 +255,16 @@ function typeText(type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tooltipOpts = {
|
const tooltipOpts = {
|
||||||
popperClass: "pool-tooltip",
|
popperClass: 'pool-tooltip',
|
||||||
popperStyle: {
|
popperStyle: { maxWidth: '600px', wordBreak: 'break-all', whiteSpace: 'pre-wrap' },
|
||||||
maxWidth: "600px",
|
|
||||||
wordBreak: "break-all",
|
|
||||||
whiteSpace: "pre-wrap",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PLATFORM_MAP = {
|
const PLATFORM_MAP = {
|
||||||
local: { label: "本地", type: "info" },
|
local: { label: '本地', type: 'info' },
|
||||||
xianyu: { label: "闲鱼", type: "warning" },
|
xianyu: { label: '闲鱼', type: 'warning' },
|
||||||
pinduoduo: { label: "拼多多", type: "danger" },
|
pinduoduo: { label: '拼多多', type: 'danger' },
|
||||||
jingdong: { label: "京东", type: "primary" },
|
jingdong: { label: '京东', type: 'primary' },
|
||||||
douyin: { label: "抖音", type: "success" },
|
douyin: { label: '抖音', type: 'success' },
|
||||||
};
|
};
|
||||||
|
|
||||||
function platformText(platform) {
|
function platformText(platform) {
|
||||||
@ -307,40 +330,23 @@ async function fetchList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => { fetchList(); });
|
||||||
fetchList();
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---- 接口说明数据 ----
|
// ---- 接口说明数据 ----
|
||||||
const BASE_URL = "https://api.yunzer.cn";
|
const BASE_URL = "https://api.yunzer.cn";
|
||||||
|
|
||||||
const paramDocs = [
|
const paramDocs = [
|
||||||
{
|
{ name: 'type', required: true, desc: '来源平台,用于标记本次提取来自哪个渠道', values: 'xianyu / pinduoduo / jingdong / douyin / local' },
|
||||||
name: "type",
|
{ name: 'module', required: true, desc: '号池模块,指定从哪个产品的号池提取', values: 'cursor / windsurf / krio' },
|
||||||
required: true,
|
{ name: 'data_type', required: false, desc: '账号类型,不传则提取任意类型', values: 'account / tk / account_tk' },
|
||||||
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 = [
|
const platformDocs = [
|
||||||
{ value: "xianyu", label: "闲鱼", desc: "闲鱼平台发货调用" },
|
{ value: 'xianyu', label: '闲鱼', desc: '闲鱼平台发货调用' },
|
||||||
{ value: "pinduoduo", label: "拼多多", desc: "拼多多平台发货调用" },
|
{ value: 'pinduoduo', label: '拼多多', desc: '拼多多平台发货调用' },
|
||||||
{ value: "jingdong", label: "京东", desc: "京东平台发货调用" },
|
{ value: 'jingdong', label: '京东', desc: '京东平台发货调用' },
|
||||||
{ value: "douyin", label: "抖音", desc: "抖音平台发货调用" },
|
{ value: 'douyin', label: '抖音', desc: '抖音平台发货调用' },
|
||||||
{ value: "local", label: "本地", desc: "本地手动调用" },
|
{ value: 'local', label: '本地', desc: '本地手动调用' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const moduleDocs = [
|
const moduleDocs = [
|
||||||
@ -384,9 +390,7 @@ const errorResp = `// 无可用卡密
|
|||||||
{ "code": 400, "msg": "缺少参数 type(来源平台)" }`;
|
{ "code": 400, "msg": "缺少参数 type(来源平台)" }`;
|
||||||
|
|
||||||
function copyText(text) {
|
function copyText(text) {
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
navigator.clipboard.writeText(text).then(() => { ElMessage.success('已复制'); });
|
||||||
ElMessage.success("已复制");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyCardInfo(row) {
|
function copyCardInfo(row) {
|
||||||
@ -394,14 +398,10 @@ function copyCardInfo(row) {
|
|||||||
if (row.account) parts.push(row.account);
|
if (row.account) parts.push(row.account);
|
||||||
if (row.password) parts.push(row.password);
|
if (row.password) parts.push(row.password);
|
||||||
if (row.token) parts.push(row.token);
|
if (row.token) parts.push(row.token);
|
||||||
if (!parts.length) {
|
if (!parts.length) { ElMessage.warning('无可复制内容'); return; }
|
||||||
ElMessage.warning("无可复制内容");
|
navigator.clipboard.writeText(parts.join('\n')).then(() => { ElMessage.success('已复制'); });
|
||||||
return;
|
|
||||||
}
|
|
||||||
navigator.clipboard.writeText(parts.join("\n")).then(() => {
|
|
||||||
ElMessage.success("已复制");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -431,14 +431,9 @@ function copyCardInfo(row) {
|
|||||||
<el-button @click="resetQuery">重置</el-button>
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
<el-button type="warning" @click="replenishVisible = true">补号</el-button>
|
<el-button type="primary" @click="openAddDialog('single')">添加账号</el-button>
|
||||||
<el-button type="primary" @click="openAddDialog('single')"
|
<el-button type="success" @click="openAddDialog('batch')">批量添加</el-button>
|
||||||
>添加账号</el-button
|
<el-button @click="markExtractForSelected">批量标记提取</el-button>
|
||||||
>
|
|
||||||
<el-button type="success" @click="openAddDialog('batch')"
|
|
||||||
>批量添加</el-button
|
|
||||||
>
|
|
||||||
<el-button @click="markExtractForSelected">批量提取</el-button>
|
|
||||||
<el-button @click="apiDocVisible = true">接口说明</el-button>
|
<el-button @click="apiDocVisible = true">接口说明</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -452,16 +447,20 @@ function copyCardInfo(row) {
|
|||||||
/>
|
/>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
<el-table
|
<el-table :data="pagedList" border stripe style="width: 100%" :loading="loading" @selection-change="handleSelectionChange">
|
||||||
:data="pagedList"
|
|
||||||
border
|
|
||||||
stripe
|
|
||||||
style="width: 100%"
|
|
||||||
:loading="loading"
|
|
||||||
@selection-change="handleSelectionChange"
|
|
||||||
>
|
|
||||||
<el-table-column type="selection" width="52" />
|
<el-table-column type="selection" width="52" />
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
|
<el-table-column label="账号类型" width="160" align="center">
|
||||||
|
<template #default="{ row }"><el-tag>{{ typeText(row.type) }}</el-tag></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="account" label="账号" min-width="180" show-overflow-tooltip :tooltip-options="tooltipOpts" />
|
||||||
|
<el-table-column prop="password" label="密码" min-width="160" show-overflow-tooltip :tooltip-options="tooltipOpts">
|
||||||
|
<template #default="{ row }">{{ row.password || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Token" min-width="200" show-overflow-tooltip :tooltip-options="tooltipOpts">
|
||||||
|
<template #default="{ row }">{{ row.token || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip :tooltip-options="tooltipOpts" />
|
||||||
<el-table-column label="提取状态" width="100">
|
<el-table-column label="提取状态" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="row.extracted ? 'success' : 'info'">{{
|
<el-tag :type="row.extracted ? 'success' : 'info'">{{
|
||||||
@ -469,6 +468,7 @@ function copyCardInfo(row) {
|
|||||||
}}</el-tag>
|
}}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column prop="extractedAt" label="提取时间" width="180" />
|
||||||
<el-table-column label="提取平台" width="110">
|
<el-table-column label="提取平台" width="110">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag
|
<el-tag
|
||||||
@ -481,43 +481,6 @@ function copyCardInfo(row) {
|
|||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="账号类型" width="160" align="center">
|
|
||||||
<template #default="{ row }"
|
|
||||||
><el-tag>{{ typeText(row.type) }}</el-tag></template
|
|
||||||
>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
prop="account"
|
|
||||||
label="账号"
|
|
||||||
min-width="180"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
prop="password"
|
|
||||||
label="密码"
|
|
||||||
min-width="160"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
>
|
|
||||||
<template #default="{ row }">{{ row.password || "-" }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Token"
|
|
||||||
min-width="200"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
>
|
|
||||||
<template #default="{ row }">{{ row.token || "-" }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
prop="remark"
|
|
||||||
label="备注"
|
|
||||||
min-width="140"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
/>
|
|
||||||
<el-table-column prop="extractedAt" label="提取时间" width="180" />
|
|
||||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button link type="primary" @click="openDetail(row)"
|
<el-button link type="primary" @click="openDetail(row)"
|
||||||
@ -539,14 +502,15 @@ function copyCardInfo(row) {
|
|||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pagination-wrap">
|
<div class="pager">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="pagination.page"
|
v-model:current-page="pagination.page"
|
||||||
v-model:page-size="pagination.pageSize"
|
v-model:page-size="pagination.pageSize"
|
||||||
background
|
background
|
||||||
layout="total, prev, pager, next, jumper"
|
:layout="isMobile ? 'prev, pager, next' : 'total, prev, pager, next, jumper'"
|
||||||
:page-sizes="[30, 50, 100]"
|
:page-sizes="[30, 50, 100]"
|
||||||
:total="total"
|
:total="total"
|
||||||
/>
|
/>
|
||||||
@ -574,38 +538,6 @@ function copyCardInfo(row) {
|
|||||||
@confirm="handleExtract"
|
@confirm="handleExtract"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 批量提取弹窗 -->
|
|
||||||
<el-dialog v-model="batchExtractVisible" title="批量提取" width="420px">
|
|
||||||
<el-form label-width="84px">
|
|
||||||
<el-form-item label="提取平台">
|
|
||||||
<el-select v-model="batchExtractForm.platform" style="width: 100%">
|
|
||||||
<el-option v-for="(v, k) in PLATFORM_MAP" :key="k" :value="k" :label="v.label" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="备注">
|
|
||||||
<el-input v-model="batchExtractForm.remark" type="textarea" :rows="3" placeholder="提取备注(可选)" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="batchExtractVisible = false">取消</el-button>
|
|
||||||
<el-button type="primary" :loading="loading" @click="handleBatchExtract">确认提取</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<!-- 补号弹窗 -->
|
|
||||||
<ReplenishDialog
|
|
||||||
v-model="replenishVisible"
|
|
||||||
:loading="loading"
|
|
||||||
:type="replenishForm.type"
|
|
||||||
:platform="replenishForm.platform"
|
|
||||||
:remark="replenishForm.remark"
|
|
||||||
:platform-map="PLATFORM_MAP"
|
|
||||||
@update:type="(v) => (replenishForm.type = v)"
|
|
||||||
@update:platform="(v) => (replenishForm.platform = v)"
|
|
||||||
@update:remark="(v) => (replenishForm.remark = v)"
|
|
||||||
@confirm="handleReplenish"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 接口说明抽屉 -->
|
<!-- 接口说明抽屉 -->
|
||||||
<el-drawer
|
<el-drawer
|
||||||
v-model="apiDocVisible"
|
v-model="apiDocVisible"
|
||||||
@ -695,7 +627,23 @@ function copyCardInfo(row) {
|
|||||||
.w-260 { width: 260px; }
|
.w-260 { width: 260px; }
|
||||||
.w-140 { width: 140px; }
|
.w-140 { width: 140px; }
|
||||||
.type-tabs { margin-bottom: 12px; }
|
.type-tabs { margin-bottom: 12px; }
|
||||||
.pagination-wrap { display: flex; justify-content: flex-end; margin-top: 14px; }
|
.pager { display: flex; justify-content: flex-end; margin-top: 14px; }
|
||||||
|
.table-scroll { width: 100%; overflow-x: hidden; }
|
||||||
|
.pool-table { min-width: 980px; }
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.account-pool-page { padding: 8px; }
|
||||||
|
.toolbar { gap: 8px; }
|
||||||
|
.toolbar-left, .toolbar-right { width: 100%; gap: 8px; }
|
||||||
|
.w-260, .w-140 { width: 100%; }
|
||||||
|
.toolbar-right .el-button {
|
||||||
|
flex: 1 1 calc(50% - 8px);
|
||||||
|
min-width: 120px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.type-tabs :deep(.el-tabs__nav-wrap) { overflow-x: auto; overflow-y: hidden; }
|
||||||
|
.pager { justify-content: center; }
|
||||||
|
}
|
||||||
.api-doc { padding: 0 4px; font-size: 13px; }
|
.api-doc { padding: 0 4px; font-size: 13px; }
|
||||||
.doc-section { margin-bottom: 24px; }
|
.doc-section { margin-bottom: 24px; }
|
||||||
.doc-title { font-weight: 600; font-size: 14px; margin-bottom: 10px; color: #303133; border-left: 3px solid #409eff; padding-left: 8px; }
|
.doc-title { font-weight: 600; font-size: 14px; margin-bottom: 10px; color: #303133; border-left: 3px solid #409eff; padding-left: 8px; }
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: Boolean, default: false },
|
modelValue: { type: Boolean, default: false },
|
||||||
@ -19,9 +20,11 @@ function typeText(type) {
|
|||||||
const PLATFORM_MAP = {
|
const PLATFORM_MAP = {
|
||||||
local: { label: '本地', type: 'info' },
|
local: { label: '本地', type: 'info' },
|
||||||
xianyu: { label: '闲鱼', type: 'warning' },
|
xianyu: { label: '闲鱼', type: 'warning' },
|
||||||
|
taobao: { label: '淘宝', type: 'info' },
|
||||||
pinduoduo: { label: '拼多多', type: 'danger' },
|
pinduoduo: { label: '拼多多', type: 'danger' },
|
||||||
jingdong: { label: '京东', type: 'primary' },
|
jingdong: { label: '京东', type: 'primary' },
|
||||||
douyin: { label: '抖音', type: 'success' },
|
douyin: { label: '抖音', type: 'success' },
|
||||||
|
ziyoushangcheng: { label: '自有商城', type: 'warning' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const platformLabel = computed(() => {
|
const platformLabel = computed(() => {
|
||||||
@ -46,13 +49,37 @@ function onSaveRemark() {
|
|||||||
if (!props.row?.id) return;
|
if (!props.row?.id) return;
|
||||||
emit('save-remark', { id: props.row.id, remark: remarkText.value || '' });
|
emit('save-remark', { id: props.row.id, remark: remarkText.value || '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyAccountPassword() {
|
||||||
|
const account = props.row?.account || '';
|
||||||
|
const password = props.row?.password || '';
|
||||||
|
if (!account && !password) {
|
||||||
|
ElMessage.warning('暂无账号密码可复制');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.clipboard.writeText(`${account}\n${password}`.trim()).then(() => {
|
||||||
|
ElMessage.success('已复制账号+密码');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToken() {
|
||||||
|
const token = props.row?.token || '';
|
||||||
|
if (!token) {
|
||||||
|
ElMessage.warning('暂无 Token 可复制');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.clipboard.writeText(token).then(() => {
|
||||||
|
ElMessage.success('已复制 Token');
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
|
class="pool-detail-dialog"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
title="账号详情"
|
title="账号详情"
|
||||||
width="560px"
|
width="90%"
|
||||||
@update:model-value="(v) => emit('update:modelValue', v)"
|
@update:model-value="(v) => emit('update:modelValue', v)"
|
||||||
>
|
>
|
||||||
<el-descriptions :column="1" border v-if="row">
|
<el-descriptions :column="1" border v-if="row">
|
||||||
@ -69,6 +96,12 @@ function onSaveRemark() {
|
|||||||
<el-tag v-if="row.extractedPlatform" :type="platformType" size="small">{{ platformLabel }}</el-tag>
|
<el-tag v-if="row.extractedPlatform" :type="platformType" size="small">{{ platformLabel }}</el-tag>
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="复制">
|
||||||
|
<div class="copy-actions">
|
||||||
|
<el-button size="small" @click="copyAccountPassword">账号+密码</el-button>
|
||||||
|
<el-button size="small" type="primary" @click="copyToken">Token</el-button>
|
||||||
|
</div>
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="备注">
|
<el-descriptions-item label="备注">
|
||||||
<div class="remark-edit-wrap">
|
<div class="remark-edit-wrap">
|
||||||
<el-input v-model="remarkText" type="textarea" :rows="3" placeholder="请输入备注" />
|
<el-input v-model="remarkText" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||||
@ -93,4 +126,42 @@ function onSaveRemark() {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-edit-wrap .el-button {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-detail-dialog) {
|
||||||
|
max-width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:deep(.pool-detail-dialog) {
|
||||||
|
width: calc(100vw - 24px) !important;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-detail-dialog .el-dialog__body) {
|
||||||
|
padding: 12px;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-detail-dialog .el-descriptions__label) {
|
||||||
|
width: 72px;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-edit-wrap .el-button {
|
||||||
|
width: 100%;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -19,9 +19,10 @@ function typeText(type) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
|
class="pool-extract-dialog"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
title="提取账号"
|
title="提取账号"
|
||||||
width="420px"
|
width="90%"
|
||||||
@update:model-value="(v) => emit('update:modelValue', v)"
|
@update:model-value="(v) => emit('update:modelValue', v)"
|
||||||
>
|
>
|
||||||
<el-form label-width="84px">
|
<el-form label-width="84px">
|
||||||
@ -53,3 +54,35 @@ function typeText(type) {
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.pool-extract-dialog) {
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:deep(.pool-extract-dialog) {
|
||||||
|
width: calc(100vw - 24px) !important;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-dialog__body) {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-form-item__label) {
|
||||||
|
width: 74px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-dialog__footer .el-button) {
|
||||||
|
width: calc(50% - 6px);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pool-extract-dialog .el-dialog__footer) {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 12px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import Edit from './components/edit.vue';
|
import Edit from './components/edit.vue';
|
||||||
import DetailDialog from './components/detail.vue';
|
import DetailDialog from './components/detail.vue';
|
||||||
import ExtractDialog from './components/extract.vue';
|
import ExtractDialog from './components/extract.vue';
|
||||||
import ReplenishDialog from './components/replenish.vue';import {
|
import ReplenishDialog from './components/replenish.vue';import PatchDialog from '../components/patch.vue';
|
||||||
|
import {
|
||||||
addAccountPool,
|
addAccountPool,
|
||||||
batchAddAccountPool,
|
batchAddAccountPool,
|
||||||
extractAccountPool,
|
extractAccountPool,
|
||||||
@ -27,6 +28,7 @@ const batchExtractForm = reactive({ platform: 'local', remark: '' });
|
|||||||
const replenishVisible = ref(false);
|
const replenishVisible = ref(false);
|
||||||
const replenishForm = reactive({ type: 'tk', platform: 'local', remark: '' });
|
const replenishForm = reactive({ type: 'tk', platform: 'local', remark: '' });
|
||||||
const apiDocVisible = ref(false);
|
const apiDocVisible = ref(false);
|
||||||
|
const patchVisible = ref(false);
|
||||||
|
|
||||||
const query = reactive({ keyword: "", status: "" });
|
const query = reactive({ keyword: "", status: "" });
|
||||||
const activeTypeTab = ref("all");
|
const activeTypeTab = ref("all");
|
||||||
@ -38,6 +40,7 @@ const total = ref(0);
|
|||||||
const selectedRows = ref([]);
|
const selectedRows = ref([]);
|
||||||
const detailRow = ref(null);
|
const detailRow = ref(null);
|
||||||
const detailRemarkSaving = ref(false);
|
const detailRemarkSaving = ref(false);
|
||||||
|
const isMobile = ref(false);
|
||||||
const pagination = reactive({ page: 1, pageSize: 30 });
|
const pagination = reactive({ page: 1, pageSize: 30 });
|
||||||
|
|
||||||
const pagedList = computed(() => tableData.value);
|
const pagedList = computed(() => tableData.value);
|
||||||
@ -123,6 +126,30 @@ function openExtractByRow(row) {
|
|||||||
extractVisible.value = true;
|
extractVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openPatchDialog() {
|
||||||
|
patchVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function 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);
|
||||||
|
return parts.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyToClipboard(text) {
|
||||||
|
if (!text) { ElMessage.warning('无可复制内容'); return false; }
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
ElMessage.success('已复制');
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('复制失败,请检查浏览器权限');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleExtract() {
|
async function handleExtract() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -227,20 +254,16 @@ function typeText(type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tooltipOpts = {
|
const tooltipOpts = {
|
||||||
popperClass: "pool-tooltip",
|
popperClass: 'pool-tooltip',
|
||||||
popperStyle: {
|
popperStyle: { maxWidth: '600px', wordBreak: 'break-all', whiteSpace: 'pre-wrap' },
|
||||||
maxWidth: "600px",
|
|
||||||
wordBreak: "break-all",
|
|
||||||
whiteSpace: "pre-wrap",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PLATFORM_MAP = {
|
const PLATFORM_MAP = {
|
||||||
local: { label: "本地", type: "info" },
|
local: { label: '本地', type: 'info' },
|
||||||
xianyu: { label: "闲鱼", type: "warning" },
|
xianyu: { label: '闲鱼', type: 'warning' },
|
||||||
pinduoduo: { label: "拼多多", type: "danger" },
|
pinduoduo: { label: '拼多多', type: 'danger' },
|
||||||
jingdong: { label: "京东", type: "primary" },
|
jingdong: { label: '京东', type: 'primary' },
|
||||||
douyin: { label: "抖音", type: "success" },
|
douyin: { label: '抖音', type: 'success' },
|
||||||
};
|
};
|
||||||
|
|
||||||
function platformText(platform) {
|
function platformText(platform) {
|
||||||
@ -306,40 +329,23 @@ async function fetchList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => { fetchList(); });
|
||||||
fetchList();
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---- 接口说明数据 ----
|
// ---- 接口说明数据 ----
|
||||||
const BASE_URL = "https://api.yunzer.cn";
|
const BASE_URL = "https://api.yunzer.cn";
|
||||||
|
|
||||||
const paramDocs = [
|
const paramDocs = [
|
||||||
{
|
{ name: 'type', required: true, desc: '来源平台,用于标记本次提取来自哪个渠道', values: 'xianyu / pinduoduo / jingdong / douyin / local' },
|
||||||
name: "type",
|
{ name: 'module', required: true, desc: '号池模块,指定从哪个产品的号池提取', values: 'cursor / windsurf / krio' },
|
||||||
required: true,
|
{ name: 'data_type', required: false, desc: '账号类型,不传则提取任意类型', values: 'account / tk / account_tk' },
|
||||||
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 = [
|
const platformDocs = [
|
||||||
{ value: "xianyu", label: "闲鱼", desc: "闲鱼平台发货调用" },
|
{ value: 'xianyu', label: '闲鱼', desc: '闲鱼平台发货调用' },
|
||||||
{ value: "pinduoduo", label: "拼多多", desc: "拼多多平台发货调用" },
|
{ value: 'pinduoduo', label: '拼多多', desc: '拼多多平台发货调用' },
|
||||||
{ value: "jingdong", label: "京东", desc: "京东平台发货调用" },
|
{ value: 'jingdong', label: '京东', desc: '京东平台发货调用' },
|
||||||
{ value: "douyin", label: "抖音", desc: "抖音平台发货调用" },
|
{ value: 'douyin', label: '抖音', desc: '抖音平台发货调用' },
|
||||||
{ value: "local", label: "本地", desc: "本地手动调用" },
|
{ value: 'local', label: '本地', desc: '本地手动调用' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const moduleDocs = [
|
const moduleDocs = [
|
||||||
@ -383,9 +389,7 @@ const errorResp = `// 无可用卡密
|
|||||||
{ "code": 400, "msg": "缺少参数 type(来源平台)" }`;
|
{ "code": 400, "msg": "缺少参数 type(来源平台)" }`;
|
||||||
|
|
||||||
function copyText(text) {
|
function copyText(text) {
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
navigator.clipboard.writeText(text).then(() => { ElMessage.success('已复制'); });
|
||||||
ElMessage.success("已复制");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyCardInfo(row) {
|
function copyCardInfo(row) {
|
||||||
@ -393,14 +397,10 @@ function copyCardInfo(row) {
|
|||||||
if (row.account) parts.push(row.account);
|
if (row.account) parts.push(row.account);
|
||||||
if (row.password) parts.push(row.password);
|
if (row.password) parts.push(row.password);
|
||||||
if (row.token) parts.push(row.token);
|
if (row.token) parts.push(row.token);
|
||||||
if (!parts.length) {
|
if (!parts.length) { ElMessage.warning('无可复制内容'); return; }
|
||||||
ElMessage.warning("无可复制内容");
|
navigator.clipboard.writeText(parts.join('\n')).then(() => { ElMessage.success('已复制'); });
|
||||||
return;
|
|
||||||
}
|
|
||||||
navigator.clipboard.writeText(parts.join("\n")).then(() => {
|
|
||||||
ElMessage.success("已复制");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -430,14 +430,9 @@ function copyCardInfo(row) {
|
|||||||
<el-button @click="resetQuery">重置</el-button>
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
<el-button type="warning" @click="replenishVisible = true">补号</el-button>
|
<el-button type="primary" @click="openAddDialog('single')">添加账号</el-button>
|
||||||
<el-button type="primary" @click="openAddDialog('single')"
|
<el-button type="success" @click="openAddDialog('batch')">批量添加</el-button>
|
||||||
>添加账号</el-button
|
<el-button @click="markExtractForSelected">批量标记提取</el-button>
|
||||||
>
|
|
||||||
<el-button type="success" @click="openAddDialog('batch')"
|
|
||||||
>批量添加</el-button
|
|
||||||
>
|
|
||||||
<el-button @click="markExtractForSelected">批量提取</el-button>
|
|
||||||
<el-button @click="apiDocVisible = true">接口说明</el-button>
|
<el-button @click="apiDocVisible = true">接口说明</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -451,16 +446,20 @@ function copyCardInfo(row) {
|
|||||||
/>
|
/>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
<el-table
|
<el-table :data="pagedList" border stripe style="width: 100%" :loading="loading" @selection-change="handleSelectionChange">
|
||||||
:data="pagedList"
|
|
||||||
border
|
|
||||||
stripe
|
|
||||||
style="width: 100%"
|
|
||||||
:loading="loading"
|
|
||||||
@selection-change="handleSelectionChange"
|
|
||||||
>
|
|
||||||
<el-table-column type="selection" width="52" />
|
<el-table-column type="selection" width="52" />
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
|
<el-table-column label="账号类型" width="160" align="center">
|
||||||
|
<template #default="{ row }"><el-tag>{{ typeText(row.type) }}</el-tag></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="account" label="账号" min-width="180" show-overflow-tooltip :tooltip-options="tooltipOpts" />
|
||||||
|
<el-table-column prop="password" label="密码" min-width="160" show-overflow-tooltip :tooltip-options="tooltipOpts">
|
||||||
|
<template #default="{ row }">{{ row.password || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Token" min-width="200" show-overflow-tooltip :tooltip-options="tooltipOpts">
|
||||||
|
<template #default="{ row }">{{ row.token || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip :tooltip-options="tooltipOpts" />
|
||||||
<el-table-column label="提取状态" width="100">
|
<el-table-column label="提取状态" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="row.extracted ? 'success' : 'info'">{{
|
<el-tag :type="row.extracted ? 'success' : 'info'">{{
|
||||||
@ -468,6 +467,7 @@ function copyCardInfo(row) {
|
|||||||
}}</el-tag>
|
}}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column prop="extractedAt" label="提取时间" width="180" />
|
||||||
<el-table-column label="提取平台" width="110">
|
<el-table-column label="提取平台" width="110">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag
|
<el-tag
|
||||||
@ -480,43 +480,6 @@ function copyCardInfo(row) {
|
|||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="账号类型" width="160" align="center">
|
|
||||||
<template #default="{ row }"
|
|
||||||
><el-tag>{{ typeText(row.type) }}</el-tag></template
|
|
||||||
>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
prop="account"
|
|
||||||
label="账号"
|
|
||||||
min-width="180"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
prop="password"
|
|
||||||
label="密码"
|
|
||||||
min-width="160"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
>
|
|
||||||
<template #default="{ row }">{{ row.password || "-" }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Token"
|
|
||||||
min-width="200"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
>
|
|
||||||
<template #default="{ row }">{{ row.token || "-" }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
prop="remark"
|
|
||||||
label="备注"
|
|
||||||
min-width="140"
|
|
||||||
show-overflow-tooltip
|
|
||||||
:tooltip-options="tooltipOpts"
|
|
||||||
/>
|
|
||||||
<el-table-column prop="extractedAt" label="提取时间" width="180" />
|
|
||||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button link type="primary" @click="openDetail(row)"
|
<el-button link type="primary" @click="openDetail(row)"
|
||||||
@ -538,14 +501,15 @@ function copyCardInfo(row) {
|
|||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pagination-wrap">
|
<div class="pager">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="pagination.page"
|
v-model:current-page="pagination.page"
|
||||||
v-model:page-size="pagination.pageSize"
|
v-model:page-size="pagination.pageSize"
|
||||||
background
|
background
|
||||||
layout="total, prev, pager, next, jumper"
|
:layout="isMobile ? 'prev, pager, next' : 'total, prev, pager, next, jumper'"
|
||||||
:page-sizes="[30, 50, 100]"
|
:page-sizes="[30, 50, 100]"
|
||||||
:total="total"
|
:total="total"
|
||||||
/>
|
/>
|
||||||
@ -573,38 +537,6 @@ function copyCardInfo(row) {
|
|||||||
@confirm="handleExtract"
|
@confirm="handleExtract"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 批量提取弹窗 -->
|
|
||||||
<el-dialog v-model="batchExtractVisible" title="批量提取" width="420px">
|
|
||||||
<el-form label-width="84px">
|
|
||||||
<el-form-item label="提取平台">
|
|
||||||
<el-select v-model="batchExtractForm.platform" style="width: 100%">
|
|
||||||
<el-option v-for="(v, k) in PLATFORM_MAP" :key="k" :value="k" :label="v.label" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="备注">
|
|
||||||
<el-input v-model="batchExtractForm.remark" type="textarea" :rows="3" placeholder="提取备注(可选)" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="batchExtractVisible = false">取消</el-button>
|
|
||||||
<el-button type="primary" :loading="loading" @click="handleBatchExtract">确认提取</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<!-- 补号弹窗 -->
|
|
||||||
<ReplenishDialog
|
|
||||||
v-model="replenishVisible"
|
|
||||||
:loading="loading"
|
|
||||||
:type="replenishForm.type"
|
|
||||||
:platform="replenishForm.platform"
|
|
||||||
:remark="replenishForm.remark"
|
|
||||||
:platform-map="PLATFORM_MAP"
|
|
||||||
@update:type="(v) => (replenishForm.type = v)"
|
|
||||||
@update:platform="(v) => (replenishForm.platform = v)"
|
|
||||||
@update:remark="(v) => (replenishForm.remark = v)"
|
|
||||||
@confirm="handleReplenish"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 接口说明抽屉 -->
|
<!-- 接口说明抽屉 -->
|
||||||
<el-drawer
|
<el-drawer
|
||||||
v-model="apiDocVisible"
|
v-model="apiDocVisible"
|
||||||
@ -694,7 +626,23 @@ function copyCardInfo(row) {
|
|||||||
.w-260 { width: 260px; }
|
.w-260 { width: 260px; }
|
||||||
.w-140 { width: 140px; }
|
.w-140 { width: 140px; }
|
||||||
.type-tabs { margin-bottom: 12px; }
|
.type-tabs { margin-bottom: 12px; }
|
||||||
.pagination-wrap { display: flex; justify-content: flex-end; margin-top: 14px; }
|
.pager { display: flex; justify-content: flex-end; margin-top: 14px; }
|
||||||
|
.table-scroll { width: 100%; overflow-x: hidden; }
|
||||||
|
.pool-table { min-width: 980px; }
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.account-pool-page { padding: 8px; }
|
||||||
|
.toolbar { gap: 8px; }
|
||||||
|
.toolbar-left, .toolbar-right { width: 100%; gap: 8px; }
|
||||||
|
.w-260, .w-140 { width: 100%; }
|
||||||
|
.toolbar-right .el-button {
|
||||||
|
flex: 1 1 calc(50% - 8px);
|
||||||
|
min-width: 120px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.type-tabs :deep(.el-tabs__nav-wrap) { overflow-x: auto; overflow-y: hidden; }
|
||||||
|
.pager { justify-content: center; }
|
||||||
|
}
|
||||||
.api-doc { padding: 0 4px; font-size: 13px; }
|
.api-doc { padding: 0 4px; font-size: 13px; }
|
||||||
.doc-section { margin-bottom: 24px; }
|
.doc-section { margin-bottom: 24px; }
|
||||||
.doc-title { font-weight: 600; font-size: 14px; margin-bottom: 10px; color: #303133; border-left: 3px solid #409eff; padding-left: 8px; }
|
.doc-title { font-weight: 600; font-size: 14px; margin-bottom: 10px; color: #303133; border-left: 3px solid #409eff; padding-left: 8px; }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user