mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
chore: bump version to v1.2.1
This commit is contained in:
@@ -35,6 +35,10 @@
|
||||
- [服务器要求](https://ecn5khs4t956.feishu.cn/wiki/W8YBww1Mmiu4Cdkp5W4c8pFNnMf?from=from_copylink)
|
||||
- [QQ机器人](https://github.com/ctwj/astrbot_plugin_urldb)
|
||||
|
||||
### v1.2.1
|
||||
1. 修复转存移除广告失败的问题和添加广告失败的问题
|
||||
2. 管理后台UI优化
|
||||
|
||||
### v1.2.0
|
||||
1. 新增手动批量转存
|
||||
2. 新增QQ机器人
|
||||
|
||||
@@ -18,6 +18,7 @@ type SearchStatRepository interface {
|
||||
GetSearchTrend(days int) ([]entity.DailySearchStat, error)
|
||||
GetKeywordTrend(keyword string, days int) ([]entity.DailySearchStat, error)
|
||||
GetSummary() (map[string]int64, error)
|
||||
FindWithPaginationOrdered(page, limit int) ([]entity.SearchStat, int64, error)
|
||||
}
|
||||
|
||||
// SearchStatRepositoryImpl 搜索统计Repository实现
|
||||
@@ -157,3 +158,20 @@ func (r *SearchStatRepositoryImpl) GetSummary() (map[string]int64, error) {
|
||||
"keywords": keywords,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FindWithPaginationOrdered 按时间倒序分页查找搜索记录
|
||||
func (r *SearchStatRepositoryImpl) FindWithPaginationOrdered(page, limit int) ([]entity.SearchStat, int64, error) {
|
||||
var stats []entity.SearchStat
|
||||
var total int64
|
||||
|
||||
offset := (page - 1) * limit
|
||||
|
||||
// 获取总数
|
||||
if err := r.db.Model(&entity.SearchStat{}).Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取分页数据,按创建时间倒序排列(最新的在前面)
|
||||
err := r.db.Order("created_at DESC").Offset(offset).Limit(limit).Find(&stats).Error
|
||||
return stats, total, err
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ func GetSearchStats(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
stats, total, err := repoManager.SearchStatRepository.FindWithPagination(page, pageSize)
|
||||
// 使用自定义方法获取按时间倒序排列的搜索记录
|
||||
stats, total, err := repoManager.SearchStatRepository.FindWithPaginationOrdered(page, pageSize)
|
||||
if err != nil {
|
||||
ErrorResponse(c, "获取搜索统计失败", http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
@@ -24,7 +24,6 @@ declare module 'vue' {
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
NList: typeof import('naive-ui')['NList']
|
||||
NListItem: typeof import('naive-ui')['NListItem']
|
||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
|
||||
@@ -65,51 +65,60 @@
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 热门关键词 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<span class="text-xl font-semibold text-gray-900 dark:text-white">热门关键词</span>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<div v-for="keyword in stats.hotKeywords" :key="keyword.keyword"
|
||||
class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div class="flex items-center">
|
||||
<span class="inline-flex items-center justify-center w-8 h-8 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400 rounded-full text-sm font-medium mr-3">
|
||||
{{ keyword.rank }}
|
||||
</span>
|
||||
<span class="text-gray-900 dark:text-white font-medium">{{ keyword.keyword }}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="text-gray-600 dark:text-gray-400 mr-2">{{ keyword.count }}次</span>
|
||||
<div class="w-24 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div class="bg-blue-600 h-2 rounded-full"
|
||||
:style="{ width: getPercentage(keyword.count) + '%' }"></div>
|
||||
<!-- 热门关键词和搜索记录并排显示 -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- 热门关键词 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<span class="text-xl font-semibold text-gray-900 dark:text-white">热门关键词</span>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<div v-for="keyword in limitedHotKeywords" :key="keyword.keyword"
|
||||
class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div class="flex items-center">
|
||||
<span class="inline-flex items-center justify-center w-8 h-8 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400 rounded-full text-sm font-medium mr-3">
|
||||
{{ keyword.rank }}
|
||||
</span>
|
||||
<span class="text-gray-900 dark:text-white font-medium">{{ keyword.keyword }}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="text-gray-600 dark:text-gray-400 mr-2">{{ keyword.count }}次</span>
|
||||
<div class="w-24 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div class="bg-blue-600 h-2 rounded-full"
|
||||
:style="{ width: getPercentage(keyword.count) + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!stats.hotKeywords || stats.hotKeywords.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
暂无热门关键词数据
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!stats.hotKeywords || stats.hotKeywords.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
暂无热门关键词数据
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-card>
|
||||
|
||||
<!-- 搜索记录 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<span class="text-xl font-semibold text-gray-900 dark:text-white">搜索记录</span>
|
||||
</template>
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="searchList"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
:bordered="false"
|
||||
striped
|
||||
/>
|
||||
<div v-if="searchList.length === 0 && !loading" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
暂无搜索记录
|
||||
</div>
|
||||
</n-card>
|
||||
<!-- 搜索记录 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<span class="text-xl font-semibold text-gray-900 dark:text-white">搜索记录</span>
|
||||
</template>
|
||||
<div class="space-y-3">
|
||||
<div v-for="record in limitedSearchList" :key="record.id"
|
||||
class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-gray-900 dark:text-white">{{ record.keyword }}</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ formatDate(record.created_at) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-white">{{ record.count }}次</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="searchList.length === 0 && !loading" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
暂无搜索记录
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -160,44 +169,35 @@ const loading = ref(false)
|
||||
const trendChart = ref<HTMLCanvasElement | null>(null)
|
||||
let chart: any = null
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
onChange: (page: number) => {
|
||||
pagination.value.page = page
|
||||
loadSearchRecords()
|
||||
},
|
||||
onUpdatePageSize: (pageSize: number) => {
|
||||
pagination.value.pageSize = pageSize
|
||||
pagination.value.page = 1
|
||||
loadSearchRecords()
|
||||
}
|
||||
// 按时间排序的搜索记录(最新的在前面)
|
||||
const sortedSearchList = computed(() => {
|
||||
return [...searchList.value].sort((a, b) => {
|
||||
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
})
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '关键词',
|
||||
key: 'keyword',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '搜索次数',
|
||||
key: 'count',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '日期',
|
||||
key: 'date',
|
||||
width: 150,
|
||||
render: (row: any) => {
|
||||
return row.date ? new Date(row.date).toLocaleDateString() : ''
|
||||
}
|
||||
}
|
||||
]
|
||||
// 限制显示前10条热门关键词
|
||||
const limitedHotKeywords = computed(() => {
|
||||
return stats.value.hotKeywords.slice(0, 10)
|
||||
})
|
||||
|
||||
// 限制显示前10条搜索记录
|
||||
const limitedSearchList = computed(() => {
|
||||
return sortedSearchList.value.slice(0, 10)
|
||||
})
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateString: string) => {
|
||||
if (!dateString) return ''
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取百分比
|
||||
const getPercentage = (count: number) => {
|
||||
|
||||
@@ -105,16 +105,16 @@
|
||||
|
||||
<!-- 资源列表 -->
|
||||
<div class="overflow-x-auto bg-white dark:bg-gray-800 rounded-lg shadow">
|
||||
<table class="w-full min-w-full table-fixed">
|
||||
<table class="w-full min-w-full">
|
||||
<thead>
|
||||
<tr class="bg-slate-800 dark:bg-gray-700 text-white dark:text-gray-100">
|
||||
<th class="px-2 sm:px-6 py-3 sm:py-4 text-left text-xs sm:text-sm w-1/2 sm:w-4/6">
|
||||
<th class="px-2 sm:px-6 py-3 sm:py-4 text-left text-xs sm:text-sm">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-cloud mr-1 text-gray-300"></i> 文件名
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-2 sm:px-6 py-3 sm:py-4 text-left text-xs sm:text-sm hidden sm:table-cell w-1/6">链接</th>
|
||||
<th class="px-2 sm:px-6 py-3 sm:py-4 text-left text-xs sm:text-sm hidden sm:table-cell w-1/6">更新时间</th>
|
||||
<th class="px-2 sm:px-6 py-3 sm:py-4 text-left text-xs sm:text-sm hidden sm:table-cell w-24">链接</th>
|
||||
<th class="px-2 sm:px-6 py-3 sm:py-4 text-left text-xs sm:text-sm hidden sm:table-cell w-32">更新时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@@ -136,10 +136,16 @@
|
||||
:class="isUpdatedToday(resource.updated_at) ? 'hover:bg-pink-50 dark:hover:bg-pink-900 bg-pink-50/30 dark:bg-pink-900/30' : 'hover:bg-gray-50 dark:hover:bg-gray-800'"
|
||||
:data-index="index"
|
||||
>
|
||||
<td class="px-2 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm w-1/2 sm:w-2/5">
|
||||
<td class="px-2 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm">
|
||||
<div class="flex items-start">
|
||||
<span class="mr-2 flex-shrink-0" v-html="getPlatformIcon(resource.pan_id || 0)"></span>
|
||||
<span class="break-words">{{ resource.title }}</span>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="break-words font-medium">{{ resource.title }}</div>
|
||||
<!-- 显示描述 -->
|
||||
<div v-if="resource.description" class="text-xs text-gray-600 dark:text-gray-400 mt-1 break-words line-clamp-2">
|
||||
{{ resource.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:hidden mt-1 space-y-1">
|
||||
<!-- 移动端显示更新时间 -->
|
||||
@@ -155,7 +161,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm hidden sm:table-cell w-1/5">
|
||||
<td class="px-2 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm hidden sm:table-cell w-32">
|
||||
<button
|
||||
class="text-blue-600 hover:text-blue-800 flex items-center gap-1 show-link-btn"
|
||||
@click="toggleLink(resource)"
|
||||
@@ -163,7 +169,7 @@
|
||||
<i class="fas fa-eye"></i> 显示链接
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-2 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-gray-500 hidden sm:table-cell w-2/5" :title="resource.updated_at">
|
||||
<td class="px-2 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-gray-500 hidden sm:table-cell w-32" :title="resource.updated_at">
|
||||
<span v-html="formatRelativeTime(resource.updated_at)"></span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -510,4 +516,27 @@ const animateCounters = () => {
|
||||
rgba(0,0,0,0.25) 100%
|
||||
);
|
||||
}
|
||||
|
||||
/* 文本截断样式 */
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 表格单元格内容溢出控制 */
|
||||
table td {
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 确保flex容器不会溢出 */
|
||||
.min-w-0 {
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user