This commit introduces the foundational structure for the admin dashboard, focusing on the member management feature. Key additions include: - A new page at `/admin/manage/members` to display and manage members. - An `AddModal` component with a comprehensive form for creating new members, featuring validation with Zod and input masking. - A reusable `PhoneInput` component with country code selection, backed by a new `useCountries` composable and a full country dataset. - A custom, user-friendly global error page (`error.vue`) to handle application errors gracefully. - Updated dashboard sidebar navigation to include the new member management section. - Added recommended VS Code extensions and settings to improve developer experience.
166 lines
3.4 KiB
Vue
166 lines
3.4 KiB
Vue
<template>
|
|
<UDashboardPanel>
|
|
<template #header>
|
|
<UDashboardNavbar :title="pageTitle" toggle>
|
|
<template #leading>
|
|
<UDashboardSidebarCollapse />
|
|
</template>
|
|
|
|
<template #right>
|
|
<AdminManageMembersAddModal />
|
|
</template>
|
|
</UDashboardNavbar>
|
|
</template>
|
|
<template #body>
|
|
<UTable
|
|
:data="data"
|
|
:columns="columns"
|
|
:loading="status === 'pending'"
|
|
class="flex-1 h-80"
|
|
/>
|
|
</template>
|
|
</UDashboardPanel>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import type { TableColumn } from "@nuxt/ui";
|
|
import type { Row } from '@tanstack/vue-table'
|
|
import { useClipboard } from '@vueuse/core'
|
|
|
|
const pageTitle = "会员籍管理";
|
|
definePageMeta({
|
|
layout: "admin-dashboard",
|
|
title: pageTitle,
|
|
});
|
|
useHead({
|
|
title: pageTitle,
|
|
});
|
|
|
|
const UAvatar = resolveComponent("UAvatar");
|
|
const UButton = resolveComponent("UButton");
|
|
const UBadge = resolveComponent("UBadge");
|
|
const UDropdownMenu = resolveComponent("UDropdownMenu");
|
|
|
|
const toast = useToast()
|
|
const { copy } = useClipboard()
|
|
|
|
type User = {
|
|
id: number;
|
|
name: string;
|
|
username: string;
|
|
email: string;
|
|
avatar: { src: string };
|
|
company: { name: string };
|
|
};
|
|
|
|
const { data, status } = await useFetch<User[]>(
|
|
"https://jsonplaceholder.typicode.com/users",
|
|
{
|
|
key: "table-users",
|
|
transform: (data) => {
|
|
return (
|
|
data?.map((user) => ({
|
|
...user,
|
|
avatar: {
|
|
src: `https://i.pravatar.cc/120?img=${user.id}`,
|
|
alt: `${user.name} avatar`,
|
|
},
|
|
})) || []
|
|
);
|
|
},
|
|
lazy: true,
|
|
}
|
|
);
|
|
|
|
const columns: TableColumn<User>[] = [
|
|
{
|
|
accessorKey: "id",
|
|
header: "ID",
|
|
},
|
|
{
|
|
accessorKey: "name",
|
|
header: "Name",
|
|
cell: ({ row }) => {
|
|
return h("div", { class: "flex items-center gap-3" }, [
|
|
h(UAvatar, {
|
|
...row.original.avatar,
|
|
size: "lg",
|
|
}),
|
|
h("div", undefined, [
|
|
h("p", { class: "font-medium text-highlighted" }, row.original.name),
|
|
h("p", { class: "" }, `@${row.original.username}`),
|
|
]),
|
|
]);
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "email",
|
|
header: "Email",
|
|
},
|
|
{
|
|
accessorKey: "company",
|
|
header: "Company",
|
|
cell: ({ row }) => row.original.company.name,
|
|
},
|
|
{
|
|
id: "actions",
|
|
cell: ({ row }) => {
|
|
return h(
|
|
"div",
|
|
{ class: "text-right" },
|
|
h(
|
|
UDropdownMenu,
|
|
{
|
|
content: {
|
|
align: "end",
|
|
},
|
|
items: getRowItems(row),
|
|
"aria-label": "Actions dropdown",
|
|
},
|
|
() =>
|
|
h(UButton, {
|
|
icon: "i-lucide-ellipsis-vertical",
|
|
color: "neutral",
|
|
variant: "ghost",
|
|
class: "ml-auto",
|
|
"aria-label": "Actions dropdown",
|
|
})
|
|
)
|
|
);
|
|
},
|
|
},
|
|
];
|
|
|
|
function getRowItems(row: Row<object>) {
|
|
return [
|
|
{
|
|
type: 'label',
|
|
label: 'Actions'
|
|
},
|
|
{
|
|
label: 'Copy payment ID',
|
|
onSelect() {
|
|
copy(row.original.id)
|
|
|
|
toast.add({
|
|
title: 'Payment ID copied to clipboard!',
|
|
color: 'success',
|
|
icon: 'i-lucide-circle-check'
|
|
})
|
|
}
|
|
},
|
|
{
|
|
type: 'separator'
|
|
},
|
|
{
|
|
label: 'View customer'
|
|
},
|
|
{
|
|
label: 'View payment details'
|
|
}
|
|
]
|
|
}
|
|
</script>
|
|
|
|
<style></style>
|