@@ -28,3 +28,27 @@ export function deleteWatermarkTask(id) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function getVideoParseLogs(params) {
|
||||
return request({
|
||||
url: '/api/admin/watermark/video-parse-logs',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getVideoParseUnlocks(params) {
|
||||
return request({
|
||||
url: '/api/admin/watermark/video-parse-unlocks',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getVideoDownloadFailures(params) {
|
||||
return request({
|
||||
url: '/api/admin/watermark/video-download-failures',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
+64
-24
@@ -6,7 +6,6 @@
|
||||
@click="closeMobileMenu"
|
||||
></div>
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<el-aside :width="asideWidth" class="sidebar" :class="{ 'sidebar-mobile': isMobile }">
|
||||
<div class="logo">
|
||||
<span v-if="!isCollapse">管理后台</span>
|
||||
@@ -20,20 +19,36 @@
|
||||
router
|
||||
@select="handleMenuSelect"
|
||||
>
|
||||
<el-menu-item
|
||||
v-for="route in menuRoutes"
|
||||
:key="route.path"
|
||||
:index="route.path"
|
||||
>
|
||||
<el-icon><component :is="route.meta.icon" /></el-icon>
|
||||
<template #title>{{ route.meta.title }}</template>
|
||||
</el-menu-item>
|
||||
<template v-for="routeItem in menuRoutes" :key="routeKey(routeItem)">
|
||||
<el-sub-menu
|
||||
v-if="hasChildren(routeItem)"
|
||||
:index="resolvePath(routeItem.path)"
|
||||
>
|
||||
<template #title>
|
||||
<el-icon><component :is="routeItem.meta.icon" /></el-icon>
|
||||
<span>{{ routeItem.meta.title }}</span>
|
||||
</template>
|
||||
<el-menu-item
|
||||
v-for="child in visibleChildren(routeItem)"
|
||||
:key="routeKey(child)"
|
||||
:index="resolvePath(routeItem.path, child.path)"
|
||||
>
|
||||
{{ child.meta?.title || child.name }}
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
<el-menu-item
|
||||
v-else
|
||||
:index="resolvePath(routeItem.path)"
|
||||
>
|
||||
<el-icon><component :is="routeItem.meta.icon" /></el-icon>
|
||||
<template #title>{{ routeItem.meta.title }}</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<el-container>
|
||||
<!-- 顶部导航栏 -->
|
||||
<el-header class="header">
|
||||
<div class="header-left">
|
||||
<el-icon class="collapse-icon" @click="toggleCollapse">
|
||||
@@ -59,7 +74,6 @@
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<!-- 主内容 -->
|
||||
<el-main class="main-content">
|
||||
<router-view />
|
||||
</el-main>
|
||||
@@ -122,26 +136,51 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
// 获取菜单路由(过滤掉隐藏的路由)
|
||||
const menuRoutes = computed(() => {
|
||||
const routes = router.getRoutes()
|
||||
const mainRoute = routes.find(r => r.path === '/')
|
||||
if (!mainRoute || !mainRoute.children) return []
|
||||
|
||||
return mainRoute.children.filter(r => !r.meta?.hidden)
|
||||
const rootRoute = router.options.routes.find((item) => item.path === '/')
|
||||
if (!rootRoute || !rootRoute.children) return []
|
||||
return rootRoute.children.filter((item) => !item.meta?.hidden)
|
||||
})
|
||||
|
||||
// 当前激活的菜单
|
||||
const visibleChildren = (routeItem) => {
|
||||
return (routeItem.children || []).filter((child) => !child.meta?.hidden)
|
||||
}
|
||||
|
||||
const hasChildren = (routeItem) => {
|
||||
return visibleChildren(routeItem).length > 0
|
||||
}
|
||||
|
||||
const routeKey = (routeItem) => {
|
||||
return `${routeItem.path || ''}-${routeItem.name || ''}`
|
||||
}
|
||||
|
||||
const resolvePath = (parentPath = '', childPath = '') => {
|
||||
const normalize = (value) => {
|
||||
if (!value) return '/'
|
||||
return value.startsWith('/') ? value : `/${value}`
|
||||
}
|
||||
|
||||
const parent = normalize(parentPath)
|
||||
if (!childPath) {
|
||||
return parent
|
||||
}
|
||||
if (childPath.startsWith('/')) {
|
||||
return childPath
|
||||
}
|
||||
return `${parent.replace(/\/$/, '')}/${childPath}`.replace(/\/{2,}/g, '/')
|
||||
}
|
||||
|
||||
const activeMenu = computed(() => {
|
||||
const { path } = route
|
||||
// 如果是子路由,返回父路由路径
|
||||
if (path === '/watermark') {
|
||||
return '/watermark/video-parse-logs'
|
||||
}
|
||||
if (path.includes('/create') || path.includes('/edit') || /\/\d+$/.test(path)) {
|
||||
return '/' + path.split('/')[1]
|
||||
}
|
||||
return path
|
||||
})
|
||||
|
||||
// 切换侧边栏折叠状态
|
||||
const toggleCollapse = () => {
|
||||
if (isMobile.value) {
|
||||
mobileMenuVisible.value = !mobileMenuVisible.value
|
||||
@@ -167,7 +206,6 @@ const handleResize = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理下拉菜单命令
|
||||
const handleCommand = async (command) => {
|
||||
if (command === 'logout') {
|
||||
try {
|
||||
@@ -222,11 +260,13 @@ const handleCommand = async (command) => {
|
||||
background: #304156;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item) {
|
||||
:deep(.el-menu-item),
|
||||
:deep(.el-sub-menu__title) {
|
||||
color: #bfcbd9;
|
||||
}
|
||||
|
||||
:deep(.el-menu-item:hover) {
|
||||
:deep(.el-menu-item:hover),
|
||||
:deep(.el-sub-menu__title:hover) {
|
||||
background: #263445 !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
+23
-2
@@ -72,8 +72,29 @@ const routes = [
|
||||
{
|
||||
path: 'watermark',
|
||||
name: 'Watermark',
|
||||
component: () => import('../views/watermark/index.vue'),
|
||||
meta: { title: '去水印管理', icon: 'Brush' }
|
||||
component: () => import('../views/watermark/layout.vue'),
|
||||
redirect: '/watermark/video-parse-logs',
|
||||
meta: { title: '去水印小程序', icon: 'Brush' },
|
||||
children: [
|
||||
{
|
||||
path: 'video-parse-logs',
|
||||
name: 'VideoParseLogs',
|
||||
component: () => import('../views/watermark/video-parse-logs.vue'),
|
||||
meta: { title: '解析日志' }
|
||||
},
|
||||
{
|
||||
path: 'video-parse-unlocks',
|
||||
name: 'VideoParseUnlocks',
|
||||
component: () => import('../views/watermark/video-parse-unlocks.vue'),
|
||||
meta: { title: '广告解锁' }
|
||||
},
|
||||
{
|
||||
path: 'video-download-failures',
|
||||
name: 'VideoDownloadFailures',
|
||||
component: () => import('../views/watermark/video-download-failures.vue'),
|
||||
meta: { title: '下载失败上报' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="header-row">
|
||||
<div class="filters">
|
||||
<el-input
|
||||
v-model="query.keyword"
|
||||
placeholder="搜索失败链接/错误信息/UA"
|
||||
clearable
|
||||
style="width: 260px"
|
||||
@keyup.enter="handleSearch"
|
||||
@clear="handleSearch"
|
||||
/>
|
||||
<el-input
|
||||
v-model="query.domain"
|
||||
placeholder="来源域名"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@keyup.enter="handleSearch"
|
||||
@clear="handleSearch"
|
||||
/>
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="list" stripe>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="domain" label="域名" min-width="140" />
|
||||
<el-table-column label="失败链接" min-width="280" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link :href="row.failed_url" target="_blank" type="primary" :underline="false">
|
||||
{{ row.failed_url }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="错误信息" min-width="220" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.error_message || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="client_ip" label="IP" width="140" />
|
||||
<el-table-column label="UA" min-width="180" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.user_agent || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上报时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.reported_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="query.page"
|
||||
v-model:page-size="query.page_size"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="total"
|
||||
@current-change="loadData"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import dayjs from 'dayjs'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { getVideoDownloadFailures } from '../../api/watermark'
|
||||
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
const dateRange = ref([])
|
||||
|
||||
const query = reactive({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
keyword: '',
|
||||
domain: ''
|
||||
})
|
||||
|
||||
const formatDateTime = (value) => {
|
||||
if (!value) return '-'
|
||||
return dayjs(value).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
const buildParams = () => {
|
||||
const params = {
|
||||
page: query.page,
|
||||
page_size: query.page_size,
|
||||
keyword: query.keyword || undefined,
|
||||
domain: query.domain || undefined
|
||||
}
|
||||
|
||||
if (Array.isArray(dateRange.value) && dateRange.value.length === 2) {
|
||||
params.date_from = dateRange.value[0]
|
||||
params.date_to = dateRange.value[1]
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getVideoDownloadFailures(buildParams())
|
||||
const payload = res.data || {}
|
||||
list.value = payload.list || []
|
||||
total.value = payload.total || 0
|
||||
query.page = payload.page || query.page
|
||||
query.page_size = payload.page_size || query.page_size
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
query.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
query.keyword = ''
|
||||
query.domain = ''
|
||||
dateRange.value = []
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const handleSizeChange = () => {
|
||||
query.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="header-row">
|
||||
<div class="filters">
|
||||
<el-input
|
||||
v-model="query.keyword"
|
||||
placeholder="搜索原文/解析链接/错误信息"
|
||||
clearable
|
||||
style="width: 260px"
|
||||
@keyup.enter="handleSearch"
|
||||
@clear="handleSearch"
|
||||
/>
|
||||
<el-select
|
||||
v-model="query.mini_program_id"
|
||||
clearable
|
||||
style="width: 180px"
|
||||
placeholder="全部小程序"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in miniProgramOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input-number
|
||||
v-model="query.user_id"
|
||||
:min="1"
|
||||
:controls="false"
|
||||
placeholder="用户ID"
|
||||
style="width: 130px"
|
||||
/>
|
||||
<el-select
|
||||
v-model="query.free_quota_used"
|
||||
clearable
|
||||
style="width: 150px"
|
||||
placeholder="免费次数"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<el-option label="计入免费" :value="true" />
|
||||
<el-option label="不计入免费" :value="false" />
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="list" stripe>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column label="小程序" min-width="140">
|
||||
<template #default="{ row }">
|
||||
{{ row.mini_program_name || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="user_id" label="用户ID" width="90" />
|
||||
<el-table-column label="原始内容" min-width="220" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.request_content || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="解析链接" min-width="220" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link
|
||||
v-if="row.parsed_url"
|
||||
:href="row.parsed_url"
|
||||
target="_blank"
|
||||
type="primary"
|
||||
:underline="false"
|
||||
>
|
||||
{{ row.parsed_url }}
|
||||
</el-link>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="免费次数" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.free_quota_used ? 'success' : 'info'">
|
||||
{{ row.free_quota_used ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="duration_ms" label="耗时(ms)" width="100" />
|
||||
<el-table-column label="错误信息" min-width="180" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.error_message || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="query.page"
|
||||
v-model:page-size="query.page_size"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="total"
|
||||
@current-change="loadData"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import dayjs from 'dayjs'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { getMiniPrograms } from '../../api/miniProgram'
|
||||
import { getVideoParseLogs } from '../../api/watermark'
|
||||
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
const miniProgramOptions = ref([])
|
||||
const dateRange = ref([])
|
||||
|
||||
const query = reactive({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
keyword: '',
|
||||
mini_program_id: undefined,
|
||||
user_id: undefined,
|
||||
free_quota_used: undefined
|
||||
})
|
||||
|
||||
const formatDateTime = (value) => {
|
||||
if (!value) return '-'
|
||||
return dayjs(value).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
const loadMiniProgramOptions = async () => {
|
||||
const res = await getMiniPrograms({ page: 1, page_size: 200 })
|
||||
const payload = res.data || {}
|
||||
miniProgramOptions.value = payload.list || []
|
||||
}
|
||||
|
||||
const buildParams = () => {
|
||||
const params = {
|
||||
page: query.page,
|
||||
page_size: query.page_size,
|
||||
keyword: query.keyword || undefined,
|
||||
mini_program_id: query.mini_program_id,
|
||||
user_id: query.user_id,
|
||||
free_quota_used: query.free_quota_used
|
||||
}
|
||||
|
||||
if (Array.isArray(dateRange.value) && dateRange.value.length === 2) {
|
||||
params.date_from = dateRange.value[0]
|
||||
params.date_to = dateRange.value[1]
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getVideoParseLogs(buildParams())
|
||||
const payload = res.data || {}
|
||||
list.value = payload.list || []
|
||||
total.value = payload.total || 0
|
||||
query.page = payload.page || query.page
|
||||
query.page_size = payload.page_size || query.page_size
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
query.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
query.keyword = ''
|
||||
query.mini_program_id = undefined
|
||||
query.user_id = undefined
|
||||
query.free_quota_used = undefined
|
||||
dateRange.value = []
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const handleSizeChange = () => {
|
||||
query.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadMiniProgramOptions(), loadData()])
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="header-row">
|
||||
<div class="filters">
|
||||
<el-select
|
||||
v-model="query.mini_program_id"
|
||||
clearable
|
||||
style="width: 180px"
|
||||
placeholder="全部小程序"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in miniProgramOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input-number
|
||||
v-model="query.user_id"
|
||||
:min="1"
|
||||
:controls="false"
|
||||
placeholder="用户ID"
|
||||
style="width: 130px"
|
||||
/>
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="list" stripe>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column label="小程序" min-width="160">
|
||||
<template #default="{ row }">
|
||||
{{ row.mini_program_name || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="user_id" label="用户ID" width="100" />
|
||||
<el-table-column label="解锁日期" width="140">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.unlock_date) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="广告完成时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.ad_watched_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="query.page"
|
||||
v-model:page-size="query.page_size"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="total"
|
||||
@current-change="loadData"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import dayjs from 'dayjs'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { getMiniPrograms } from '../../api/miniProgram'
|
||||
import { getVideoParseUnlocks } from '../../api/watermark'
|
||||
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
const miniProgramOptions = ref([])
|
||||
const dateRange = ref([])
|
||||
|
||||
const query = reactive({
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
mini_program_id: undefined,
|
||||
user_id: undefined
|
||||
})
|
||||
|
||||
const formatDateTime = (value) => {
|
||||
if (!value) return '-'
|
||||
return dayjs(value).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
const formatDate = (value) => {
|
||||
if (!value) return '-'
|
||||
return dayjs(value).format('YYYY-MM-DD')
|
||||
}
|
||||
|
||||
const loadMiniProgramOptions = async () => {
|
||||
const res = await getMiniPrograms({ page: 1, page_size: 200 })
|
||||
const payload = res.data || {}
|
||||
miniProgramOptions.value = payload.list || []
|
||||
}
|
||||
|
||||
const buildParams = () => {
|
||||
const params = {
|
||||
page: query.page,
|
||||
page_size: query.page_size,
|
||||
mini_program_id: query.mini_program_id,
|
||||
user_id: query.user_id
|
||||
}
|
||||
|
||||
if (Array.isArray(dateRange.value) && dateRange.value.length === 2) {
|
||||
params.date_from = dateRange.value[0]
|
||||
params.date_to = dateRange.value[1]
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getVideoParseUnlocks(buildParams())
|
||||
const payload = res.data || {}
|
||||
list.value = payload.list || []
|
||||
total.value = payload.total || 0
|
||||
query.page = payload.page || query.page
|
||||
query.page_size = payload.page_size || query.page_size
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
query.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
query.mini_program_id = undefined
|
||||
query.user_id = undefined
|
||||
dateRange.value = []
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const handleSizeChange = () => {
|
||||
query.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadMiniProgramOptions(), loadData()])
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user