feat: add post buy count
This commit is contained in:
@@ -23,4 +23,7 @@ export const postService = {
|
||||
deletePost(id) {
|
||||
return httpClient.delete(`/admin/posts/${id}`);
|
||||
},
|
||||
sendTo(id, userId) {
|
||||
return httpClient.post(`/admin/posts/${id}/send-to/${userId}`);
|
||||
},
|
||||
}
|
||||
@@ -10,6 +10,9 @@ export const userService = {
|
||||
}
|
||||
});
|
||||
},
|
||||
searchUser(id) {
|
||||
return httpClient.get(`/admin/users/${id}`);
|
||||
},
|
||||
getUser(id) {
|
||||
return httpClient.get(`/admin/users/${id}`);
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import { InputText } from "primevue";
|
||||
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 Dialog from 'primevue/dialog';
|
||||
import Dropdown from "primevue/dropdown";
|
||||
@@ -172,10 +173,45 @@ const previewFile = (file) => {
|
||||
|
||||
previewDialogVisible.value = true;
|
||||
};
|
||||
|
||||
// Add delete related methods
|
||||
const confirmDelete = (file) => {
|
||||
confirm.require({
|
||||
message: `确定要删除文件 "${file.name}" 吗?`,
|
||||
header: '确认删除',
|
||||
icon: 'pi pi-exclamation-triangle',
|
||||
acceptClass: 'p-button-danger',
|
||||
accept: () => handleDelete(file),
|
||||
reject: () => { },
|
||||
acceptLabel: '删除',
|
||||
rejectLabel: '取消'
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = async (file) => {
|
||||
try {
|
||||
await mediaService.delete(file.id);
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: '成功',
|
||||
detail: '文件已删除',
|
||||
life: 3000
|
||||
});
|
||||
fetchMediaFiles();
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: '错误',
|
||||
detail: '删除文件失败',
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Toast />
|
||||
<ConfirmDialog />
|
||||
<!-- Remove ConfirmDialog -->
|
||||
|
||||
<!-- Add Dialog component -->
|
||||
|
||||
@@ -5,10 +5,12 @@ import { getFileTypeByMimeCN } from "@/utils/filetype";
|
||||
import { InputText } from 'primevue';
|
||||
import Badge from 'primevue/badge';
|
||||
|
||||
import { userService } from '@/api/userService';
|
||||
import Button from 'primevue/button';
|
||||
import Column from 'primevue/column';
|
||||
import ConfirmDialog from 'primevue/confirmdialog';
|
||||
import DataTable from 'primevue/datatable';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import Dropdown from 'primevue/dropdown';
|
||||
import ProgressSpinner from 'primevue/progressspinner';
|
||||
import Toast from 'primevue/toast';
|
||||
@@ -193,12 +195,184 @@ const formatPrice = (price) => {
|
||||
const formatMediaTypes = (assets) => {
|
||||
return assets.map(asset => getFileTypeByMimeCN(asset.type)).join(', ');
|
||||
};
|
||||
|
||||
// Add user selection dialog state and methods
|
||||
const sendDialogVisible = ref(false);
|
||||
const selectedPost = ref(null);
|
||||
const selectedUser = ref(null);
|
||||
|
||||
// 修改用户列表相关变量
|
||||
const users = ref({
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 10
|
||||
});
|
||||
const userFirst = ref(0);
|
||||
const userRows = ref(10);
|
||||
const userLoading = ref(false);
|
||||
|
||||
const sendToUser = (post) => {
|
||||
selectedPost.value = post;
|
||||
sendDialogVisible.value = true;
|
||||
searchUsers(''); // 初始加载用户列表
|
||||
};
|
||||
|
||||
// 修改 searchUsers 函数
|
||||
const searchUsers = async (query = '') => {
|
||||
userLoading.value = true;
|
||||
try {
|
||||
const currentPage = (userFirst.value / userRows.value) + 1;
|
||||
const response = await userService.getUsers({
|
||||
page: currentPage,
|
||||
limit: userRows.value,
|
||||
keyword: userFilter.value
|
||||
});
|
||||
users.value = response.data;
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: '错误',
|
||||
detail: '加载用户列表失败',
|
||||
life: 3000
|
||||
});
|
||||
} finally {
|
||||
userLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 修改用户分页事件处理
|
||||
const onUserPage = (event) => {
|
||||
userFirst.value = event.first;
|
||||
userRows.value = event.rows;
|
||||
searchUsers();
|
||||
};
|
||||
|
||||
// 添加用户选择相关变量
|
||||
const userFilter = ref('');
|
||||
const userSearchTimeout = ref(null);
|
||||
|
||||
// 修改用户搜索处理函数
|
||||
const onUserSearch = (event) => {
|
||||
if (userSearchTimeout.value) {
|
||||
clearTimeout(userSearchTimeout.value);
|
||||
}
|
||||
userSearchTimeout.value = setTimeout(() => {
|
||||
userFirst.value = 0; // 重置到第一页
|
||||
searchUsers();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handleSendConfirm = async () => {
|
||||
if (!selectedUser.value) {
|
||||
toast.add({
|
||||
severity: 'warn',
|
||||
summary: '提示',
|
||||
detail: '请选择用户',
|
||||
life: 3000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await postService.sendTo(selectedPost.value.id, selectedUser.value.id);
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: '成功',
|
||||
detail: '赠送成功',
|
||||
life: 3000
|
||||
});
|
||||
sendDialogVisible.value = false;
|
||||
selectedUser.value = null;
|
||||
selectedPost.value = null;
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: '错误',
|
||||
detail: '赠送失败',
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const formatUserLabel = (user) => {
|
||||
return `${user.nickname} (${user.phone})`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Toast />
|
||||
<ConfirmDialog />
|
||||
|
||||
<!-- Add user selection dialog -->
|
||||
<Dialog v-model:visible="sendDialogVisible" modal header="选择用户" :style="{ width: '80vw' }">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="mb-4">
|
||||
<span class="font-bold">文章:</span>
|
||||
{{ selectedPost?.title }}
|
||||
</div>
|
||||
|
||||
<div class="pb-4">
|
||||
<InputText v-model="userFilter" placeholder="搜索用户..." class="w-full" @input="onUserSearch" />
|
||||
</div>
|
||||
|
||||
<!-- 修改 Dialog 中的 DataTable 部分 -->
|
||||
<DataTable v-model:selection="selectedUser" :value="users.items" selectionMode="single"
|
||||
:loading="userLoading" :paginator="true" :rows="userRows" :totalRecords="users.total" :lazy="true"
|
||||
:first="userFirst" @page="onUserPage" dataKey="id" class="p-datatable-sm" responsiveLayout="scroll"
|
||||
style="max-height: 60vh" scrollable>
|
||||
|
||||
<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 selectionMode="single" style="width: 3rem" />
|
||||
|
||||
<Column field="username" header="用户名">
|
||||
<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="phone" header="手机号" />
|
||||
|
||||
<Column field="status" header="状态">
|
||||
<template #body="{ data }">
|
||||
<Badge :value="data.status === 0 ? '活跃' : '禁用'"
|
||||
:severity="data.status === 0 ? 'success' : 'danger'" />
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="created_at" header="注册时间">
|
||||
<template #body="{ data }">
|
||||
{{ formatDate(data.created_at) }}
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="取消" icon="pi pi-times" @click="sendDialogVisible = false" class="p-button-text" />
|
||||
<Button label="确认赠送" icon="pi pi-check" @click="handleSendConfirm" :disabled="!selectedUser" autofocus
|
||||
severity="primary" />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="flex justify-between items-center mb-6 gap-4">
|
||||
<h1 class="text-2xl font-semibold text-gray-800 text-nowrap">文章列表</h1>
|
||||
@@ -252,6 +426,14 @@ const formatMediaTypes = (assets) => {
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="bought_count" header="购买数量" sortable>
|
||||
<template #body="{ data }">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-gray-500">{{ data.bought_count }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column field="updated_at" header="时间信息" sortable>
|
||||
<template #body="{ data }">
|
||||
<div class="flex flex-col">
|
||||
@@ -295,15 +477,13 @@ const formatMediaTypes = (assets) => {
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<Column header="操作" :exportable="false" style="min-width:8rem">
|
||||
<Column header="" :exportable="false" style="min-width:8rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex justify-center space-x-2">
|
||||
<Button icon="pi pi-shopping-cart" rounded text severity="info" @click="sendToUser(data)"
|
||||
aria-label="赠送" />
|
||||
<Button icon="pi pi-pencil" rounded text severity="info" @click="navigateToEditPost(data)"
|
||||
aria-label="编辑" />
|
||||
<Button icon="pi pi-eye" rounded text severity="secondary" @click="viewPost(data)"
|
||||
aria-label="查看" />
|
||||
<Button icon="pi pi-trash" rounded text severity="danger" @click="confirmDelete(data)"
|
||||
aria-label="删除" />
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
Reference in New Issue
Block a user