platform-vue/src/views/accountpool/kiro/components/detail.vue
2026-05-22 08:29:48 +08:00

601 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { computed, reactive, ref, watch } from "vue";
import { ElMessage } from "element-plus";
const props = defineProps({
modelValue: {
type: Boolean,
default: false,
},
row: {
type: Object,
default: null,
},
saveLoading: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:modelValue", "save-remark", "detail-action"]);
const remarkText = ref("");
const remarkDialogVisible = ref(false);
const platformDialogVisible = ref(false);
const unavailableDialogVisible = ref(false);
const unextractDialogVisible = ref(false);
const platformForm = reactive({ platform: "local" });
const TYPE_MAP = {
account: { label: "账号密码", type: "success" },
account_tk: { label: "账号密码+Token", type: "primary" },
tk: { label: "Token", type: "warning" },
};
const PLATFORM_MAP = {
local: { label: "本地", type: "info" },
xianyu: { label: "闲鱼", type: "warning" },
taobao: { label: "淘宝", type: "info" },
pinduoduo: { label: "拼多多", type: "danger" },
jingdong: { label: "京东", type: "primary" },
douyin: { label: "抖音", type: "success" },
ziyoushangcheng: { label: "自有商城", type: "warning" },
};
const statusInfo = computed(() => {
const status = Number(props.row?.extractStatus || 0);
if (status === 2) return { label: "补号", type: "warning" };
if (status === 3) return { label: "续杯", type: "primary" };
if (props.row?.extracted) return { label: "已提取", type: "success" };
return { label: "未提取", type: "info" };
});
const typeInfo = computed(() => {
return (
TYPE_MAP[props.row?.type] || { label: props.row?.type || "-", type: "info" }
);
});
const platformInfo = computed(() => {
const key = props.row?.extractedPlatform;
if (!key) return { label: "-", type: "info" };
return PLATFORM_MAP[key] || { label: key, type: "info" };
});
const isUsedInfo = computed(() => {
const raw = props.row?.isUsed;
if (raw === null || raw === undefined || raw === "") {
return { label: "未探测", type: "info" };
}
const n = Number(raw);
if (n === 1) return { label: "可用", type: "success" };
if (n === 0) return { label: "已用完", type: "danger" };
return { label: String(raw), type: "info" };
});
const hasAccountPassword = computed(
() => !!(props.row?.account || props.row?.password),
);
const hasToken = computed(() => !!props.row?.token);
watch(
() => props.row,
(row) => {
remarkText.value = row?.remark || "";
platformForm.platform = row?.extractedPlatform || "local";
},
{ immediate: true },
);
function closeDialog() {
emit("update:modelValue", false);
}
function openRemarkDialog() {
remarkText.value = props.row?.remark || "";
remarkDialogVisible.value = true;
}
function onSaveRemark() {
if (!props.row?.id) return;
emit("save-remark", { id: props.row.id, remark: remarkText.value || "" });
remarkDialogVisible.value = false;
}
function onSetUnavailable() {
if (!props.row?.id) return;
emit("detail-action", { action: "unavailable", id: props.row.id });
unavailableDialogVisible.value = false;
}
function onUpdatePlatform() {
if (!props.row?.id) return;
emit("detail-action", {
action: "platform",
id: props.row.id,
platform: platformForm.platform,
});
platformDialogVisible.value = false;
}
function onUnextract() {
if (!props.row?.id) return;
emit("detail-action", { action: "unextract", id: props.row.id });
unextractDialogVisible.value = false;
}
async function copyText(text, successText) {
const val = String(text || "").trim();
if (!val) {
ElMessage.warning("暂无可复制内容");
return;
}
try {
await navigator.clipboard.writeText(val);
ElMessage.success(successText || "已复制");
} catch {
ElMessage.error("复制失败,请检查浏览器权限");
}
}
function copyAccountPassword() {
const parts = [];
if (props.row?.account) parts.push(props.row.account);
if (props.row?.password) parts.push(props.row.password);
copyText(parts.join("\n"), "已复制账号+密码");
}
function copyToken() {
copyText(props.row?.token, "已复制 Token");
}
function copyAll() {
const parts = [];
if (props.row?.account) parts.push(`账号:${props.row.account}`);
if (props.row?.password) parts.push(`密码:${props.row.password}`);
if (props.row?.token) parts.push(`Token${props.row.token}`);
copyText(parts.join("\n"), "已复制完整账号信息");
}
</script>
<template>
<el-dialog
class="pool-detail-dialog"
:model-value="modelValue"
width="760px"
destroy-on-close
:show-close="false"
@update:model-value="(v) => emit('update:modelValue', v)"
>
<template #header>
<div class="detail-header">
<div>
<div class="detail-title">账号详情</div>
<div class="detail-subtitle">
通过弹窗执行账号状态、平台、备注等维护操作
</div>
</div>
<div class="header-actions">
<el-button circle plain @click="closeDialog">×</el-button>
</div>
</div>
</template>
<div v-if="row" class="detail-body">
<div class="info-grid">
<div class="info-card">
<div class="info-label">ID</div>
<div class="info-value">{{ row?.id || "-" }}</div>
</div>
<div class="info-card">
<div class="info-label">账号类型</div>
<div class="info-value">
<el-tag :type="typeInfo.type" round>{{ typeInfo.label }}</el-tag>
</div>
</div>
<div class="info-card">
<div class="info-label">提取状态</div>
<div class="info-value">
<el-tag :type="statusInfo.type" effect="dark" round>
{{ statusInfo.label }}
</el-tag>
</div>
</div>
<div class="info-card">
<div class="info-label">提取平台</div>
<div class="info-value">
<el-tag
v-if="row.extractedPlatform"
:type="platformInfo.type"
size="small"
>
{{ platformInfo.label }}
</el-tag>
<span v-else>-</span>
</div>
</div>
<div class="info-card">
<div class="info-label">提取时间</div>
<div class="info-value">{{ row.extractedAt || "-" }}</div>
</div>
<div class="info-card">
<div class="info-label">可用检测</div>
<div class="info-value">
<el-tag :type="isUsedInfo.type" round>
{{ isUsedInfo.label }}
</el-tag>
</div>
</div>
<div class="info-card">
<div class="info-label">账号</div>
<div class="info-value">{{ row.account || "-" }}</div>
</div>
<div class="info-card">
<div class="info-label">密码</div>
<div class="info-value">{{ row.password || "-" }}</div>
</div>
</div>
<div class="section-card">
<div class="section-head">
<div>
<div class="section-title">Token</div>
<div class="section-subtitle">
长 Token 已做自动换行,便于检查与复制
</div>
</div>
<el-button
size="small"
type="primary"
plain
:disabled="!hasToken"
@click="copyToken"
>
复制 Token
</el-button>
</div>
<pre class="token-box">{{ row.token || "暂无 Token" }}</pre>
</div>
<div class="section-card">
<div class="section-head">
<div>
<div class="section-title">快捷功能</div>
<div class="section-subtitle">按使用场景复制账号信息</div>
</div>
</div>
<div class="copy-actions">
<el-button
:disabled="!hasAccountPassword"
@click="copyAccountPassword"
>
复制账号+密码
</el-button>
<el-button
type="primary"
plain
:disabled="!hasToken"
@click="copyToken"
>
复制 Token
</el-button>
<el-button
type="success"
plain
:disabled="!hasAccountPassword && !hasToken"
@click="copyAll"
>
复制全部
</el-button>
</div>
</div>
<div class="section-card">
<div class="section-head">
<div>
<div class="section-title">维护操作</div>
<div class="section-subtitle">
点击按钮后打开确认/编辑弹窗,再执行对应操作
</div>
</div>
</div>
<div class="copy-actions">
<el-button
type="danger"
plain
@click="unavailableDialogVisible = true"
>
改不可用
</el-button>
<el-button
type="warning"
plain
@click="platformDialogVisible = true"
>
改平台
</el-button>
<el-button type="info" plain @click="unextractDialogVisible = true">
反提取
</el-button>
<el-button type="primary" plain @click="openRemarkDialog">
改备注
</el-button>
</div>
</div>
<div class="section-card">
<div class="section-head">
<div>
<div class="section-title">备注</div>
<div class="section-subtitle">备注改为弹窗编辑,当前仅展示</div>
</div>
</div>
<div class="remark-display">{{ row.remark || "暂无备注" }}</div>
</div>
</div>
</el-dialog>
<el-dialog
v-model="unavailableDialogVisible"
title="改不可用"
width="420px"
append-to-body
>
<el-alert type="warning" :closable="false">
确认将当前账号标记为不可用/已用完?
</el-alert>
<template #footer>
<el-button @click="unavailableDialogVisible = false">取消</el-button>
<el-button type="danger" :loading="saveLoading" @click="onSetUnavailable">
确认改不可用
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="platformDialogVisible"
title="改平台"
width="420px"
append-to-body
>
<el-form label-width="84px">
<el-form-item label="提取平台">
<el-select v-model="platformForm.platform" style="width: 100%">
<el-option
v-for="(v, k) in PLATFORM_MAP"
:key="k"
:label="v.label"
:value="k"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="platformDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saveLoading" @click="onUpdatePlatform">
确认修改
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="unextractDialogVisible"
title="反提取"
width="420px"
append-to-body
>
<el-alert type="warning" :closable="false">
反提取会把账号恢复为未提取,并清空提取时间与提取平台。
</el-alert>
<template #footer>
<el-button @click="unextractDialogVisible = false">取消</el-button>
<el-button type="warning" :loading="saveLoading" @click="onUnextract">
确认反提取
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="remarkDialogVisible"
title="改备注"
width="520px"
append-to-body
>
<el-input
v-model="remarkText"
type="textarea"
:rows="5"
resize="none"
placeholder="请输入备注"
/>
<template #footer>
<el-button @click="remarkDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saveLoading" @click="onSaveRemark">
保存备注
</el-button>
</template>
</el-dialog>
</template>
<style scoped>
:deep(.pool-detail-dialog) {
max-width: calc(100vw - 28px);
border-radius: 18px;
overflow: hidden;
}
:deep(.pool-detail-dialog .el-dialog__header) {
padding: 18px 22px;
margin: 0;
border-bottom: 1px solid #eef0f5;
}
:deep(.pool-detail-dialog .el-dialog__body) {
padding: 18px 22px 22px;
background: #f6f8fb;
}
:deep(.el-tag) {
border-radius: 4px !important;
}
.detail-header,
.header-actions,
.section-head,
.copy-actions {
display: flex;
align-items: center;
}
.detail-header {
justify-content: space-between;
gap: 14px;
}
.detail-title {
font-size: 18px;
font-weight: 700;
color: #1f2937;
}
.detail-subtitle,
.section-subtitle {
margin-top: 4px;
font-size: 12px;
color: #909399;
}
.header-actions {
gap: 10px;
}
.header-actions .el-button {
font-size: 18px;
font-weight: 300;
}
.detail-body {
display: flex;
flex-direction: column;
gap: 14px;
}
.section-card,
.info-card {
background: #fff;
border: 1px solid #edf0f6;
box-shadow: 0 10px 28px rgba(31, 41, 55, 0.06);
}
.info-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.info-card {
border-radius: 14px;
padding: 14px;
min-width: 0;
}
.info-label {
color: #909399;
font-size: 12px;
margin-bottom: 8px;
}
.info-value {
color: #303133;
font-size: 14px;
font-weight: 600;
word-break: break-all;
min-height: 20px;
}
.section-card {
border-radius: 16px;
padding: 16px;
}
.section-head {
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}
.section-title {
font-size: 15px;
font-weight: 700;
color: #303133;
}
.token-box {
margin: 0;
padding: 14px;
max-height: 220px;
overflow: auto;
border-radius: 12px;
background: #111827;
color: #d1e7ff;
font-size: 12px;
line-height: 1.7;
white-space: pre-wrap;
word-break: break-all;
}
.copy-actions {
flex-wrap: wrap;
gap: 10px;
}
.copy-actions .el-button {
margin: 0;
}
.remark-display {
padding: 12px;
min-height: 42px;
border-radius: 10px;
background: #f8fafc;
color: #303133;
line-height: 1.6;
white-space: pre-wrap;
word-break: break-all;
}
@media (max-width: 768px) {
:deep(.pool-detail-dialog) {
width: calc(100vw - 24px) !important;
margin: 0 auto;
}
:deep(.pool-detail-dialog .el-dialog__header) {
padding: 14px 14px;
}
:deep(.pool-detail-dialog .el-dialog__body) {
padding: 12px;
max-height: 76vh;
overflow-y: auto;
}
.detail-header,
.section-head {
align-items: flex-start;
}
.section-head {
flex-direction: column;
}
.info-grid {
grid-template-columns: 1fr;
}
.copy-actions .el-button,
.section-head .el-button {
width: 100%;
}
}
</style>