feat: add post page

This commit is contained in:
yanghao05
2025-04-09 16:40:34 +08:00
parent aa8077937f
commit 6151f4e244
8 changed files with 457 additions and 91 deletions

View File

@@ -1,6 +1,8 @@
<script setup>
import { postService } from '@/api/postService'; // Assuming you have a postService for API calls
import { InputText } from 'primevue';
import Badge from 'primevue/badge';
import Button from 'primevue/button';
import Column from 'primevue/column';
import ConfirmDialog from 'primevue/confirmdialog';
@@ -18,22 +20,6 @@ const router = useRouter();
const confirm = useConfirm();
const toast = useToast();
// State for edit dialog (removed "create" functionality since we now have a dedicated page)
const postDialog = ref(false);
const postDialogTitle = ref('编辑文章');
const editMode = ref(true); // Always true now since we only use dialog for editing
const currentPost = ref({
id: null,
title: '',
author: '',
thumbnail: '',
price: 0,
publishedAt: '',
status: '',
mediaTypes: [],
viewCount: 0
});
// Post statuses for filtering
const statusOptions = ref([
{ name: '所有状态', value: null },
@@ -50,46 +36,34 @@ const mediaTypeOptions = ref([
{ name: '音频', value: '音频' }
]);
const selectedStatus = ref(statusOptions.value[0]);
const globalFilterValue = ref('');
const loading = ref(false);
// Sample data - in a real app, this would come from an API
const posts = ref([
{
id: 1,
title: '如何高效学习编程',
author: '张三',
thumbnail: 'https://via.placeholder.com/150',
price: 29.99,
publishedAt: '2023-06-15 14:30',
status: '已发布',
mediaTypes: ['文章', '视频'],
viewCount: 1254
},
{
id: 2,
title: '前端开发最佳实践',
author: '李四',
thumbnail: 'https://via.placeholder.com/150',
price: 49.99,
publishedAt: '2023-06-10 09:15',
status: '草稿',
mediaTypes: ['文章'],
viewCount: 789
},
{
id: 3,
title: '数据分析入门指南',
author: '王五',
thumbnail: 'https://via.placeholder.com/150',
price: 0.00,
publishedAt: '2023-06-05 16:45',
status: '已下架',
mediaTypes: ['文章', '音频'],
viewCount: 2567
}
]);
const posts = ref([]);
// Pagination state
const page = ref(0); // 改为从0开始计数
const limit = ref(10);
const total = ref(0);
// Status mapping
const statusMap = {
1: '已发布',
2: '草稿',
3: '已下架'
};
// Transform assets to media types
const getMediaTypes = (assets) => {
return [...new Set(assets.map(asset => {
switch (asset.type) {
case 'audio': return '音频';
case 'video': return '视频';
default: return '文章';
}
}))];
};
// Navigate to post creation page
const navigateToCreatePost = () => {
@@ -122,17 +96,41 @@ const confirmDelete = (post) => {
});
};
// Fetch posts data
// Format datetime to YY/MM/DD HH:mm:ss
const formatDateTime = (dateStr) => {
const date = new Date(dateStr);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).replace(/\//g, '-');
};
// Calculate price after discount
const calculateDiscountPrice = (price, discount) => {
if (!discount) return price;
return price * (1 - discount / 100);
};
// Fetch posts data with pagination
const fetchPosts = async () => {
loading.value = true;
try {
// In a real app, this would be an API call
// const response = await postApi.getPosts();
// posts.value = response.data;
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 500));
// Using sample data already defined above
const response = await postService.getPosts(page.value + 1, limit.value); // API调用时页码加1
posts.value = response.items.map(post => ({
...post,
status: statusMap[post.status] || '未知',
mediaTypes: getMediaTypes(post.assets),
price: post.price / 100, // Convert cents to yuan
publishedAt: formatDateTime(post.created_at),
viewCount: post.views,
likes: post.likes
}));
total.value = response.total;
} catch (error) {
toast.add({ severity: 'error', summary: '错误', detail: '加载文章失败', life: 3000 });
} finally {
@@ -140,6 +138,13 @@ const fetchPosts = async () => {
}
};
// Handle page change
const onPage = (event) => {
page.value = event.page; // PrimeVue的页码从0开始
limit.value = event.rows;
fetchPosts();
};
onMounted(() => {
fetchPosts();
});
@@ -183,11 +188,12 @@ const formatMediaTypes = (mediaTypes) => {
<InputText v-model="globalFilterValue" placeholder="搜索文章..." class="flex-1" />
</div>
<DataTable v-model:filters="filters" :value="posts" :paginator="true" :rows="5"
<DataTable v-model:filters="filters" :value="posts" :paginator="true" :rows="limit" :totalRecords="total"
:loading="loading" :lazy="true" v-model:first="page" @page="onPage"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
:rowsPerPageOptions="[5, 10, 25]"
currentPageReportTemplate="显示第 {first} 到 {last} 条,共 {totalRecords} 条结果" :loading="loading" dataKey="id"
:globalFilterFields="['title', 'author', 'status', 'mediaTypes']"
:rowsPerPageOptions="[10, 20, 50]"
currentPageReportTemplate="显示第 {first} 到 {last} 条,共 {totalRecords} 条结果" dataKey="id"
:globalFilterFields="['title', 'description', 'status']"
:filters="{ global: { value: globalFilterValue, matchMode: 'contains' } }" stripedRows removableSort
class="p-datatable-sm" responsiveLayout="scroll">
@@ -212,11 +218,25 @@ const formatMediaTypes = (mediaTypes) => {
<Column field="price" header="价格" sortable>
<template #body="{ data }">
<div class="text-sm text-gray-900">{{ formatPrice(data.price) }}</div>
<div class="text-sm text-gray-900">
<span class="line-through text-gray-500" v-if="data.discount">
{{ formatPrice(data.price) }}
</span>
<span :class="{ 'ml-2': data.discount }">
{{ formatPrice(calculateDiscountPrice(data.price, data.discount)) }}
</span>
<span v-if="data.discount" class="ml-2 text-red-500">
(-{{ data.discount }}%)
</span>
</div>
</template>
</Column>
<Column field="publishedAt" header="发布时间" sortable></Column>
<Column field="publishedAt" header="发布时间" sortable>
<template #body="{ data }">
<div class="text-sm text-gray-900">{{ data.publishedAt }}</div>
</template>
</Column>
<Column field="status" header="发布状态" sortable>
<template #body="{ data }">
@@ -246,6 +266,12 @@ const formatMediaTypes = (mediaTypes) => {
</template>
</Column>
<Column field="likes" header="点赞数" sortable>
<template #body="{ data }">
<div class="text-sm text-gray-500">{{ data.likes }}</div>
</template>
</Column>
<Column header="操作" :exportable="false" style="min-width:8rem">
<template #body="{ data }">
<div class="flex justify-center space-x-2">