feat: add post page
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user