修复补号功能
This commit is contained in:
parent
3ee4b2e9a8
commit
cf7c94c7e7
13
src/api/home.js
Normal file
13
src/api/home.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按天统计号池已提取(售卖)数量,依据 extracted_time
|
||||||
|
* @param {{ days?: number }} params days 默认 14,最大 90
|
||||||
|
*/
|
||||||
|
export function getAccountPoolDailyExtract(params) {
|
||||||
|
return request({
|
||||||
|
url: '/platform/home/accountPoolDailyExtract',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -54,6 +54,8 @@ function normalizeRow(raw) {
|
|||||||
const p = (v) => String(v).padStart(2, '0');
|
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 `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||||
};
|
};
|
||||||
|
const st = Number(pick('is_extracted', 'isExtracted', 'IsExtracted'));
|
||||||
|
const extractStatus = Number.isFinite(st) ? st : 0;
|
||||||
return {
|
return {
|
||||||
id: pick('id', 'Id', 'ID'),
|
id: pick('id', 'Id', 'ID'),
|
||||||
type: pick('data_type', 'dataType', 'type'),
|
type: pick('data_type', 'dataType', 'type'),
|
||||||
@ -61,7 +63,8 @@ function normalizeRow(raw) {
|
|||||||
password: pick('password', 'Password'),
|
password: pick('password', 'Password'),
|
||||||
token: pick('token', 'Token'),
|
token: pick('token', 'Token'),
|
||||||
remark: pick('remark', 'Remark'),
|
remark: pick('remark', 'Remark'),
|
||||||
extracted: Number(pick('is_extracted', 'isExtracted', 'IsExtracted')) === 1,
|
extractStatus,
|
||||||
|
extracted: extractStatus !== 0,
|
||||||
extractedAt: formatTime(pickNullable('extracted_time', 'extractedAt')),
|
extractedAt: formatTime(pickNullable('extracted_time', 'extractedAt')),
|
||||||
extractedPlatform: pickNullable('extracted_platform', 'extractedPlatform'),
|
extractedPlatform: pickNullable('extracted_platform', 'extractedPlatform'),
|
||||||
createdAt: formatTime(pick('create_time', 'createdAt')),
|
createdAt: formatTime(pick('create_time', 'createdAt')),
|
||||||
|
|||||||
@ -100,7 +100,9 @@ function copyToken() {
|
|||||||
<span class="token-text">{{ row.token || '-' }}</span>
|
<span class="token-text">{{ row.token || '-' }}</span>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="提取状态">
|
<el-descriptions-item label="提取状态">
|
||||||
{{ row.extracted ? '已提取' : '未提取' }}
|
{{
|
||||||
|
row.extractStatus === 2 ? '补号' : row.extracted ? '已提取' : '未提取'
|
||||||
|
}}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="提取时间">{{ row.extractedAt || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="提取时间">{{ row.extractedAt || '-' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="提取平台">
|
<el-descriptions-item label="提取平台">
|
||||||
|
|||||||
@ -20,13 +20,23 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
replenish: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
platformMap: {
|
platformMap: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'update:platform', 'update:remark', 'confirm']);
|
const emit = defineEmits([
|
||||||
|
'update:modelValue',
|
||||||
|
'update:platform',
|
||||||
|
'update:remark',
|
||||||
|
'update:replenish',
|
||||||
|
'confirm',
|
||||||
|
]);
|
||||||
|
|
||||||
function typeText(type) {
|
function typeText(type) {
|
||||||
if (type === 'account') return '账号密码';
|
if (type === 'account') return '账号密码';
|
||||||
@ -47,6 +57,14 @@ function typeText(type) {
|
|||||||
<el-form-item label="提取类型">
|
<el-form-item label="提取类型">
|
||||||
<el-input :model-value="typeText(type)" disabled />
|
<el-input :model-value="typeText(type)" disabled />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="是否补号">
|
||||||
|
<el-switch
|
||||||
|
:model-value="replenish"
|
||||||
|
active-text="是"
|
||||||
|
inactive-text="否"
|
||||||
|
@update:model-value="(v) => emit('update:replenish', v)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="提取平台">
|
<el-form-item label="提取平台">
|
||||||
<el-select
|
<el-select
|
||||||
:model-value="platform"
|
:model-value="platform"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, onUnmounted, reactive, ref, watch } from "vue";
|
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
|
||||||
import { ElMessage } from "element-plus";
|
import { 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";
|
||||||
@ -40,6 +40,7 @@ const extractForm = reactive({
|
|||||||
platform: "local",
|
platform: "local",
|
||||||
type: "account",
|
type: "account",
|
||||||
remark: "",
|
remark: "",
|
||||||
|
replenish: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableData = ref([]);
|
const tableData = ref([]);
|
||||||
@ -54,6 +55,9 @@ const pagination = reactive({
|
|||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 跳转未提取末页时跳过 watcher,避免先被重置到第 1 页 */
|
||||||
|
const skipWatchFetchDuringUnusedJump = ref(false);
|
||||||
|
|
||||||
const pagedList = computed(() => tableData.value);
|
const pagedList = computed(() => tableData.value);
|
||||||
|
|
||||||
function resetQuery() {
|
function resetQuery() {
|
||||||
@ -73,6 +77,7 @@ const typeTabs = computed(() => {
|
|||||||
watch(
|
watch(
|
||||||
() => [query.keyword, query.status, activeTypeTab.value],
|
() => [query.keyword, query.status, activeTypeTab.value],
|
||||||
() => {
|
() => {
|
||||||
|
if (skipWatchFetchDuringUnusedJump.value) return;
|
||||||
pagination.page = 1;
|
pagination.page = 1;
|
||||||
fetchList();
|
fetchList();
|
||||||
},
|
},
|
||||||
@ -81,6 +86,7 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
() => [pagination.page, pagination.pageSize],
|
() => [pagination.page, pagination.pageSize],
|
||||||
() => {
|
() => {
|
||||||
|
if (skipWatchFetchDuringUnusedJump.value) return;
|
||||||
fetchList();
|
fetchList();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -142,6 +148,7 @@ function openExtractDialog() {
|
|||||||
extractTargetRow.value = null;
|
extractTargetRow.value = null;
|
||||||
extractForm.platform = "local";
|
extractForm.platform = "local";
|
||||||
extractForm.type = "account";
|
extractForm.type = "account";
|
||||||
|
extractForm.replenish = false;
|
||||||
extractVisible.value = true;
|
extractVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +157,7 @@ function openExtractByRow(row) {
|
|||||||
extractForm.platform = "local";
|
extractForm.platform = "local";
|
||||||
extractForm.type = row.type;
|
extractForm.type = row.type;
|
||||||
extractForm.remark = row.remark || "";
|
extractForm.remark = row.remark || "";
|
||||||
|
extractForm.replenish = false;
|
||||||
extractVisible.value = true;
|
extractVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,6 +173,14 @@ function buildCopyTextByRow(row) {
|
|||||||
return parts.join('\n');
|
return parts.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rowToText(row) {
|
||||||
|
const parts = [];
|
||||||
|
if (row?.account) parts.push(row.account);
|
||||||
|
if (row?.password) parts.push(row.password);
|
||||||
|
if (row?.token) parts.push(row.token);
|
||||||
|
return parts.join(' / ');
|
||||||
|
}
|
||||||
|
|
||||||
async function copyToClipboard(text) {
|
async function copyToClipboard(text) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
ElMessage.warning('无可复制内容');
|
ElMessage.warning('无可复制内容');
|
||||||
@ -193,6 +209,7 @@ async function handleExtract() {
|
|||||||
type: target.type,
|
type: target.type,
|
||||||
platform: extractForm.platform,
|
platform: extractForm.platform,
|
||||||
remark: extractForm.remark || "",
|
remark: extractForm.remark || "",
|
||||||
|
replenish: !!extractForm.replenish,
|
||||||
});
|
});
|
||||||
if (res?.code !== 200) {
|
if (res?.code !== 200) {
|
||||||
ElMessage.error(res?.msg || "提取失败");
|
ElMessage.error(res?.msg || "提取失败");
|
||||||
@ -270,12 +287,46 @@ async function handleBatchExtract() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleReplenish() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await replenishAccountPool(moduleKey, {
|
||||||
|
type: replenishForm.type,
|
||||||
|
platform: replenishForm.platform,
|
||||||
|
remark: replenishForm.remark || "",
|
||||||
|
});
|
||||||
|
if (res?.code !== 200) {
|
||||||
|
ElMessage.error(res?.msg || "补号失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ElMessage.success("补号成功,已复制到剪贴板");
|
||||||
|
replenishVisible.value = false;
|
||||||
|
const row = normalizeRow(res.data || {});
|
||||||
|
navigator.clipboard.writeText(rowToText(row)).catch(() => {});
|
||||||
|
await fetchList();
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function typeText(type) {
|
function typeText(type) {
|
||||||
if (type === "account") return "账号密码";
|
if (type === "account") return "账号密码";
|
||||||
if (type === "account_tk") return "账号密码+Token";
|
if (type === "account_tk") return "账号密码+Token";
|
||||||
return "Token";
|
return "Token";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractStatusLabel(row) {
|
||||||
|
if (row?.extractStatus === 2) return "补号";
|
||||||
|
if (row?.extracted) return "已提取";
|
||||||
|
return "未提取";
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractStatusTagType(row) {
|
||||||
|
if (row?.extractStatus === 2) return "warning";
|
||||||
|
if (row?.extracted) return "success";
|
||||||
|
return "info";
|
||||||
|
}
|
||||||
|
|
||||||
const tooltipOpts = {
|
const tooltipOpts = {
|
||||||
popperClass: 'pool-tooltip',
|
popperClass: 'pool-tooltip',
|
||||||
popperStyle: { maxWidth: '600px', wordBreak: 'break-all', whiteSpace: 'pre-wrap' },
|
popperStyle: { maxWidth: '600px', wordBreak: 'break-all', whiteSpace: 'pre-wrap' },
|
||||||
@ -318,6 +369,8 @@ function normalizeRow(raw) {
|
|||||||
const p = (v) => String(v).padStart(2, "0");
|
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 `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||||
};
|
};
|
||||||
|
const st = Number(pick("is_extracted", "isExtracted", "IsExtracted"));
|
||||||
|
const extractStatus = Number.isFinite(st) ? st : 0;
|
||||||
return {
|
return {
|
||||||
id: pick("id", "Id", "ID"),
|
id: pick("id", "Id", "ID"),
|
||||||
type: pick("data_type", "dataType", "type"),
|
type: pick("data_type", "dataType", "type"),
|
||||||
@ -325,7 +378,8 @@ function normalizeRow(raw) {
|
|||||||
password: pick("password", "Password"),
|
password: pick("password", "Password"),
|
||||||
token: pick("token", "Token"),
|
token: pick("token", "Token"),
|
||||||
remark: pick("remark", "Remark"),
|
remark: pick("remark", "Remark"),
|
||||||
extracted: Number(pick("is_extracted", "isExtracted", "IsExtracted")) === 1,
|
extractStatus,
|
||||||
|
extracted: extractStatus !== 0,
|
||||||
extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")),
|
extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")),
|
||||||
extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"),
|
extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"),
|
||||||
createdAt: formatTime(pick("create_time", "createdAt")),
|
createdAt: formatTime(pick("create_time", "createdAt")),
|
||||||
@ -354,6 +408,35 @@ async function fetchList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 筛选「未提取」并翻到该条件下的最后一页(当前关键词、账号类型 tab 不变) */
|
||||||
|
async function jumpToLastUnusedPage() {
|
||||||
|
const type = activeTypeTab.value === "all" ? undefined : activeTypeTab.value;
|
||||||
|
const res = await getAccountPoolList(moduleKey, {
|
||||||
|
page: 1,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
keyword: query.keyword || undefined,
|
||||||
|
status: "unused",
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
if (res?.code !== 200) {
|
||||||
|
ElMessage.error(res?.msg || "获取列表失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cnt = Number(res?.data?.total || 0);
|
||||||
|
if (cnt === 0) {
|
||||||
|
ElMessage.warning("暂无未提取数据");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastPage = Math.max(1, Math.ceil(cnt / pagination.pageSize));
|
||||||
|
skipWatchFetchDuringUnusedJump.value = true;
|
||||||
|
pagination.page = lastPage;
|
||||||
|
query.status = "unused";
|
||||||
|
await nextTick();
|
||||||
|
skipWatchFetchDuringUnusedJump.value = false;
|
||||||
|
await fetchList();
|
||||||
|
ElMessage.success(`已跳转未提取第 ${lastPage} 页(共 ${cnt} 条)`);
|
||||||
|
}
|
||||||
|
|
||||||
function updateDeviceType() {
|
function updateDeviceType() {
|
||||||
isMobile.value = window.innerWidth <= 768;
|
isMobile.value = window.innerWidth <= 768;
|
||||||
}
|
}
|
||||||
@ -470,6 +553,14 @@ function copyCardInfo(row) {
|
|||||||
<el-option label="未提取" value="unused" />
|
<el-option label="未提取" value="unused" />
|
||||||
<el-option label="已提取" value="extracted" />
|
<el-option label="已提取" value="extracted" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
title="按当前搜索与账号类型,筛选未提取并跳到最后一页"
|
||||||
|
@click="jumpToLastUnusedPage"
|
||||||
|
>
|
||||||
|
未提取末页
|
||||||
|
</el-button>
|
||||||
<el-button @click="resetQuery">重置</el-button>
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
@ -517,8 +608,8 @@ function copyCardInfo(row) {
|
|||||||
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip :tooltip-options="tooltipOpts" />
|
<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="extractStatusTagType(row)">
|
||||||
{{ row.extracted ? "已提取" : "未提取" }}
|
{{ extractStatusLabel(row) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -541,9 +632,9 @@ function copyCardInfo(row) {
|
|||||||
>详情</el-button
|
>详情</el-button
|
||||||
>
|
>
|
||||||
<el-button
|
<el-button
|
||||||
|
v-if="!row.extractedAt && !row.extracted"
|
||||||
link
|
link
|
||||||
type="warning"
|
type="warning"
|
||||||
:disabled="row.extracted"
|
|
||||||
@click="openExtractByRow(row)"
|
@click="openExtractByRow(row)"
|
||||||
>提取</el-button
|
>提取</el-button
|
||||||
>
|
>
|
||||||
@ -586,9 +677,11 @@ function copyCardInfo(row) {
|
|||||||
:type="extractForm.type"
|
:type="extractForm.type"
|
||||||
:platform="extractForm.platform"
|
:platform="extractForm.platform"
|
||||||
:remark="extractForm.remark"
|
:remark="extractForm.remark"
|
||||||
|
:replenish="extractForm.replenish"
|
||||||
:platform-map="PLATFORM_MAP"
|
:platform-map="PLATFORM_MAP"
|
||||||
@update:platform="(v) => (extractForm.platform = v)"
|
@update:platform="(v) => (extractForm.platform = v)"
|
||||||
@update:remark="(v) => (extractForm.remark = v)"
|
@update:remark="(v) => (extractForm.remark = v)"
|
||||||
|
@update:replenish="(v) => (extractForm.replenish = v)"
|
||||||
@confirm="handleExtract"
|
@confirm="handleExtract"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -90,7 +90,9 @@ function copyToken() {
|
|||||||
<el-descriptions-item label="Token">
|
<el-descriptions-item label="Token">
|
||||||
<span class="token-text">{{ row.token || '-' }}</span>
|
<span class="token-text">{{ row.token || '-' }}</span>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="提取状态">{{ row.extracted ? '已提取' : '未提取' }}</el-descriptions-item>
|
<el-descriptions-item label="提取状态">{{
|
||||||
|
row.extractStatus === 2 ? '补号' : row.extracted ? '已提取' : '未提取'
|
||||||
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item label="提取时间">{{ row.extractedAt || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="提取时间">{{ row.extractedAt || '-' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="提取平台">
|
<el-descriptions-item label="提取平台">
|
||||||
<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>
|
||||||
|
|||||||
@ -5,10 +5,17 @@ const props = defineProps({
|
|||||||
type: { type: String, default: 'account' },
|
type: { type: String, default: 'account' },
|
||||||
platform: { type: String, default: 'local' },
|
platform: { type: String, default: 'local' },
|
||||||
remark: { type: String, default: '' },
|
remark: { type: String, default: '' },
|
||||||
|
replenish: { type: Boolean, default: false },
|
||||||
platformMap: { type: Object, default: () => ({}) },
|
platformMap: { type: Object, default: () => ({}) },
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'update:platform', 'update:remark', 'confirm']);
|
const emit = defineEmits([
|
||||||
|
'update:modelValue',
|
||||||
|
'update:platform',
|
||||||
|
'update:remark',
|
||||||
|
'update:replenish',
|
||||||
|
'confirm',
|
||||||
|
]);
|
||||||
|
|
||||||
function typeText(type) {
|
function typeText(type) {
|
||||||
if (type === 'account') return '账号密码';
|
if (type === 'account') return '账号密码';
|
||||||
@ -29,6 +36,14 @@ function typeText(type) {
|
|||||||
<el-form-item label="提取类型">
|
<el-form-item label="提取类型">
|
||||||
<el-input :model-value="typeText(type)" disabled />
|
<el-input :model-value="typeText(type)" disabled />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="是否补号">
|
||||||
|
<el-switch
|
||||||
|
:model-value="replenish"
|
||||||
|
active-text="是"
|
||||||
|
inactive-text="否"
|
||||||
|
@update:model-value="(v) => emit('update:replenish', v)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="提取平台">
|
<el-form-item label="提取平台">
|
||||||
<el-select
|
<el-select
|
||||||
:model-value="platform"
|
:model-value="platform"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { 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';
|
||||||
@ -34,7 +34,7 @@ const patchVisible = ref(false);
|
|||||||
const query = reactive({ keyword: "", status: "" });
|
const query = reactive({ keyword: "", status: "" });
|
||||||
const activeTypeTab = ref("all");
|
const activeTypeTab = ref("all");
|
||||||
|
|
||||||
const extractForm = reactive({ platform: 'local', type: 'account', remark: '' });
|
const extractForm = reactive({ platform: 'local', type: 'account', remark: '', replenish: false });
|
||||||
|
|
||||||
const tableData = ref([]);
|
const tableData = ref([]);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
@ -44,6 +44,8 @@ const detailRemarkSaving = ref(false);
|
|||||||
const isMobile = ref(false);
|
const isMobile = ref(false);
|
||||||
const pagination = reactive({ page: 1, pageSize: 30 });
|
const pagination = reactive({ page: 1, pageSize: 30 });
|
||||||
|
|
||||||
|
const skipWatchFetchDuringUnusedJump = ref(false);
|
||||||
|
|
||||||
const pagedList = computed(() => tableData.value);
|
const pagedList = computed(() => tableData.value);
|
||||||
|
|
||||||
function resetQuery() {
|
function resetQuery() {
|
||||||
@ -61,6 +63,7 @@ const typeTabs = computed(() => [
|
|||||||
watch(
|
watch(
|
||||||
() => [query.keyword, query.status, activeTypeTab.value],
|
() => [query.keyword, query.status, activeTypeTab.value],
|
||||||
() => {
|
() => {
|
||||||
|
if (skipWatchFetchDuringUnusedJump.value) return;
|
||||||
pagination.page = 1;
|
pagination.page = 1;
|
||||||
fetchList();
|
fetchList();
|
||||||
},
|
},
|
||||||
@ -68,6 +71,7 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
() => [pagination.page, pagination.pageSize],
|
() => [pagination.page, pagination.pageSize],
|
||||||
() => {
|
() => {
|
||||||
|
if (skipWatchFetchDuringUnusedJump.value) return;
|
||||||
fetchList();
|
fetchList();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -124,6 +128,7 @@ function openExtractByRow(row) {
|
|||||||
extractForm.platform = "local";
|
extractForm.platform = "local";
|
||||||
extractForm.type = row.type;
|
extractForm.type = row.type;
|
||||||
extractForm.remark = row.remark || '';
|
extractForm.remark = row.remark || '';
|
||||||
|
extractForm.replenish = false;
|
||||||
extractVisible.value = true;
|
extractVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +162,11 @@ async function handleExtract() {
|
|||||||
const target = extractTargetRow.value;
|
const target = extractTargetRow.value;
|
||||||
if (!target) { ElMessage.warning("未找到提取目标"); return; }
|
if (!target) { ElMessage.warning("未找到提取目标"); return; }
|
||||||
const res = await extractAccountPool(moduleKey, {
|
const res = await extractAccountPool(moduleKey, {
|
||||||
id: target.id, type: target.type, platform: extractForm.platform, remark: extractForm.remark || '',
|
id: target.id,
|
||||||
|
type: target.type,
|
||||||
|
platform: extractForm.platform,
|
||||||
|
remark: extractForm.remark || '',
|
||||||
|
replenish: !!extractForm.replenish,
|
||||||
});
|
});
|
||||||
if (res?.code !== 200) { ElMessage.error(res?.msg || "提取失败"); return; }
|
if (res?.code !== 200) { ElMessage.error(res?.msg || "提取失败"); return; }
|
||||||
ElMessage.success("提取成功");
|
ElMessage.success("提取成功");
|
||||||
@ -294,6 +303,8 @@ function normalizeRow(raw) {
|
|||||||
const p = (v) => String(v).padStart(2, "0");
|
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 `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||||
};
|
};
|
||||||
|
const st = Number(pick("is_extracted", "isExtracted", "IsExtracted"));
|
||||||
|
const extractStatus = Number.isFinite(st) ? st : 0;
|
||||||
return {
|
return {
|
||||||
id: pick("id", "Id", "ID"),
|
id: pick("id", "Id", "ID"),
|
||||||
type: pick("data_type", "dataType", "type"),
|
type: pick("data_type", "dataType", "type"),
|
||||||
@ -301,13 +312,26 @@ function normalizeRow(raw) {
|
|||||||
password: pick("password", "Password"),
|
password: pick("password", "Password"),
|
||||||
token: pick("token", "Token"),
|
token: pick("token", "Token"),
|
||||||
remark: pick("remark", "Remark"),
|
remark: pick("remark", "Remark"),
|
||||||
extracted: Number(pick("is_extracted", "isExtracted", "IsExtracted")) === 1,
|
extractStatus,
|
||||||
|
extracted: extractStatus !== 0,
|
||||||
extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")),
|
extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")),
|
||||||
extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"),
|
extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"),
|
||||||
createdAt: formatTime(pick("create_time", "createdAt")),
|
createdAt: formatTime(pick("create_time", "createdAt")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractStatusLabel(row) {
|
||||||
|
if (row?.extractStatus === 2) return "补号";
|
||||||
|
if (row?.extracted) return "已提取";
|
||||||
|
return "未提取";
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractStatusTagType(row) {
|
||||||
|
if (row?.extractStatus === 2) return "warning";
|
||||||
|
if (row?.extracted) return "success";
|
||||||
|
return "info";
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchList() {
|
async function fetchList() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -330,6 +354,34 @@ async function fetchList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function jumpToLastUnusedPage() {
|
||||||
|
const type = activeTypeTab.value === "all" ? undefined : activeTypeTab.value;
|
||||||
|
const res = await getAccountPoolList(moduleKey, {
|
||||||
|
page: 1,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
keyword: query.keyword || undefined,
|
||||||
|
status: "unused",
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
if (res?.code !== 200) {
|
||||||
|
ElMessage.error(res?.msg || "获取列表失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cnt = Number(res?.data?.total || 0);
|
||||||
|
if (cnt === 0) {
|
||||||
|
ElMessage.warning("暂无未提取数据");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastPage = Math.max(1, Math.ceil(cnt / pagination.pageSize));
|
||||||
|
skipWatchFetchDuringUnusedJump.value = true;
|
||||||
|
pagination.page = lastPage;
|
||||||
|
query.status = "unused";
|
||||||
|
await nextTick();
|
||||||
|
skipWatchFetchDuringUnusedJump.value = false;
|
||||||
|
await fetchList();
|
||||||
|
ElMessage.success(`已跳转未提取第 ${lastPage} 页(共 ${cnt} 条)`);
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => { fetchList(); });
|
onMounted(() => { fetchList(); });
|
||||||
|
|
||||||
// ---- 接口说明数据 ----
|
// ---- 接口说明数据 ----
|
||||||
@ -428,6 +480,14 @@ function copyCardInfo(row) {
|
|||||||
<el-option label="未提取" value="unused" />
|
<el-option label="未提取" value="unused" />
|
||||||
<el-option label="已提取" value="extracted" />
|
<el-option label="已提取" value="extracted" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
title="按当前搜索与账号类型,筛选未提取并跳到最后一页"
|
||||||
|
@click="jumpToLastUnusedPage"
|
||||||
|
>
|
||||||
|
未提取末页
|
||||||
|
</el-button>
|
||||||
<el-button @click="resetQuery">重置</el-button>
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
@ -465,8 +525,8 @@ function copyCardInfo(row) {
|
|||||||
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip :tooltip-options="tooltipOpts" />
|
<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="extractStatusTagType(row)">{{
|
||||||
row.extracted ? "已提取" : "未提取"
|
extractStatusLabel(row)
|
||||||
}}</el-tag>
|
}}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -489,9 +549,9 @@ function copyCardInfo(row) {
|
|||||||
>详情</el-button
|
>详情</el-button
|
||||||
>
|
>
|
||||||
<el-button
|
<el-button
|
||||||
|
v-if="!row.extractedAt && !row.extracted"
|
||||||
link
|
link
|
||||||
type="warning"
|
type="warning"
|
||||||
:disabled="row.extracted"
|
|
||||||
@click="openExtractByRow(row)"
|
@click="openExtractByRow(row)"
|
||||||
>提取</el-button
|
>提取</el-button
|
||||||
>
|
>
|
||||||
@ -534,9 +594,11 @@ function copyCardInfo(row) {
|
|||||||
:type="extractForm.type"
|
:type="extractForm.type"
|
||||||
:platform="extractForm.platform"
|
:platform="extractForm.platform"
|
||||||
:remark="extractForm.remark"
|
:remark="extractForm.remark"
|
||||||
|
:replenish="extractForm.replenish"
|
||||||
:platform-map="PLATFORM_MAP"
|
:platform-map="PLATFORM_MAP"
|
||||||
@update:platform="(v) => (extractForm.platform = v)"
|
@update:platform="(v) => (extractForm.platform = v)"
|
||||||
@update:remark="(v) => (extractForm.remark = v)"
|
@update:remark="(v) => (extractForm.remark = v)"
|
||||||
|
@update:replenish="(v) => (extractForm.replenish = v)"
|
||||||
@confirm="handleExtract"
|
@confirm="handleExtract"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -90,7 +90,9 @@ function copyToken() {
|
|||||||
<el-descriptions-item label="Token">
|
<el-descriptions-item label="Token">
|
||||||
<span class="token-text">{{ row.token || '-' }}</span>
|
<span class="token-text">{{ row.token || '-' }}</span>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="提取状态">{{ row.extracted ? '已提取' : '未提取' }}</el-descriptions-item>
|
<el-descriptions-item label="提取状态">{{
|
||||||
|
row.extractStatus === 2 ? '补号' : row.extracted ? '已提取' : '未提取'
|
||||||
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item label="提取时间">{{ row.extractedAt || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="提取时间">{{ row.extractedAt || '-' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="提取平台">
|
<el-descriptions-item label="提取平台">
|
||||||
<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>
|
||||||
|
|||||||
@ -5,10 +5,17 @@ const props = defineProps({
|
|||||||
type: { type: String, default: 'account' },
|
type: { type: String, default: 'account' },
|
||||||
platform: { type: String, default: 'local' },
|
platform: { type: String, default: 'local' },
|
||||||
remark: { type: String, default: '' },
|
remark: { type: String, default: '' },
|
||||||
|
replenish: { type: Boolean, default: false },
|
||||||
platformMap: { type: Object, default: () => ({}) },
|
platformMap: { type: Object, default: () => ({}) },
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'update:platform', 'update:remark', 'confirm']);
|
const emit = defineEmits([
|
||||||
|
'update:modelValue',
|
||||||
|
'update:platform',
|
||||||
|
'update:remark',
|
||||||
|
'update:replenish',
|
||||||
|
'confirm',
|
||||||
|
]);
|
||||||
|
|
||||||
function typeText(type) {
|
function typeText(type) {
|
||||||
if (type === 'account') return '账号密码';
|
if (type === 'account') return '账号密码';
|
||||||
@ -29,6 +36,14 @@ function typeText(type) {
|
|||||||
<el-form-item label="提取类型">
|
<el-form-item label="提取类型">
|
||||||
<el-input :model-value="typeText(type)" disabled />
|
<el-input :model-value="typeText(type)" disabled />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="是否补号">
|
||||||
|
<el-switch
|
||||||
|
:model-value="replenish"
|
||||||
|
active-text="是"
|
||||||
|
inactive-text="否"
|
||||||
|
@update:model-value="(v) => emit('update:replenish', v)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="提取平台">
|
<el-form-item label="提取平台">
|
||||||
<el-select
|
<el-select
|
||||||
:model-value="platform"
|
:model-value="platform"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { 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';
|
||||||
@ -33,7 +33,7 @@ const patchVisible = ref(false);
|
|||||||
const query = reactive({ keyword: "", status: "" });
|
const query = reactive({ keyword: "", status: "" });
|
||||||
const activeTypeTab = ref("all");
|
const activeTypeTab = ref("all");
|
||||||
|
|
||||||
const extractForm = reactive({ platform: 'local', type: 'account', remark: '' });
|
const extractForm = reactive({ platform: 'local', type: 'account', remark: '', replenish: false });
|
||||||
|
|
||||||
const tableData = ref([]);
|
const tableData = ref([]);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
@ -43,6 +43,8 @@ const detailRemarkSaving = ref(false);
|
|||||||
const isMobile = ref(false);
|
const isMobile = ref(false);
|
||||||
const pagination = reactive({ page: 1, pageSize: 30 });
|
const pagination = reactive({ page: 1, pageSize: 30 });
|
||||||
|
|
||||||
|
const skipWatchFetchDuringUnusedJump = ref(false);
|
||||||
|
|
||||||
const pagedList = computed(() => tableData.value);
|
const pagedList = computed(() => tableData.value);
|
||||||
|
|
||||||
function resetQuery() {
|
function resetQuery() {
|
||||||
@ -60,6 +62,7 @@ const typeTabs = computed(() => [
|
|||||||
watch(
|
watch(
|
||||||
() => [query.keyword, query.status, activeTypeTab.value],
|
() => [query.keyword, query.status, activeTypeTab.value],
|
||||||
() => {
|
() => {
|
||||||
|
if (skipWatchFetchDuringUnusedJump.value) return;
|
||||||
pagination.page = 1;
|
pagination.page = 1;
|
||||||
fetchList();
|
fetchList();
|
||||||
},
|
},
|
||||||
@ -67,6 +70,7 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
() => [pagination.page, pagination.pageSize],
|
() => [pagination.page, pagination.pageSize],
|
||||||
() => {
|
() => {
|
||||||
|
if (skipWatchFetchDuringUnusedJump.value) return;
|
||||||
fetchList();
|
fetchList();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -123,6 +127,7 @@ function openExtractByRow(row) {
|
|||||||
extractForm.platform = "local";
|
extractForm.platform = "local";
|
||||||
extractForm.type = row.type;
|
extractForm.type = row.type;
|
||||||
extractForm.remark = row.remark || '';
|
extractForm.remark = row.remark || '';
|
||||||
|
extractForm.replenish = false;
|
||||||
extractVisible.value = true;
|
extractVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +161,11 @@ async function handleExtract() {
|
|||||||
const target = extractTargetRow.value;
|
const target = extractTargetRow.value;
|
||||||
if (!target) { ElMessage.warning("未找到提取目标"); return; }
|
if (!target) { ElMessage.warning("未找到提取目标"); return; }
|
||||||
const res = await extractAccountPool(moduleKey, {
|
const res = await extractAccountPool(moduleKey, {
|
||||||
id: target.id, type: target.type, platform: extractForm.platform, remark: extractForm.remark || '',
|
id: target.id,
|
||||||
|
type: target.type,
|
||||||
|
platform: extractForm.platform,
|
||||||
|
remark: extractForm.remark || '',
|
||||||
|
replenish: !!extractForm.replenish,
|
||||||
});
|
});
|
||||||
if (res?.code !== 200) { ElMessage.error(res?.msg || "提取失败"); return; }
|
if (res?.code !== 200) { ElMessage.error(res?.msg || "提取失败"); return; }
|
||||||
ElMessage.success("提取成功");
|
ElMessage.success("提取成功");
|
||||||
@ -293,6 +302,8 @@ function normalizeRow(raw) {
|
|||||||
const p = (v) => String(v).padStart(2, "0");
|
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 `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||||
};
|
};
|
||||||
|
const st = Number(pick("is_extracted", "isExtracted", "IsExtracted"));
|
||||||
|
const extractStatus = Number.isFinite(st) ? st : 0;
|
||||||
return {
|
return {
|
||||||
id: pick("id", "Id", "ID"),
|
id: pick("id", "Id", "ID"),
|
||||||
type: pick("data_type", "dataType", "type"),
|
type: pick("data_type", "dataType", "type"),
|
||||||
@ -300,13 +311,26 @@ function normalizeRow(raw) {
|
|||||||
password: pick("password", "Password"),
|
password: pick("password", "Password"),
|
||||||
token: pick("token", "Token"),
|
token: pick("token", "Token"),
|
||||||
remark: pick("remark", "Remark"),
|
remark: pick("remark", "Remark"),
|
||||||
extracted: Number(pick("is_extracted", "isExtracted", "IsExtracted")) === 1,
|
extractStatus,
|
||||||
|
extracted: extractStatus !== 0,
|
||||||
extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")),
|
extractedAt: formatTime(pickNullable("extracted_time", "extractedAt")),
|
||||||
extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"),
|
extractedPlatform: pickNullable("extracted_platform", "extractedPlatform"),
|
||||||
createdAt: formatTime(pick("create_time", "createdAt")),
|
createdAt: formatTime(pick("create_time", "createdAt")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractStatusLabel(row) {
|
||||||
|
if (row?.extractStatus === 2) return "补号";
|
||||||
|
if (row?.extracted) return "已提取";
|
||||||
|
return "未提取";
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractStatusTagType(row) {
|
||||||
|
if (row?.extractStatus === 2) return "warning";
|
||||||
|
if (row?.extracted) return "success";
|
||||||
|
return "info";
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchList() {
|
async function fetchList() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -329,6 +353,34 @@ async function fetchList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function jumpToLastUnusedPage() {
|
||||||
|
const type = activeTypeTab.value === 'all' ? undefined : activeTypeTab.value;
|
||||||
|
const res = await getAccountPoolList(moduleKey, {
|
||||||
|
page: 1,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
keyword: query.keyword || undefined,
|
||||||
|
status: 'unused',
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
if (res?.code !== 200) {
|
||||||
|
ElMessage.error(res?.msg || '获取列表失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cnt = Number(res?.data?.total || 0);
|
||||||
|
if (cnt === 0) {
|
||||||
|
ElMessage.warning('暂无未提取数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastPage = Math.max(1, Math.ceil(cnt / pagination.pageSize));
|
||||||
|
skipWatchFetchDuringUnusedJump.value = true;
|
||||||
|
pagination.page = lastPage;
|
||||||
|
query.status = 'unused';
|
||||||
|
await nextTick();
|
||||||
|
skipWatchFetchDuringUnusedJump.value = false;
|
||||||
|
await fetchList();
|
||||||
|
ElMessage.success(`已跳转未提取第 ${lastPage} 页(共 ${cnt} 条)`);
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => { fetchList(); });
|
onMounted(() => { fetchList(); });
|
||||||
|
|
||||||
// ---- 接口说明数据 ----
|
// ---- 接口说明数据 ----
|
||||||
@ -427,6 +479,14 @@ function copyCardInfo(row) {
|
|||||||
<el-option label="未提取" value="unused" />
|
<el-option label="未提取" value="unused" />
|
||||||
<el-option label="已提取" value="extracted" />
|
<el-option label="已提取" value="extracted" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
title="按当前搜索与账号类型,筛选未提取并跳到最后一页"
|
||||||
|
@click="jumpToLastUnusedPage"
|
||||||
|
>
|
||||||
|
未提取末页
|
||||||
|
</el-button>
|
||||||
<el-button @click="resetQuery">重置</el-button>
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
@ -464,8 +524,8 @@ function copyCardInfo(row) {
|
|||||||
<el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip :tooltip-options="tooltipOpts" />
|
<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="extractStatusTagType(row)">{{
|
||||||
row.extracted ? "已提取" : "未提取"
|
extractStatusLabel(row)
|
||||||
}}</el-tag>
|
}}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -488,9 +548,9 @@ function copyCardInfo(row) {
|
|||||||
>详情</el-button
|
>详情</el-button
|
||||||
>
|
>
|
||||||
<el-button
|
<el-button
|
||||||
|
v-if="!row.extractedAt && !row.extracted"
|
||||||
link
|
link
|
||||||
type="warning"
|
type="warning"
|
||||||
:disabled="row.extracted"
|
|
||||||
@click="openExtractByRow(row)"
|
@click="openExtractByRow(row)"
|
||||||
>提取</el-button
|
>提取</el-button
|
||||||
>
|
>
|
||||||
@ -533,9 +593,11 @@ function copyCardInfo(row) {
|
|||||||
:type="extractForm.type"
|
:type="extractForm.type"
|
||||||
:platform="extractForm.platform"
|
:platform="extractForm.platform"
|
||||||
:remark="extractForm.remark"
|
:remark="extractForm.remark"
|
||||||
|
:replenish="extractForm.replenish"
|
||||||
:platform-map="PLATFORM_MAP"
|
:platform-map="PLATFORM_MAP"
|
||||||
@update:platform="(v) => (extractForm.platform = v)"
|
@update:platform="(v) => (extractForm.platform = v)"
|
||||||
@update:remark="(v) => (extractForm.remark = v)"
|
@update:remark="(v) => (extractForm.remark = v)"
|
||||||
|
@update:replenish="(v) => (extractForm.replenish = v)"
|
||||||
@confirm="handleExtract"
|
@confirm="handleExtract"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<el-row :gutter="20" class="charts-row">
|
<el-row :gutter="20" class="charts-row">
|
||||||
<el-col :xs="24" :sm="24" :md="16">
|
<el-col :xs="24" :sm="24" :md="16">
|
||||||
<el-card shadow="hover" header="用户增长趋势">
|
<el-card shadow="hover" header="Token售卖统计">
|
||||||
<div ref="lineChartRef" class="chart-box"></div>
|
<div ref="lineChartRef" class="chart-box"></div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -36,9 +36,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, shallowRef } from 'vue';
|
import { ref, onMounted, onUnmounted, shallowRef, nextTick } from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
import { User, Pointer, Connection, Histogram } from '@element-plus/icons-vue';
|
import { User, Pointer, Connection, Histogram } from '@element-plus/icons-vue';
|
||||||
|
import { getAccountPoolDailyExtract } from '@/api/home';
|
||||||
|
|
||||||
// --- 类型定义 ---
|
// --- 类型定义 ---
|
||||||
interface SummaryItem {
|
interface SummaryItem {
|
||||||
@ -63,35 +65,94 @@ const summaryData = ref<SummaryItem[]>([
|
|||||||
{ title: '留存率', value: 85, icon: Histogram, color: '#F56C6C', percentage: 1, isUp: true },
|
{ title: '留存率', value: 85, icon: Histogram, color: '#F56C6C', percentage: 1, isUp: true },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// --- 初始化图表 ---
|
function buildSalesLineOption(
|
||||||
const initCharts = () => {
|
days: string[],
|
||||||
// 折线图配置
|
cursor: number[],
|
||||||
if (lineChartRef.value) {
|
kiro: number[],
|
||||||
lineChartInstance.value = echarts.init(lineChartRef.value);
|
windsurf: number[],
|
||||||
lineChartInstance.value.setOption({
|
) {
|
||||||
tooltip: { trigger: 'axis' },
|
const showSymbol = days.length <= 31;
|
||||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['Cursor', 'Kiro', 'Windsurf'],
|
||||||
|
top: 4,
|
||||||
|
},
|
||||||
|
grid: { left: '3%', right: '4%', bottom: '3%', top: 52, containLabel: true },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
data: days,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
minInterval: 1,
|
||||||
},
|
},
|
||||||
yAxis: { type: 'value' },
|
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '新增用户',
|
name: 'Cursor',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
data: [120, 132, 101, 134, 90, 230, 210],
|
showSymbol,
|
||||||
areaStyle: { opacity: 0.3 },
|
data: cursor,
|
||||||
itemStyle: { color: '#3973FF' }
|
areaStyle: { opacity: 0.08 },
|
||||||
}
|
lineStyle: { width: 2 },
|
||||||
]
|
itemStyle: { color: '#3973FF' },
|
||||||
});
|
},
|
||||||
}
|
{
|
||||||
|
name: 'Kiro',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
showSymbol,
|
||||||
|
data: kiro,
|
||||||
|
areaStyle: { opacity: 0.08 },
|
||||||
|
lineStyle: { width: 2 },
|
||||||
|
itemStyle: { color: '#67C23A' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Windsurf',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
showSymbol,
|
||||||
|
data: windsurf,
|
||||||
|
areaStyle: { opacity: 0.08 },
|
||||||
|
lineStyle: { width: 2 },
|
||||||
|
itemStyle: { color: '#E6A23C' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 饼图配置
|
async function loadAccountPoolDailyExtract() {
|
||||||
if (pieChartRef.value) {
|
await nextTick();
|
||||||
|
if (!lineChartRef.value) return;
|
||||||
|
if (!lineChartInstance.value) {
|
||||||
|
lineChartInstance.value = echarts.init(lineChartRef.value);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await getAccountPoolDailyExtract({ days: 14 });
|
||||||
|
if (res?.code !== 200) {
|
||||||
|
ElMessage.error((res as { msg?: string })?.msg || '加载售卖数据失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const d = (res as { data?: Record<string, unknown> }).data || {};
|
||||||
|
const days = Array.isArray(d.days) ? (d.days as string[]) : [];
|
||||||
|
const cursor = Array.isArray(d.cursor) ? (d.cursor as number[]) : [];
|
||||||
|
const kiro = Array.isArray(d.kiro) ? (d.kiro as number[]) : [];
|
||||||
|
const windsurf = Array.isArray(d.windsurf) ? (d.windsurf as number[]) : [];
|
||||||
|
lineChartInstance.value.setOption(buildSalesLineOption(days, cursor, kiro, windsurf), {
|
||||||
|
notMerge: true,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
ElMessage.error('加载售卖数据失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initPieChart = () => {
|
||||||
|
if (!pieChartRef.value) return;
|
||||||
pieChartInstance.value = echarts.init(pieChartRef.value);
|
pieChartInstance.value = echarts.init(pieChartRef.value);
|
||||||
pieChartInstance.value.setOption({
|
pieChartInstance.value.setOption({
|
||||||
tooltip: { trigger: 'item' },
|
tooltip: { trigger: 'item' },
|
||||||
@ -113,12 +174,20 @@ const initCharts = () => {
|
|||||||
{ value: 1048, name: '普通用户' },
|
{ value: 1048, name: '普通用户' },
|
||||||
{ value: 735, name: 'VIP会员' },
|
{ value: 735, name: 'VIP会员' },
|
||||||
{ value: 580, name: '超级管理员' },
|
{ value: 580, name: '超级管理员' },
|
||||||
{ value: 484, name: '运营人员' }
|
{ value: 484, name: '运营人员' },
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const initLineChartShell = () => {
|
||||||
|
if (!lineChartRef.value) return;
|
||||||
|
lineChartInstance.value = echarts.init(lineChartRef.value);
|
||||||
|
lineChartInstance.value.setOption(
|
||||||
|
buildSalesLineOption([], [], [], []),
|
||||||
|
{ notMerge: true },
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- 生命周期与自适应 ---
|
// --- 生命周期与自适应 ---
|
||||||
@ -127,13 +196,20 @@ const handleResize = () => {
|
|||||||
pieChartInstance.value?.resize();
|
pieChartInstance.value?.resize();
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
initCharts();
|
await nextTick();
|
||||||
|
initLineChartShell();
|
||||||
|
initPieChart();
|
||||||
|
void loadAccountPoolDailyExtract();
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
|
lineChartInstance.value?.dispose();
|
||||||
|
lineChartInstance.value = null;
|
||||||
|
pieChartInstance.value?.dispose();
|
||||||
|
pieChartInstance.value = null;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user