feat: add superadmin user library detail
This commit is contained in:
@@ -186,6 +186,45 @@ export const UserService = {
|
||||
items: normalizeItems(data?.items)
|
||||
};
|
||||
},
|
||||
async listUserLibrary(userID, { page, limit, tenant_id, tenant_code, tenant_name, content_id, keyword, status, order_id, order_status, paid_at_from, paid_at_to, accessed_at_from, accessed_at_to, sortField, sortOrder } = {}) {
|
||||
if (!userID) throw new Error('userID is required');
|
||||
|
||||
const iso = (d) => {
|
||||
if (!d) return undefined;
|
||||
const date = d instanceof Date ? d : new Date(d);
|
||||
if (Number.isNaN(date.getTime())) return undefined;
|
||||
return date.toISOString();
|
||||
};
|
||||
|
||||
const query = {
|
||||
page,
|
||||
limit,
|
||||
tenant_id,
|
||||
tenant_code,
|
||||
tenant_name,
|
||||
content_id,
|
||||
keyword,
|
||||
status,
|
||||
order_id,
|
||||
order_status,
|
||||
paid_at_from: iso(paid_at_from),
|
||||
paid_at_to: iso(paid_at_to),
|
||||
accessed_at_from: iso(accessed_at_from),
|
||||
accessed_at_to: iso(accessed_at_to)
|
||||
};
|
||||
if (sortField && sortOrder) {
|
||||
if (sortOrder === 1) query.asc = sortField;
|
||||
if (sortOrder === -1) query.desc = sortField;
|
||||
}
|
||||
|
||||
const data = await requestJson(`/super/v1/users/${userID}/library`, { query });
|
||||
return {
|
||||
page: data?.page ?? page ?? 1,
|
||||
limit: data?.limit ?? limit ?? 10,
|
||||
total: data?.total ?? 0,
|
||||
items: normalizeItems(data?.items)
|
||||
};
|
||||
},
|
||||
async listUserFollowing(userID, { page, limit, tenant_id, code, name, status, created_at_from, created_at_to, sortField, sortOrder } = {}) {
|
||||
if (!userID) throw new Error('userID is required');
|
||||
|
||||
|
||||
@@ -52,6 +52,12 @@ const followingTenantsTotal = ref(0);
|
||||
const followingTenantsPage = ref(1);
|
||||
const followingTenantsRows = ref(10);
|
||||
|
||||
const libraryItems = ref([]);
|
||||
const libraryLoading = ref(false);
|
||||
const libraryTotal = ref(0);
|
||||
const libraryPage = ref(1);
|
||||
const libraryRows = ref(10);
|
||||
|
||||
const rechargeOrders = ref([]);
|
||||
const rechargeOrdersLoading = ref(false);
|
||||
const rechargeOrdersTotal = ref(0);
|
||||
@@ -60,6 +66,23 @@ const rechargeOrdersRows = ref(10);
|
||||
|
||||
const tabValue = ref('owned');
|
||||
|
||||
const accessStatusOptions = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: 'active', value: 'active' },
|
||||
{ label: 'revoked', value: 'revoked' },
|
||||
{ label: 'expired', value: 'expired' }
|
||||
];
|
||||
|
||||
const orderStatusOptions = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: 'created', value: 'created' },
|
||||
{ label: 'paid', value: 'paid' },
|
||||
{ label: 'refunding', value: 'refunding' },
|
||||
{ label: 'refunded', value: 'refunded' },
|
||||
{ label: 'canceled', value: 'canceled' },
|
||||
{ label: 'failed', value: 'failed' }
|
||||
];
|
||||
|
||||
function formatDate(value) {
|
||||
if (!value) return '-';
|
||||
if (String(value).startsWith('0001-01-01')) return '-';
|
||||
@@ -124,6 +147,19 @@ function getOrderStatusSeverity(value) {
|
||||
}
|
||||
}
|
||||
|
||||
function getAccessStatusSeverity(value) {
|
||||
switch (value) {
|
||||
case 'active':
|
||||
return 'success';
|
||||
case 'revoked':
|
||||
return 'danger';
|
||||
case 'expired':
|
||||
return 'warn';
|
||||
default:
|
||||
return 'secondary';
|
||||
}
|
||||
}
|
||||
|
||||
function getNotificationReadSeverity(value) {
|
||||
return value ? 'secondary' : 'warn';
|
||||
}
|
||||
@@ -275,6 +311,36 @@ async function loadLikes() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLibrary() {
|
||||
const id = userID.value;
|
||||
if (!id || Number.isNaN(id)) return;
|
||||
libraryLoading.value = true;
|
||||
try {
|
||||
const result = await UserService.listUserLibrary(id, {
|
||||
page: libraryPage.value,
|
||||
limit: libraryRows.value,
|
||||
tenant_id: libraryTenantID.value || undefined,
|
||||
tenant_code: libraryTenantCode.value || undefined,
|
||||
tenant_name: libraryTenantName.value || undefined,
|
||||
content_id: libraryContentID.value || undefined,
|
||||
keyword: libraryKeyword.value || undefined,
|
||||
status: libraryStatus.value || undefined,
|
||||
order_id: libraryOrderID.value || undefined,
|
||||
order_status: libraryOrderStatus.value || undefined,
|
||||
paid_at_from: libraryPaidAtFrom.value || undefined,
|
||||
paid_at_to: libraryPaidAtTo.value || undefined,
|
||||
accessed_at_from: libraryAccessedAtFrom.value || undefined,
|
||||
accessed_at_to: libraryAccessedAtTo.value || undefined
|
||||
});
|
||||
libraryItems.value = result.items;
|
||||
libraryTotal.value = result.total;
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载内容消费明细', life: 4000 });
|
||||
} finally {
|
||||
libraryLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFollowingTenants() {
|
||||
const id = userID.value;
|
||||
if (!id || Number.isNaN(id)) return;
|
||||
@@ -453,6 +519,19 @@ const likesKeyword = ref('');
|
||||
const likesCreatedAtFrom = ref(null);
|
||||
const likesCreatedAtTo = ref(null);
|
||||
|
||||
const libraryTenantID = ref(null);
|
||||
const libraryTenantCode = ref('');
|
||||
const libraryTenantName = ref('');
|
||||
const libraryContentID = ref(null);
|
||||
const libraryKeyword = ref('');
|
||||
const libraryStatus = ref('');
|
||||
const libraryOrderID = ref(null);
|
||||
const libraryOrderStatus = ref('');
|
||||
const libraryPaidAtFrom = ref(null);
|
||||
const libraryPaidAtTo = ref(null);
|
||||
const libraryAccessedAtFrom = ref(null);
|
||||
const libraryAccessedAtTo = ref(null);
|
||||
|
||||
const followingTenantID = ref(null);
|
||||
const followingCode = ref('');
|
||||
const followingName = ref('');
|
||||
@@ -558,6 +637,35 @@ function onLikesPage(event) {
|
||||
loadLikes();
|
||||
}
|
||||
|
||||
function onLibrarySearch() {
|
||||
libraryPage.value = 1;
|
||||
loadLibrary();
|
||||
}
|
||||
|
||||
function onLibraryReset() {
|
||||
libraryTenantID.value = null;
|
||||
libraryTenantCode.value = '';
|
||||
libraryTenantName.value = '';
|
||||
libraryContentID.value = null;
|
||||
libraryKeyword.value = '';
|
||||
libraryStatus.value = '';
|
||||
libraryOrderID.value = null;
|
||||
libraryOrderStatus.value = '';
|
||||
libraryPaidAtFrom.value = null;
|
||||
libraryPaidAtTo.value = null;
|
||||
libraryAccessedAtFrom.value = null;
|
||||
libraryAccessedAtTo.value = null;
|
||||
libraryPage.value = 1;
|
||||
libraryRows.value = 10;
|
||||
loadLibrary();
|
||||
}
|
||||
|
||||
function onLibraryPage(event) {
|
||||
libraryPage.value = (event.page ?? 0) + 1;
|
||||
libraryRows.value = event.rows ?? libraryRows.value;
|
||||
loadLibrary();
|
||||
}
|
||||
|
||||
function onFollowingSearch() {
|
||||
followingTenantsPage.value = 1;
|
||||
loadFollowingTenants();
|
||||
@@ -609,6 +717,7 @@ watch(
|
||||
couponsPage.value = 1;
|
||||
favoritesPage.value = 1;
|
||||
likesPage.value = 1;
|
||||
libraryPage.value = 1;
|
||||
rechargeOrdersPage.value = 1;
|
||||
loadUser();
|
||||
loadWallet();
|
||||
@@ -620,6 +729,7 @@ watch(
|
||||
loadCoupons();
|
||||
loadFavorites();
|
||||
loadLikes();
|
||||
loadLibrary();
|
||||
loadRechargeOrders();
|
||||
},
|
||||
{ immediate: true }
|
||||
@@ -691,6 +801,7 @@ onMounted(() => {
|
||||
<Tab value="following">关注</Tab>
|
||||
<Tab value="favorites">收藏</Tab>
|
||||
<Tab value="likes">点赞</Tab>
|
||||
<Tab value="library">内容消费</Tab>
|
||||
<Tab value="wallet">钱包</Tab>
|
||||
<Tab value="recharge">充值记录</Tab>
|
||||
<Tab value="coupons">优惠券</Tab>
|
||||
@@ -1100,6 +1211,117 @@ onMounted(() => {
|
||||
</DataTable>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel value="library">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-muted-color">共 {{ libraryTotal }} 条</span>
|
||||
</div>
|
||||
|
||||
<SearchPanel :loading="libraryLoading" @search="onLibrarySearch" @reset="onLibraryReset">
|
||||
<SearchField label="TenantID">
|
||||
<InputNumber v-model="libraryTenantID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="TenantCode">
|
||||
<InputText v-model="libraryTenantCode" placeholder="请输入" class="w-full" @keyup.enter="onLibrarySearch" />
|
||||
</SearchField>
|
||||
<SearchField label="TenantName">
|
||||
<InputText v-model="libraryTenantName" placeholder="请输入" class="w-full" @keyup.enter="onLibrarySearch" />
|
||||
</SearchField>
|
||||
<SearchField label="ContentID">
|
||||
<InputNumber v-model="libraryContentID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="关键字">
|
||||
<InputText v-model="libraryKeyword" placeholder="标题/摘要/描述" class="w-full" @keyup.enter="onLibrarySearch" />
|
||||
</SearchField>
|
||||
<SearchField label="访问状态">
|
||||
<Select v-model="libraryStatus" :options="accessStatusOptions" optionLabel="label" optionValue="value" placeholder="请选择" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="订单ID">
|
||||
<InputNumber v-model="libraryOrderID" :min="1" placeholder="精确匹配" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="订单状态">
|
||||
<Select v-model="libraryOrderStatus" :options="orderStatusOptions" optionLabel="label" optionValue="value" placeholder="请选择" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="支付时间 From">
|
||||
<DatePicker v-model="libraryPaidAtFrom" showIcon showButtonBar placeholder="开始时间" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="支付时间 To">
|
||||
<DatePicker v-model="libraryPaidAtTo" showIcon showButtonBar placeholder="结束时间" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="获取时间 From">
|
||||
<DatePicker v-model="libraryAccessedAtFrom" showIcon showButtonBar placeholder="开始时间" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="获取时间 To">
|
||||
<DatePicker v-model="libraryAccessedAtTo" showIcon showButtonBar placeholder="结束时间" class="w-full" />
|
||||
</SearchField>
|
||||
</SearchPanel>
|
||||
|
||||
<DataTable
|
||||
:value="libraryItems"
|
||||
dataKey="access_id"
|
||||
:loading="libraryLoading"
|
||||
lazy
|
||||
:paginator="true"
|
||||
:rows="libraryRows"
|
||||
:totalRecords="libraryTotal"
|
||||
:first="(libraryPage - 1) * libraryRows"
|
||||
:rowsPerPageOptions="[10, 20, 50, 100]"
|
||||
@page="onLibraryPage"
|
||||
currentPageReportTemplate="显示第 {first} - {last} 条,共 {totalRecords} 条"
|
||||
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
||||
scrollable
|
||||
scrollHeight="420px"
|
||||
responsiveLayout="scroll"
|
||||
>
|
||||
<Column field="access_id" header="记录ID" style="min-width: 7rem" />
|
||||
<Column header="内容标题" style="min-width: 18rem">
|
||||
<template #body="{ data }">
|
||||
<span>{{ data?.content?.content?.title ?? data?.snapshot?.content_title ?? '-' }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="content_id" header="ContentID" style="min-width: 8rem" />
|
||||
<Column header="租户" style="min-width: 14rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ data?.content?.tenant?.name ?? '-' }}</span>
|
||||
<span class="text-xs text-muted-color">ID: {{ data?.tenant_id ?? '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="作者" style="min-width: 12rem">
|
||||
<template #body="{ data }">
|
||||
<span>{{ data?.content?.owner?.username ?? data?.snapshot?.content_user_id ?? '-' }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="order_id" header="订单ID" style="min-width: 9rem" />
|
||||
<Column header="订单状态" style="min-width: 12rem">
|
||||
<template #body="{ data }">
|
||||
<Tag :value="data?.order_status_description || data?.order_status || '-'" :severity="getOrderStatusSeverity(data?.order_status)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="实付金额" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
{{ formatCny(data.amount_paid) }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="访问状态" style="min-width: 10rem">
|
||||
<template #body="{ data }">
|
||||
<Tag :value="data?.access_status_description || data?.access_status || '-'" :severity="getAccessStatusSeverity(data?.access_status)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="支付时间" style="min-width: 14rem">
|
||||
<template #body="{ data }">
|
||||
{{ formatDate(data.paid_at) }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="获取时间" style="min-width: 14rem">
|
||||
<template #body="{ data }">
|
||||
{{ formatDate(data.accessed_at) }}
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel value="wallet">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
|
||||
Reference in New Issue
Block a user