设备管理优化

This commit is contained in:
扫地僧 2026-06-16 00:39:26 +08:00
parent fa2dd8548c
commit 0a95690a12
5 changed files with 140 additions and 58 deletions

View File

@ -573,10 +573,7 @@ onUnmounted(() => {
<el-table-column prop="id" label="ID" width="80" />
<el-table-column label="激活码" min-width="260" show-overflow-tooltip>
<template #default="{ row }">
<div class="code-cell">
<span class="code-text">{{ row.code || '-' }}</span>
<el-button v-if="row.code" link type="primary" @click="copyCode(row.code)">复制</el-button>
</div>
</template>
</el-table-column>
<el-table-column label="类型" width="100" align="center">
@ -598,7 +595,7 @@ onUnmounted(() => {
<div class="muted">设备{{ row.machineCode || row.bindDeviceId || '-' }}</div>
</template>
</el-table-column>
<el-table-column prop="ownerUserName" label="归属用户" min-width="130" show-overflow-tooltip />
<!-- <el-table-column prop="ownerUserName" label="归属用户" min-width="130" show-overflow-tooltip /> -->
<el-table-column prop="activatedAt" label="激活时间" width="180">
<template #default="{ row }">{{ row.activatedAt || '-' }}</template>
</el-table-column>
@ -609,9 +606,10 @@ onUnmounted(() => {
<template #default="{ row }">{{ row.createdAt || '-' }}</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="250" fixed="right" align="center">
<el-table-column label="操作" width="290" fixed="right" align="center">
<template #default="{ row }">
<el-button link type="primary" @click="openDetail(row)">详情</el-button>
<el-button v-if="row.code" link type="primary" @click="copyCode(row.code)">复制</el-button>
<el-button link type="warning" @click="openEdit(row)">编辑</el-button>
<el-button link :type="Number(row.status) === 3 ? 'success' : 'info'" @click="handleToggleStatus(row)">
{{ Number(row.status) === 3 ? '启用' : '禁用' }}

View File

@ -1,4 +1,6 @@
<script lang="ts" setup>
import { ElMessage } from 'element-plus';
defineProps({
modelValue: {
type: Boolean,
@ -45,6 +47,18 @@ function statusType(status: unknown) {
if (value === 'failed' || value === '0') return 'danger';
return 'info';
}
function copyCode(code: unknown) {
const text = String(code || '').trim();
if (!text) {
ElMessage.warning('暂无激活码可复制');
return;
}
navigator.clipboard.writeText(text).then(() => {
ElMessage.success('激活码已复制');
});
}
</script>
<template>
@ -54,7 +68,7 @@ function statusType(status: unknown) {
title="激活记录"
size="760px"
direction="rtl"
@update:model-value="(v) => emit('update:modelValue', v)"
@update:model-value="(v: boolean) => emit('update:modelValue', v)"
@opened="emit('refresh')"
>
<div class="record-header">
@ -74,10 +88,23 @@ function statusType(status: unknown) {
</el-tag>
</template>
</el-table-column>
<el-table-column prop="account" label="激活账号" min-width="150" show-overflow-tooltip />
<el-table-column prop="ip" label="IP" min-width="130" show-overflow-tooltip />
<el-table-column prop="clientVersion" label="客户端版本" width="120" />
<el-table-column prop="createdAt" label="激活时间" width="180" />
<el-table-column label="激活码" min-width="260" show-overflow-tooltip>
<template #default="{ row: item }">
<span class="code-text">{{ item.activationCode || item.code || '-' }}</span>
<el-button v-if="item.activationCode || item.code" link type="primary" @click="copyCode(item.activationCode || item.code)">
复制
</el-button>
</template>
</el-table-column>
<el-table-column prop="machineCode" label="机器码" min-width="180" show-overflow-tooltip />
<el-table-column prop="deviceInfo" label="设备信息" min-width="160" show-overflow-tooltip />
<el-table-column prop="durationDays" label="有效天数" width="100" align="center" />
<el-table-column prop="activatedAt" label="激活时间" width="180">
<template #default="{ row: item }">{{ item.activatedAt || item.createdAt || '-' }}</template>
</el-table-column>
<el-table-column prop="expiredAt" label="到期时间" width="180">
<template #default="{ row: item }">{{ item.expiredAt || '-' }}</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
</el-table>
@ -88,8 +115,8 @@ function statusType(status: unknown) {
background
layout="total, prev, pager, next, jumper"
:total="total"
@update:current-page="(v) => emit('update:page', v)"
@update:page-size="(v) => emit('update:pageSize', v)"
@update:current-page="(v: number) => emit('update:page', v)"
@update:page-size="(v: number) => emit('update:pageSize', v)"
/>
</div>
</el-drawer>
@ -121,6 +148,12 @@ function statusType(status: unknown) {
margin-top: 14px;
}
.code-text {
font-family: Consolas, Monaco, 'Courier New', monospace;
font-weight: 600;
word-break: break-all;
}
@media (max-width: 768px) {
:deep(.equipment-record-drawer) {
width: 100vw !important;

View File

@ -16,7 +16,7 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue']);
const statusMap: Record<string, { label: string; type: string }> = {
active: { label: '正常', type: 'success' },
active: { label: '已激活', type: 'success' },
inactive: { label: '未激活', type: 'info' },
disabled: { label: '禁用', type: 'danger' },
expired: { label: '已过期', type: 'warning' },
@ -50,7 +50,7 @@ function copyText(text: unknown, label = '内容') {
:model-value="modelValue"
title="设备详情"
width="760px"
@update:model-value="(v) => emit('update:modelValue', v)"
@update:model-value="(v: boolean) => emit('update:modelValue', v)"
>
<el-descriptions v-if="row" :column="2" border>
<el-descriptions-item label="设备ID">
@ -73,7 +73,7 @@ function copyText(text: unknown, label = '内容') {
<el-descriptions-item label="机器码">
<span class="code-text">{{ display(row.machineCode) }}</span>
</el-descriptions-item>
<el-descriptions-item label="授权码">
<el-descriptions-item label="绑定激活码">
<span class="code-text">{{ display(row.licenseCode) }}</span>
</el-descriptions-item>
<el-descriptions-item label="系统平台">
@ -83,7 +83,7 @@ function copyText(text: unknown, label = '内容') {
{{ display(row.version) }}
</el-descriptions-item>
<el-descriptions-item label="绑定账号">
{{ display(row.account) }}
{{ display(row.raw?.bindAccount || row.raw?.bind_account) }}
</el-descriptions-item>
<el-descriptions-item label="归属用户">
{{ display(row.owner) }}

View File

@ -34,28 +34,31 @@ defineProps({
const emit = defineEmits(['update:modelValue', 'update:page', 'update:pageSize', 'refresh']);
function copyContent(content: unknown) {
function copyContent(content: unknown, label = '提取内容') {
const text = String(content || '').trim();
if (!text) {
ElMessage.warning('暂无提取内容可复制');
ElMessage.warning(`暂无${label}可复制`);
return;
}
navigator.clipboard.writeText(text).then(() => {
ElMessage.success('提取内容已复制');
ElMessage.success(`${label}已复制`);
});
}
function statusText(status: unknown) {
const value = String(status || '');
if (value === 'success' || value === '1') return '成功';
if (value === 'failed' || value === '0') return '失败';
if (value === 'success' || value === '1') return '已提取';
if (value === '2') return '补号';
if (value === '3') return '异常';
if (value === 'failed' || value === '0') return '未提取';
return value || '-';
}
function statusType(status: unknown) {
const value = String(status || '');
if (value === 'success' || value === '1') return 'success';
if (value === 'failed' || value === '0') return 'danger';
if (value === '2') return 'warning';
if (value === '3' || value === 'failed') return 'danger';
return 'info';
}
</script>
@ -67,7 +70,7 @@ function statusType(status: unknown) {
title="提取记录"
size="860px"
direction="rtl"
@update:model-value="(v) => emit('update:modelValue', v)"
@update:model-value="(v: boolean) => emit('update:modelValue', v)"
@opened="emit('refresh')"
>
<div class="record-header">
@ -87,19 +90,47 @@ function statusType(status: unknown) {
</el-tag>
</template>
</el-table-column>
<el-table-column prop="platform" label="提取平台" width="110" />
<el-table-column prop="type" label="提取类型" width="110" />
<el-table-column prop="account" label="提取账号" min-width="150" show-overflow-tooltip />
<el-table-column label="提取内容" min-width="220" show-overflow-tooltip>
<el-table-column prop="platform" label="提取平台" width="110">
<template #default="{ row: item }">{{ item.platform || '-' }}</template>
</el-table-column>
<el-table-column prop="type" label="数据类型" width="110">
<template #default="{ row: item }">{{ item.type || '-' }}</template>
</el-table-column>
<el-table-column label="Cursor账号" min-width="170" show-overflow-tooltip>
<template #default="{ row: item }">
<span>{{ item.content || '-' }}</span>
<el-button v-if="item.content" link type="primary" @click="copyContent(item.content)">
<span>{{ item.account || '-' }}</span>
<el-button v-if="item.account" link type="primary" @click="copyContent(item.account, 'Cursor账号')">
复制
</el-button>
</template>
</el-table-column>
<el-table-column prop="ip" label="IP" min-width="130" show-overflow-tooltip />
<el-table-column prop="createdAt" label="提取时间" width="180" />
<el-table-column label="密码" min-width="150" show-overflow-tooltip>
<template #default="{ row: item }">
<span>{{ item.password || '-' }}</span>
<el-button v-if="item.password" link type="primary" @click="copyContent(item.password, '密码')">
复制
</el-button>
</template>
</el-table-column>
<el-table-column label="Token" min-width="240" show-overflow-tooltip>
<template #default="{ row: item }">
<span>{{ item.token || '-' }}</span>
<el-button v-if="item.token" link type="primary" @click="copyContent(item.token, 'Token')">
复制
</el-button>
</template>
</el-table-column>
<el-table-column label="提取内容" min-width="260" show-overflow-tooltip>
<template #default="{ row: item }">
<span>{{ item.content || '-' }}</span>
<el-button v-if="item.content" link type="primary" @click="copyContent(item.content, '提取内容')">
复制
</el-button>
</template>
</el-table-column>
<el-table-column prop="extractedAt" label="提取时间" width="180">
<template #default="{ row: item }">{{ item.extractedAt || item.createdAt || '-' }}</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
</el-table>
@ -110,8 +141,8 @@ function statusType(status: unknown) {
background
layout="total, prev, pager, next, jumper"
:total="total"
@update:current-page="(v) => emit('update:page', v)"
@update:page-size="(v) => emit('update:pageSize', v)"
@update:current-page="(v: number) => emit('update:page', v)"
@update:page-size="(v: number) => emit('update:pageSize', v)"
/>
</div>
</el-drawer>

View File

@ -15,7 +15,7 @@ import {
getCursorEquipmentExtractRecords,
getCursorEquipmentList,
updateCursorEquipment,
} from '@/api/cursorEquipment';
} from '../../../api/cursorEquipment';
type EquipmentRow = Record<string, any>;
@ -60,10 +60,10 @@ const extractState = reactive({
});
const statusOptions = [
{ label: '未激活', value: 'inactive' },
{ label: '正常', value: 'active' },
{ label: '禁用', value: 'disabled' },
{ label: '已过期', value: 'expired' },
{ label: '未激活', value: 0 },
{ label: '已激活', value: 1 },
{ label: '禁用', value: 3 },
{ label: '已过期', value: 2 },
];
const osOptions = [
@ -74,7 +74,7 @@ const osOptions = [
];
const statusMap: Record<string, { label: string; type: string }> = {
active: { label: '正常', type: 'success' },
active: { label: '已激活', type: 'success' },
inactive: { label: '未激活', type: 'info' },
disabled: { label: '禁用', type: 'danger' },
expired: { label: '已过期', type: 'warning' },
@ -87,7 +87,7 @@ const summary = computed(() => {
const expired = tableData.value.filter((item) => item.status === 'expired').length;
return [
{ label: '当前页设备', value: tableData.value.length, type: 'primary' },
{ label: '正常设备', value: active, type: 'success' },
{ label: '已激活设备', value: active, type: 'success' },
{ label: '未激活', value: inactive, type: 'info' },
{ label: '禁用/过期', value: disabled + expired, type: 'danger' },
];
@ -137,24 +137,35 @@ function formatTime(value: any) {
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
}
function normalizeEquipmentStatus(status: any) {
const value = String(status ?? '').trim();
if (value === '0' || value === 'inactive') return 'inactive';
if (value === '1' || value === 'active' || value === 'normal') return 'active';
if (value === '2' || value === 'expired') return 'expired';
if (value === '3' || value === 'disabled' || value === 'disable') return 'disabled';
return value || 'inactive';
}
function normalizeRow(raw: any): EquipmentRow {
const status = String(pick(raw, 'status', 'Status') || 'inactive');
const status = normalizeEquipmentStatus(pick(raw, 'status', 'Status'));
return {
id: pick(raw, 'id', 'ID', 'Id'),
name: pick(raw, 'name', 'device_name', 'deviceName', 'Name', 'DeviceName'),
deviceNo: pick(raw, 'device_no', 'deviceNo', 'DeviceNo', 'serial_no', 'serialNo'),
machineCode: pick(raw, 'machine_code', 'machineCode', 'MachineCode', 'fingerprint'),
licenseCode: pick(raw, 'license_code', 'licenseCode', 'LicenseCode'),
os: pick(raw, 'os', 'OS', 'platform', 'Platform'),
licenseCode: pick(raw, 'bindActivationCode', 'activationCode', 'activation_code', 'code', 'Code', 'license_code', 'licenseCode', 'LicenseCode'),
os: pick(raw, 'system', 'System', 'os', 'OS', 'platform', 'Platform'),
version: pick(raw, 'version', 'Version', 'client_version', 'clientVersion'),
account: pick(raw, 'account', 'Account', 'email', 'Email'),
account: pick(raw, 'bindActivationCode', 'activationCode', 'activation_code', 'code', 'Code', 'license_code', 'licenseCode', 'LicenseCode'),
owner: pick(raw, 'owner', 'Owner', 'user_name', 'userName', 'tenant_name', 'tenantName'),
status,
activationCount: Number(pick(raw, 'activation_count', 'activationCount', 'ActivationCount') || 0),
extractCount: Number(pick(raw, 'extract_count', 'extractCount', 'ExtractCount') || 0),
lastActivatedAt: formatTime(pick(raw, 'last_activated_at', 'lastActivatedAt', 'activated_at')),
lastActivatedAt: formatTime(pick(raw, 'lastActivatedAt', 'last_activated_at', 'activationTime', 'activation_time', 'activated_at', 'activatedAt')),
lastExtractedAt: formatTime(pick(raw, 'last_extracted_at', 'lastExtractedAt', 'extracted_at')),
expiredAt: formatTime(pick(raw, 'expired_at', 'expiredAt', 'expire_time', 'expireTime')),
expiredAt: formatTime(pick(raw, 'expiredAt', 'expired_at', 'expireTime', 'expire_time')),
createdAt: formatTime(pick(raw, 'create_time', 'created_at', 'createdAt', 'CreatedAt')),
remark: pick(raw, 'remark', 'Remark'),
raw,
@ -164,14 +175,23 @@ function normalizeRow(raw: any): EquipmentRow {
function normalizeRecord(raw: any): EquipmentRow {
return {
id: pick(raw, 'id', 'ID', 'Id'),
status: pick(raw, 'status', 'Status', 'result', 'Result'),
status: pick(raw, 'status', 'Status', 'result', 'Result', 'isExtracted', 'is_extracted'),
activationCode: pick(raw, 'activationCode', 'activation_code', 'code', 'Code'),
durationDays: pick(raw, 'durationDays', 'duration_days'),
machineCode: pick(raw, 'machineCode', 'machine_code', 'MachineCode'),
deviceInfo: pick(raw, 'deviceInfo', 'device_info', 'DeviceInfo'),
expiredAt: formatTime(pick(raw, 'expiredAt', 'expired_at', 'expireTime', 'expire_time')),
activatedAt: formatTime(pick(raw, 'activatedAt', 'activated_at', 'activationTime', 'activation_time')),
account: pick(raw, 'account', 'Account', 'email', 'Email'),
platform: pick(raw, 'platform', 'Platform', 'source', 'Source'),
password: pick(raw, 'password', 'Password'),
token: pick(raw, 'token', 'Token'),
platform: pick(raw, 'platform', 'Platform', 'source', 'Source', 'extractedPlatform', 'extracted_platform'),
type: pick(raw, 'type', 'Type', 'data_type', 'dataType'),
content: pick(raw, 'content', 'Content', 'extract_content', 'extractContent', 'token', 'Token'),
content: pick(raw, 'content', 'Content', 'extract_content', 'extractContent'),
ip: pick(raw, 'ip', 'IP', 'client_ip', 'clientIp'),
clientVersion: pick(raw, 'client_version', 'clientVersion', 'version', 'Version'),
createdAt: formatTime(pick(raw, 'create_time', 'created_at', 'createdAt', 'CreatedAt')),
extractedAt: formatTime(pick(raw, 'extractedAt', 'extracted_at', 'extracted_time')),
remark: pick(raw, 'remark', 'Remark', 'message', 'Message'),
raw,
};
@ -202,7 +222,7 @@ async function fetchList() {
page: pagination.page,
pageSize: pagination.pageSize,
keyword: query.keyword || undefined,
status: query.status || undefined,
status: query.status === '' ? undefined : query.status,
os: query.os || undefined,
});
if (res?.code !== 200) {
@ -401,7 +421,7 @@ onUnmounted(() => {
<div class="toolbar-left">
<el-input
v-model="query.keyword"
placeholder="搜索设备名称 / 编号 / 机器码 / 账号"
placeholder="搜索设备名称 / 编号 / 机器码 / 激活码"
clearable
class="w-300"
/>
@ -450,7 +470,7 @@ onUnmounted(() => {
<el-table-column prop="version" label="版本" width="110" align="center">
<template #default="{ row }">{{ row.version || '-' }}</template>
</el-table-column>
<el-table-column prop="account" label="绑定账号" min-width="160" show-overflow-tooltip />
<el-table-column prop="account" label="绑定激活码" min-width="160" show-overflow-tooltip />
<el-table-column prop="owner" label="归属用户" min-width="130" show-overflow-tooltip />
<el-table-column label="激活/提取" width="120" align="center">
<template #default="{ row }">
@ -492,18 +512,18 @@ onUnmounted(() => {
</div>
</el-card>
<DetailDialog v-model="detailVisible" :row="currentRow" />
<DetailDialog v-model="detailVisible" :row="currentRow || undefined" />
<EditDialog
v-model="editVisible"
:row="currentRow"
:row="currentRow || undefined"
:loading="actionLoading"
@submit="handleSave"
/>
<DeleteDialog
v-model="deleteVisible"
:row="currentRow"
:row="currentRow || undefined"
:loading="actionLoading"
@confirm="handleDelete"
/>
@ -512,7 +532,7 @@ onUnmounted(() => {
v-model="activationVisible"
v-model:page="activationState.page"
v-model:page-size="activationState.pageSize"
:row="currentRow"
:row="currentRow || undefined"
:loading="activationState.loading"
:records="activationState.records"
:total="activationState.total"
@ -523,7 +543,7 @@ onUnmounted(() => {
v-model="extractVisible"
v-model:page="extractState.page"
v-model:page-size="extractState.pageSize"
:row="currentRow"
:row="currentRow || undefined"
:loading="extractState.loading"
:records="extractState.records"
:total="extractState.total"