feat: add users page

This commit is contained in:
yanghao05
2025-04-10 21:04:35 +08:00
parent ed27cb3534
commit 5a63eee1ce
22 changed files with 797 additions and 60 deletions

View File

@@ -66,48 +66,7 @@ const mediaTotalPages = computed(() => {
});
// Sample media data - in a real app, this would come from an API
const mediaItems = ref([
{
id: 1,
fileName: 'sunset-beach.jpg',
fileType: 'Image',
thumbnailUrl: 'https://via.placeholder.com/300x225',
fileSize: '2.4 MB',
uploadTime: 'Today, 10:30 AM'
},
{
id: 2,
fileName: 'presentation.pdf',
fileType: 'PDF',
thumbnailUrl: null,
fileSize: '4.8 MB',
uploadTime: 'Yesterday, 3:45 PM'
},
{
id: 3,
fileName: 'promo_video.mp4',
fileType: 'Video',
thumbnailUrl: null,
fileSize: '24.8 MB',
uploadTime: 'Aug 28, 2023'
},
{
id: 4,
fileName: 'report_q3.docx',
fileType: 'Document',
thumbnailUrl: null,
fileSize: '1.2 MB',
uploadTime: 'Aug 25, 2023'
},
{
id: 5,
fileName: 'podcast_interview.mp3',
fileType: 'Audio',
thumbnailUrl: null,
fileSize: '18.5 MB',
uploadTime: 'Aug 20, 2023'
}
]);
const mediaItems = ref([]);
// Fetch post data by ID
const fetchPost = async (id) => {

View File

@@ -0,0 +1,191 @@
<script setup>
import { userService } from '@/api/userService';
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import Badge from 'primevue/badge';
import Button from 'primevue/button';
import Column from 'primevue/column';
import ConfirmDialog from 'primevue/confirmdialog';
import DataTable from 'primevue/datatable';
import InputText from 'primevue/inputtext';
import ProgressSpinner from 'primevue/progressspinner';
import Toast from 'primevue/toast';
import { useConfirm } from 'primevue/useconfirm';
import { useToast } from 'primevue/usetoast';
import { onMounted, ref } from 'vue';
const toast = useToast();
const confirm = useConfirm();
const globalFilterValue = ref('');
const loading = ref(false);
const searchTimeout = ref(null);
const filters = ref({
global: { value: null, matchMode: 'contains' },
status: { value: null, matchMode: 'equals' }
});
// Table state
const users = ref({
items: [],
total: 0,
page: 1,
limit: 10
});
// DataTable pagination state
const first = ref(0);
const rows = ref(10);
// 配置 dayjs
dayjs.extend(utc);
dayjs.extend(timezone);
const fetchUsers = async () => {
loading.value = true;
try {
// Calculate current page from first and rows
const currentPage = (first.value / rows.value) + 1;
const response = await userService.getUsers({
page: currentPage,
limit: rows.value,
keyword: globalFilterValue.value
});
users.value = response.data;
} catch (error) {
console.error('Failed to fetch users:', error);
toast.add({ severity: 'error', summary: '错误', detail: '加载用户数据失败', life: 3000 });
} finally {
loading.value = false;
}
};
const onPage = (event) => {
first.value = event.first;
rows.value = event.rows;
fetchUsers();
};
const onSearch = (event) => {
if (searchTimeout.value) {
clearTimeout(searchTimeout.value);
}
searchTimeout.value = setTimeout(() => {
first.value = 0; // Reset to first page when searching
fetchUsers();
}, 300);
};
const formatDate = (date) => {
return dayjs.tz(date, 'Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
};
const handleDelete = (user) => {
confirm.require({
message: `确定要删除用户 "${user.username}" 吗?`,
header: '确认删除',
icon: 'pi pi-exclamation-triangle',
acceptClass: 'p-button-danger',
accept: async () => {
try {
await userService.deleteUser(user.id);
toast.add({ severity: 'success', summary: '成功', detail: '用户已删除', life: 3000 });
fetchUsers();
} catch (error) {
console.error('Failed to delete user:', error);
toast.add({ severity: 'error', summary: '错误', detail: '删除用户失败', life: 3000 });
}
}
});
};
onMounted(() => {
fetchUsers();
});
</script>
<template>
<Toast />
<ConfirmDialog />
<div class="w-full">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-semibold text-gray-800">用户列表</h1>
</div>
<div class="card">
<div class="pb-10 flex">
<InputText v-model="globalFilterValue" placeholder="搜索用户..." class="flex-1" @input="onSearch" />
</div>
<DataTable v-model:filters="filters" :value="users.items" :paginator="true" :rows="rows"
:totalRecords="users.total" :loading="loading" :lazy="true" :first="first" @page="onPage"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
:rowsPerPageOptions="[10, 25, 50]"
currentPageReportTemplate="显示第 {first} 到 {last} 条,共 {totalRecords} 条结果" dataKey="id"
:globalFilterFields="['username', 'open_id']" stripedRows removableSort class="p-datatable-sm"
responsiveLayout="scroll">
<template #empty>
<div class="text-center p-4">未找到用户</div>
</template>
<template #loading>
<div class="flex flex-col items-center justify-center p-4">
<ProgressSpinner style="width:50px;height:50px" />
<span class="mt-2">加载用户数据...</span>
</div>
</template>
<Column field="id" header="ID" sortable></Column>
<Column field="username" header="用户名" sortable>
<template #body="{ data }">
<div class="flex items-center space-x-3">
<div class="avatar">
<div class="mask mask-squircle w-12 h-12">
<img :src="data.avatar" :alt="data.username" />
</div>
</div>
<div>
<div class="font-bold">{{ data.username }}</div>
</div>
</div>
</template>
</Column>
<Column field="open_id" header="OpenID" sortable></Column>
<Column field="status" header="状态" sortable>
<template #body="{ data }">
<Badge :value="data.status === 0 ? '活跃' : '禁用'"
:severity="data.status === 0 ? 'success' : 'danger'" />
</template>
</Column>
<Column field="created_at" header="创建时间" sortable>
<template #body="{ data }">
{{ formatDate(data.created_at) }}
</template>
</Column>
<Column field="updated_at" header="更新时间" sortable>
<template #body="{ data }">
{{ formatDate(data.updated_at) }}
</template>
</Column>
<Column header="操作" :exportable="false" style="min-width:8rem">
<template #body="{ data }">
<div class="flex justify-center space-x-2">
<Button icon="pi pi-trash" rounded text severity="danger" @click="handleDelete(data)"
aria-label="删除" />
</div>
</template>
</Column>
</DataTable>
</div>
</div>
</template>