1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-09-12 16:40:40 +08:00

feat: 新增收藏功能与多项体验优化

- 新增本地收藏store与工具函数,支持文章/瞬间收藏持久化
- 重构插件可用性检查hook,统一管理依赖插件状态
- 替换旧数据加载hook为新的状态管理方案
- 优化首页布局与加载逻辑,调整公告组件样式
- 为瞬间详情页添加点赞、评论与收藏功能
- 新增文本处理工具,支持HTML/Markdown转纯文本与摘要截断
- 修复评论弹窗适配瞬间类型的参数问题
This commit is contained in:
小莫唐尼
2026-09-04 17:17:18 +08:00
parent 629b1f9219
commit 62b604644c
28 changed files with 1548 additions and 1387 deletions
+229 -253
View File
@@ -1,280 +1,256 @@
<script lang="ts" setup>
/**
* 数据看板页(源自旧项目 pagesA/data-visual,新建复刻)
* 标签统计/分类统计(环形图)、文章发布趋势(热度图)、评论活跃用户(柱状图)、热门文章 Top10(柱状图)
*/
import { ref } from 'vue'
import { onPullDownRefresh } from '@dcloudio/uni-app'
import { getChartData } from '@/api/uni-halo'
import { usePluginAvailable } from '@/utils/plugin'
import type { IDataStatistics } from '@/api/uni-halo'
import { ref } from 'vue'
import { onPullDownRefresh } from '@dcloudio/uni-app'
import { getChartData } from '@/api/uni-halo'
import type { IDataStatistics } from '@/api/uni-halo'
definePage({
style: {
navigationBarTitleText: '数据看板',
enablePullDownRefresh: true,
},
})
definePage({
style: {
navigationBarTitleText: '数据看板',
enablePullDownRefresh: true,
},
})
/** 依赖插件(plugin-data-statistics) */
const uniHaloPluginId = 'plugin-data-statistics'
const uniHaloPluginAvailable = ref(true)
/** 依赖插件(plugin-data-statistics) */
const uniHaloPluginId = 'plugin-data-statistics'
const { available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable(uniHaloPluginId)
/* ---------------- 状态 ---------------- */
const loading = ref<'loading' | 'success' | 'error'>('loading')
const statistics = ref<IDataStatistics>({
tags: [],
categories: [],
articles: [],
comments: [],
top10Articles: [],
})
/* ---------------- 状态 ---------------- */
const loading = ref<'loading' | 'success' | 'error'>('loading')
const statistics = ref<IDataStatistics>({
tags: [],
categories: [],
articles: [],
comments: [],
top10Articles: [],
})
/* ---------------- 图表配置 ---------------- */
const chartColors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316', '#ea7ccc', '#0EA5E9']
/* ---------------- 图表配置 ---------------- */
const chartColors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316', '#ea7ccc', '#0EA5E9']
/** 标签统计(环形图) */
const tagChart = ref({
isExpand: true,
type: 'ring',
data: { series: [{ data: [] as { name: string, value: number }[] }] },
})
/** 标签统计(环形图) */
const tagChart = ref({
isExpand: true,
type: 'ring',
data: { series: [{ data: [] as { name : string, value : number }[] }] },
})
/** 分类统计(柱状图) */
const categoryChart = ref({
isExpand: true,
type: 'column',
data: { categories: [] as string[], series: [{ name: '分类', data: [] as number[] }] },
})
/** 分类统计(柱状图) */
const categoryChart = ref({
isExpand: true,
type: 'column',
data: { categories: [] as string[], series: [{ name: '分类', data: [] as number[] }] },
})
/** 文章发布趋势(热度图) */
const trandArticleChart = ref({
isExpand: true,
type: 'hotmap',
data: [] as { date: string, count: number }[],
})
/** 文章发布趋势(热度图) */
const trandArticleChart = ref({
isExpand: true,
type: 'hotmap',
data: [] as { date : string, count : number }[],
})
/** 评论活跃用户(柱状图) */
const userCommentsChart = ref({
isExpand: true,
type: 'column',
data: { categories: [] as string[], series: [{ name: '评论', data: [] as number[] }] },
})
/** 评论活跃用户(柱状图) */
const userCommentsChart = ref({
isExpand: true,
type: 'column',
data: { categories: [] as string[], series: [{ name: '评论', data: [] as number[] }] },
})
/** 热门文章 Top10(柱状图) */
const top10ArticlesChart = ref({
isExpand: true,
type: 'column',
data: { categories: [] as string[], series: [{ name: '访问量', data: [] as number[] }] },
})
/** 热门文章 Top10(柱状图) */
const top10ArticlesChart = ref({
isExpand: true,
type: 'column',
data: { categories: [] as string[], series: [{ name: '访问量', data: [] as number[] }] },
})
/* ---------------- 数据处理 ---------------- */
function handleTagChart() {
const data = [...statistics.value.tags].sort((a, b) => b.count - a.count)
tagChart.value.data = {
series: [
{
data: data.map(item => ({ name: item.name, value: item.count })),
},
],
}
}
/* ---------------- 数据处理 ---------------- */
function handleTagChart() {
const data = [...statistics.value.tags].sort((a, b) => b.count - a.count)
tagChart.value.data = {
series: [
{
data: data.map(item => ({ name: item.name, value: item.count })),
},
],
}
}
function handleCategoriesChart() {
const data = [...statistics.value.categories].sort((a, b) => b.total - a.total)
categoryChart.value.data = {
categories: data.map(item => item.name),
series: [{ name: '分类', data: data.map(item => item.total) }],
}
}
function handleCategoriesChart() {
const data = [...statistics.value.categories].sort((a, b) => b.total - a.total)
categoryChart.value.data = {
categories: data.map(item => item.name),
series: [{ name: '分类', data: data.map(item => item.total) }],
}
}
function handleTrendArticlesChart() {
trandArticleChart.value.data = statistics.value.articles.map(item => ({
date: item.date,
count: item.count,
}))
}
function handleTrendArticlesChart() {
trandArticleChart.value.data = statistics.value.articles.map(item => ({
date: item.date,
count: item.count,
}))
}
function handleUserCommentsChart() {
const data = [...statistics.value.comments].sort((a, b) => b.count - a.count).slice(0, 10)
userCommentsChart.value.data = {
categories: data.map(item => item.username),
series: [{ name: '评论', data: data.map(item => item.count) }],
}
}
function handleUserCommentsChart() {
const data = [...statistics.value.comments].sort((a, b) => b.count - a.count).slice(0, 10)
userCommentsChart.value.data = {
categories: data.map(item => item.username),
series: [{ name: '评论', data: data.map(item => item.count) }],
}
}
function handleTop10ArticlesChart() {
const data = [...statistics.value.top10Articles].sort((a, b) => b.views - a.views).slice(0, 10)
top10ArticlesChart.value.data = {
categories: data.map(item => item.name),
series: [{ name: '访问量', data: data.map(item => item.views) }],
}
}
function handleTop10ArticlesChart() {
const data = [...statistics.value.top10Articles].sort((a, b) => b.views - a.views).slice(0, 10)
top10ArticlesChart.value.data = {
categories: data.map(item => item.name),
series: [{ name: '访问量', data: data.map(item => item.views) }],
}
}
/* ---------------- 数据加载 ---------------- */
async function handleGetData() {
uni.showLoading({ mask: true, title: '加载中...' })
loading.value = 'loading'
try {
const res = await getChartData()
statistics.value = res.data
handleTagChart()
handleCategoriesChart()
handleTrendArticlesChart()
handleUserCommentsChart()
handleTop10ArticlesChart()
loading.value = 'success'
}
catch (err) {
console.error(err)
loading.value = 'error'
}
finally {
setTimeout(() => {
uni.hideLoading()
uni.stopPullDownRefresh()
}, 100)
}
}
/* ---------------- 数据加载 ---------------- */
async function handleGetData() {
uni.showLoading({ mask: true, title: '加载中...' })
loading.value = 'loading'
try {
const res = await getChartData()
statistics.value = res.data
handleTagChart()
handleCategoriesChart()
handleTrendArticlesChart()
handleUserCommentsChart()
handleTop10ArticlesChart()
loading.value = 'success'
}
catch (err) {
console.error(err)
loading.value = 'error'
}
finally {
setTimeout(() => {
uni.hideLoading()
uni.stopPullDownRefresh()
}, 100)
}
}
/* ---------------- 生命周期 ---------------- */
async function init() {
uniHaloPluginAvailable.value = await usePluginAvailable(uniHaloPluginId)
if (!uniHaloPluginAvailable.value) {
uni.stopPullDownRefresh()
return
}
handleGetData()
}
/* ---------------- 生命周期 ---------------- */
async function init() {
await checkPluginAvailable()
if (!uniHaloPluginAvailable.value) {
uni.stopPullDownRefresh()
return
}
handleGetData()
}
onPullDownRefresh(() => {
if (!uniHaloPluginAvailable.value) {
uni.stopPullDownRefresh()
return
}
handleGetData()
})
onPullDownRefresh(() => {
if (!uniHaloPluginAvailable.value) {
uni.stopPullDownRefresh()
return
}
handleGetData()
})
init()
init()
</script>
<template>
<view class="app-page box-border min-h-screen w-screen p-6 text-[#353437]" style="background-color: #fafafd;">
<uh-plugin-unavailable
v-if="!uniHaloPluginAvailable"
:plugin-id="uniHaloPluginId"
error-text="阿偶检测到当前插件没有安装或者启用无法使用功能哦请联系管理员"
@on-refresh="handleGetData"
/>
<template v-else>
<!-- 加载/错误占位 -->
<view v-if="loading !== 'success'">
<uh-data-loading :loading-status="loading" @refresh="handleGetData" />
</view>
<view class="bg-page box-border min-h-screen w-screen p-3">
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="uniHaloPluginId"
error-text="阿偶检测到当前插件没有安装或者启用无法使用功能哦请联系管理员" @on-refresh="handleGetData" />
<template v-else>
<uh-data-loading v-if="loading !== 'success'" :loading-status="loading" @refresh="handleGetData" />
<!-- 内容区域 -->
<view v-else class="content flex flex-col gap-6">
<!-- 标签统计 -->
<view class="card box-border rounded-xl bg-white/95 p-6" style="box-shadow: 0 0 12rpx rgb(226 232 240 / 35%);">
<view class="card-head flex items-center justify-between" @click="tagChart.isExpand = !tagChart.isExpand">
<view class="card-head-title flex items-baseline gap-2">
<text class="card-head-text relative box-border pl-6 text-[30rpx] font-bold">标签统计</text>
<text class="card-head-subtext text-[26rpx] text-[#6b7280] font-normal">全部标签的文章数量占比</text>
</view>
<wd-icon :name="tagChart.isExpand ? 'arrow-up' : 'arrow-down'" size="16px" color="#909399" />
</view>
<view v-show="tagChart.isExpand" class="card-body mt-6 box-border w-full overflow-hidden border-2 border-[#e9eef3] rounded-xl bg-[#fcfdfe] p-3">
<qiun-data-charts
type="ring"
:chart-data="tagChart.data"
:opts="{ color: chartColors, padding: [5, 5, 5, 5], dataLabel: false, legend: { show: false }, extra: { ring: { ringWidth: 36, offsetAngle: -90, border: true, borderWidth: 1, borderColor: '#FFFFFF' } } }"
/>
</view>
</view>
<!-- 内容区域 -->
<view v-else class="content flex flex-col gap-3">
<!-- 标签统计 -->
<view class="uh-global-card-glass uh-shadow-xs box-border rounded-xl p-4">
<uh-section-title>
标签统计
<template #right>
<view class="flex items-center gap-x-2">
<text class="text-xs text-gray-500">全部标签的文章数量占比</text>
<wd-icon :name="tagChart.isExpand ? 'up' : 'down'" size="16px" color="#909399"
@click="tagChart.isExpand = !tagChart.isExpand" />
</view>
</template>
</uh-section-title>
<view v-show="tagChart.isExpand" class="box-border w-full mt-3">
<qiun-data-charts type="ring" :chart-data="tagChart.data"
:opts="{ color: chartColors, padding: [5, 5, 5, 5], dataLabel: false, legend: { show: false }, extra: { ring: { ringWidth: 36, offsetAngle: -90, border: true, borderWidth: 1, borderColor: '#FFFFFF' } } }" />
</view>
</view>
<!-- 分类统计 -->
<view class="card box-border rounded-xl bg-white/95 p-6" style="box-shadow: 0 0 12rpx rgb(226 232 240 / 35%);">
<view class="card-head flex items-center justify-between" @click="categoryChart.isExpand = !categoryChart.isExpand">
<view class="card-head-title flex items-baseline gap-2">
<text class="card-head-text relative box-border pl-6 text-[30rpx] font-bold">分类统计</text>
<text class="card-head-subtext text-[26rpx] text-[#6b7280] font-normal">全部分类的文章数量占比</text>
</view>
<wd-icon :name="categoryChart.isExpand ? 'arrow-up' : 'arrow-down'" size="16px" color="#909399" />
</view>
<view v-show="categoryChart.isExpand" class="card-body mt-6 box-border w-full overflow-hidden border-2 border-[#e9eef3] rounded-xl bg-[#fcfdfe] p-3">
<qiun-data-charts
type="column"
:chart-data="categoryChart.data"
:opts="{ color: chartColors, padding: [20, 15, 10, 15], legend: { show: false }, xAxis: { disableGrid: true, fontSize: 10, itemCount: 6 }, yAxis: { gridType: 'dash', dashLength: 4 }, extra: { column: { type: 'group', width: 22, linearType: 'custom', seriesGap: 5, barBorderCircle: true, customColor: ['#F59E0B'] } } }"
/>
</view>
</view>
<!-- 分类统计 -->
<view class="uh-global-card-glass uh-shadow-xs box-border rounded-xl p-4">
<uh-section-title>
分类统计
<template #right>
<view class="flex items-center gap-x-2">
<text class="text-xs text-gray-500">全部分类的文章数量占比</text>
<wd-icon :name="categoryChart.isExpand ? 'up' : 'down'" size="16px" color="#909399"
@click="categoryChart.isExpand = !categoryChart.isExpand" />
</view>
</template>
</uh-section-title>
<view v-show="categoryChart.isExpand" class="box-border w-full mt-3">
<qiun-data-charts type="column" :chart-data="categoryChart.data"
:opts="{ color: chartColors, padding: [20, 15, 10, 15], legend: { show: false }, xAxis: { disableGrid: true, fontSize: 10, itemCount: 6 }, yAxis: { gridType: 'dash', dashLength: 4 }, extra: { column: { type: 'group', width: 22, linearType: 'custom', seriesGap: 5, barBorderCircle: true, customColor: ['#F59E0B'] } } }" />
</view>
</view>
<!-- 文章发布趋势 -->
<view class="card box-border rounded-xl bg-white/95 p-6" style="box-shadow: 0 0 12rpx rgb(226 232 240 / 35%);">
<view class="card-head flex items-center justify-between" @click="trandArticleChart.isExpand = !trandArticleChart.isExpand">
<view class="card-head-title flex items-baseline gap-2">
<text class="card-head-text relative box-border pl-6 text-[30rpx] font-bold">文章发布趋势</text>
<text class="card-head-subtext text-[26rpx] text-[#6b7280] font-normal">按日期统计文章发布数量</text>
</view>
<wd-icon :name="trandArticleChart.isExpand ? 'arrow-up' : 'arrow-down'" size="16px" color="#909399" />
</view>
<view v-show="trandArticleChart.isExpand" class="card-body mt-6 box-border w-full overflow-hidden border-2 border-[#e9eef3] rounded-xl bg-[#fcfdfe] p-3">
<uh-heatmap :chart-data="trandArticleChart.data" />
</view>
</view>
<!-- 文章发布趋势 -->
<view class="uh-global-card-glass uh-shadow-xs box-border rounded-xl p-4">
<uh-section-title>
文章发布趋势
<template #right>
<view class="flex items-center gap-x-2">
<text class="text-xs text-gray-500">按日期统计文章发布数量</text>
<wd-icon :name="trandArticleChart.isExpand ? 'up' : 'down'" size="16px" color="#909399"
@click="trandArticleChart.isExpand = !trandArticleChart.isExpand" />
</view>
</template>
</uh-section-title>
<view v-show="trandArticleChart.isExpand" class="box-border w-full mt-3">
<uh-heatmap :chart-data="trandArticleChart.data" />
</view>
</view>
<!-- 评论活跃用户 -->
<view class="card box-border rounded-xl bg-white/95 p-6" style="box-shadow: 0 0 12rpx rgb(226 232 240 / 35%);">
<view class="card-head flex items-center justify-between" @click="userCommentsChart.isExpand = !userCommentsChart.isExpand">
<view class="card-head-title flex items-baseline gap-2">
<text class="card-head-text relative box-border pl-6 text-[30rpx] font-bold">评论活跃用户</text>
<text class="card-head-subtext text-[26rpx] text-[#6b7280] font-normal">按评论作者统计评论数量</text>
</view>
<wd-icon :name="userCommentsChart.isExpand ? 'arrow-up' : 'arrow-down'" size="16px" color="#909399" />
</view>
<view v-show="userCommentsChart.isExpand" class="card-body mt-6 box-border w-full overflow-hidden border-2 border-[#e9eef3] rounded-xl bg-[#fcfdfe] p-3">
<qiun-data-charts
type="column"
:chart-data="userCommentsChart.data"
:opts="{ color: chartColors, padding: [20, 15, 10, 10], legend: { show: false }, xAxis: { disableGrid: true, fontSize: 10, itemCount: 5 }, yAxis: { gridType: 'dash', dashLength: 4 }, extra: { column: { type: 'group', width: 22, linearType: 'custom', seriesGap: 5, barBorderCircle: true, customColor: ['#F59E0B'] } } }"
/>
</view>
</view>
<!-- 评论活跃用户 -->
<view class="uh-global-card-glass uh-shadow-xs box-border rounded-xl p-4">
<uh-section-title>
评论活跃用户
<template #right>
<view class="flex items-center gap-x-2">
<text class="text-xs text-gray-500">按评论作者统计评论数量</text>
<wd-icon :name="userCommentsChart.isExpand ? 'up' : 'down'" size="16px" color="#909399"
@click="userCommentsChart.isExpand = !userCommentsChart.isExpand" />
</view>
</template>
</uh-section-title>
<view v-show="userCommentsChart.isExpand" class="box-border w-full mt-3">
<qiun-data-charts type="column" :chart-data="userCommentsChart.data"
:opts="{ color: chartColors, padding: [20, 15, 10, 10], legend: { show: false }, xAxis: { disableGrid: true, fontSize: 10, itemCount: 5 }, yAxis: { gridType: 'dash', dashLength: 4 }, extra: { column: { type: 'group', width: 22, linearType: 'custom', seriesGap: 5, barBorderCircle: true, customColor: ['#F59E0B'] } } }" />
</view>
</view>
<!-- 热门文章 Top10 -->
<view class="card box-border rounded-xl bg-white/95 p-6" style="box-shadow: 0 0 12rpx rgb(226 232 240 / 35%);">
<view class="card-head">
<view class="card-head-title flex items-baseline gap-2">
<text class="card-head-text relative box-border pl-6 text-[30rpx] font-bold">热门文章前10</text>
<text class="card-head-subtext text-[26rpx] text-[#6b7280] font-normal">按访问量排序的热门文章</text>
</view>
</view>
<view class="card-body mt-6 box-border w-full overflow-hidden border-2 border-[#e9eef3] rounded-xl bg-[#fcfdfe] p-3">
<qiun-data-charts
type="column"
:chart-data="top10ArticlesChart.data"
:opts="{ color: chartColors, padding: [20, 15, 10, 10], legend: { show: false }, xAxis: { disableGrid: true, fontSize: 10, itemCount: 5 }, yAxis: { gridType: 'dash', dashLength: 4 }, extra: { column: { type: 'group', width: 22, linearType: 'custom', seriesGap: 5, barBorderCircle: true, customColor: ['#F59E0B'] } } }"
/>
</view>
</view>
</view>
</template>
</view>
</template>
<style scoped lang="scss">
.card-head-text {
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 8rpx;
height: 70%;
background-color: #03a9f4;
border-radius: 12rpx;
}
}
</style>
<!-- 热门文章 Top10 -->
<view class="uh-global-card-glass uh-shadow-xs box-border rounded-xl p-4">
<uh-section-title>
热门文章前10
<template #right>
<view class="flex items-center gap-x-2">
<text class="text-xs text-gray-500">按访问量排序的热门文章</text>
<wd-icon :name="top10ArticlesChart.isExpand ? 'up' : 'down'" size="16px" color="#909399"
@click="top10ArticlesChart.isExpand = !top10ArticlesChart.isExpand" />
</view>
</template>
</uh-section-title>
<view v-show="top10ArticlesChart.isExpand" class="box-border w-full mt-3">
<qiun-data-charts type="column" :chart-data="top10ArticlesChart.data"
:opts="{ color: chartColors, padding: [20, 15, 10, 10], legend: { show: false }, xAxis: { disableGrid: true, fontSize: 10, itemCount: 5 }, yAxis: { gridType: 'dash', dashLength: 4 }, extra: { column: { type: 'group', width: 22, linearType: 'custom', seriesGap: 5, barBorderCircle: true, customColor: ['#F59E0B'] } } }" />
</view>
</view>
</view>
</template>
</view>
</template>