Files
pikasTech-HWLAB/web/hwlab-cloud-web/src/components/users/UserDirectoryCollection.vue
T

93 lines
3.9 KiB
Vue

<!-- SPEC: PJ2026-010405 云端控制台. -->
<!-- Implementation reference: draft-2026-07-13-p0-cloud-console. -->
<!-- Responsibility: 以共享表格和实体卡片语义展示用户目录 -->
<script setup lang="ts">
import { Users } from "lucide-vue-next";
import DataGrid, { type DataGridColumn } from "@/components/common/DataGrid.vue";
import SectionFrame from "@/components/layout/SectionFrame.vue";
import type { AdminBillingUserSummary } from "@/types";
defineProps<{ users: AdminBillingUserSummary[]; selectedId: string | null; view: "list" | "grid"; pendingId: string | null }>();
const emit = defineEmits<{ select: [userId: string]; status: [user: AdminBillingUserSummary] }>();
const columns: DataGridColumn[] = [{ key: "user", label: "User" }, { key: "status", label: "Status" }, { key: "credits", label: "Credits" }, { key: "usage", label: "Usage" }, { key: "keys", label: "Keys" }, { key: "actions", label: "Action", align: "right" }];
const userKey = (item: AdminBillingUserSummary): string => String(item.user?.id || "");
const formatNumber = (value: unknown): string => Number(value ?? 0).toLocaleString();
</script>
<template>
<SectionFrame
:title="view === 'grid' ? '用户实体网格' : '用户紧凑列表'"
description="选择动作与状态写操作使用同级控件,避免交互元素嵌套。"
:body-padding="false"
>
<div v-if="view === 'grid'" class="user-entity-grid">
<article
v-for="item in users"
:key="userKey(item)"
class="user-entity-card"
:data-selected="userKey(item) === selectedId"
>
<button
class="user-entity-select"
type="button"
@click="emit('select', item.user?.id || '')"
>
<Users :size="20" aria-hidden="true" />
<strong>{{ item.user?.displayName || item.user?.username }}</strong>
<small>{{ item.user?.email || item.user?.id }}</small>
<dl>
<div><dt>Available</dt><dd>{{ formatNumber(item.credits?.available) }}</dd></div>
<div><dt>Usage</dt><dd>{{ formatNumber(item.usage?.totalCredits) }}</dd></div>
<div><dt>Keys</dt><dd>{{ formatNumber(item.apiKeys?.activeCount) }}</dd></div>
</dl>
</button>
<button
class="table-action"
type="button"
:disabled="pendingId === item.user?.id"
@click="emit('status', item)"
>
{{ item.user?.status === "disabled" ? "启用" : "禁用" }}
</button>
</article>
</div>
<DataGrid
v-else
:columns="columns"
:rows="users"
:row-key="userKey"
:selected-id="selectedId"
aria-label="用户列表"
@select="emit('select', $event.user?.id || '')"
>
<template #cell-user="{ row }">
<strong>{{ row.user?.displayName || row.user?.username }}</strong>
<small>{{ row.user?.email || row.user?.id }} · {{ row.planId || "default" }}</small>
</template>
<template #cell-status="{ row }">
<span class="status-pill" :data-status="row.user?.status">
{{ row.user?.role }} / {{ row.user?.status }}
</span>
</template>
<template #cell-credits="{ row }">
{{ formatNumber(row.credits?.available) }} / {{ formatNumber(row.credits?.reserved) }}
</template>
<template #cell-usage="{ row }">{{ formatNumber(row.usage?.totalCredits) }}</template>
<template #cell-keys="{ row }">
{{ formatNumber(row.apiKeys?.activeCount) }} / {{ formatNumber(row.apiKeys?.totalCount) }}
</template>
<template #cell-actions="{ row }">
<button
class="table-action"
type="button"
:disabled="pendingId === row.user?.id"
@click.stop="emit('status', row)"
>
{{ row.user?.status === "disabled" ? "启用" : "禁用" }}
</button>
</template>
</DataGrid>
</SectionFrame>
</template>