| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549 |
- <template>
- <view class="app-page flex flex-col pa-24" :class="[uniHaloPluginPageClass]">
- <PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId" :error-text="uniHaloPluginAvailableError" />
- <template v-else>
- <!-- 加载区域 -->
- <view v-if="loading == 'loading'" class="loading-wrap pa-24">
- <tm-skeleton model="listAvatr"></tm-skeleton>
- <tm-skeleton model="listAvatr"></tm-skeleton>
- <tm-skeleton model="listAvatr"></tm-skeleton>
- <tm-skeleton model="listAvatr"></tm-skeleton>
- </view>
- <view v-else-if="loading == 'error'" class="content-empty flex flex-center">
- <tm-empty icon="icon-wind-cry" label="加载异常"></tm-empty>
- </view>
- <!-- 内容区域 -->
- <view v-else class="content flex flex-col uh-gap-y-12">
- <!-- 标签统计 -->
- <view class="card bg-white pa-24 round-4">
- <view class="card-head flex items-center justify-between">
- <view class="card-head_title flex items-end uh-gap-x-4">
- <text class="card-head_text">标签统计</text>
- <text class="card-head_subtext">(全部标签的文章数量占比)</text>
- </view>
- <view @click="tagChart.isExpand = !tagChart.isExpand">
- <tm-icons v-if="tagChart.isExpand" :size="24" name="icon-angle-down" color="gray"></tm-icons>
- <tm-icons v-else :size="24" name="icon-angle-up" color="gray"></tm-icons>
- </view>
- </view>
- <view v-show="tagChart.isExpand" class="card-body flex">
- <view class="chart-box">
- <qiun-data-charts :type="tagChart.type" :opts="tagChart.opts" :chartData="tagChart.data" :tooltipFormat="tagChart.tooltipFormat" />
- </view>
- </view>
- </view>
- <!-- 分类统计 -->
- <view class="card bg-white pa-24 round-4">
- <view class="card-head flex items-center justify-between">
- <view class="card-head_title flex items-end uh-gap-x-4">
- <text class="card-head_text">分类统计</text>
- <text class="card-head_subtext">(全部分类的文章数量占比)</text>
- </view>
- <view @click="categoryChart.isExpand = !categoryChart.isExpand">
- <tm-icons v-if="categoryChart.isExpand" :size="24" name="icon-angle-down" color="gray"></tm-icons>
- <tm-icons v-else :size="24" name="icon-angle-up" color="gray"></tm-icons>
- </view>
- </view>
- <view v-show="categoryChart.isExpand" class="card-body flex">
- <view class="chart-box">
- <qiun-data-charts
- :canvasId="categoryChart.id"
- :canvas2d="true"
- :ontouch="true"
- :type="categoryChart.type"
- :opts="categoryChart.opts"
- :chartData="categoryChart.data"
- :tooltipFormat="categoryChart.tooltipFormat"
- />
- </view>
- </view>
- </view>
- <!-- 文章发布趋势 -->
- <view class="card bg-white pa-24 round-4">
- <view class="card-head flex items-center justify-between">
- <view class="card-head_title flex items-end uh-gap-x-4">
- <text class="card-head_text">文章发布趋势</text>
- <text class="card-head_subtext">(按日期统计文章发布数量)</text>
- </view>
- <view @click="trandArticleChart.isExpand = !trandArticleChart.isExpand">
- <tm-icons v-if="trandArticleChart.isExpand" :size="24" name="icon-angle-down" color="gray"></tm-icons>
- <tm-icons v-else :size="24" name="icon-angle-up" color="gray"></tm-icons>
- </view>
- </view>
- <view v-show="trandArticleChart.isExpand" class="card-body flex">
- <heatmap style="width: 100%" :chartData="trandArticleChart.data"></heatmap>
- </view>
- </view>
- <!-- 评论活跃用户 -->
- <view class="card bg-white pa-24 round-4">
- <view class="card-head flex items-center justify-between">
- <view class="card-head_title flex items-end uh-gap-x-4">
- <text class="card-head_text">评论活跃用户</text>
- <text class="card-head_subtext">(按评论作者统计评论数量)</text>
- </view>
- <view @click="userCommentsChart.isExpand = !userCommentsChart.isExpand">
- <tm-icons v-if="userCommentsChart.isExpand" :size="24" name="icon-angle-down" color="gray"></tm-icons>
- <tm-icons v-else :size="24" name="icon-angle-up" color="gray"></tm-icons>
- </view>
- </view>
- <view v-show="userCommentsChart.isExpand" class="card-body flex">
- <view class="chart-box">
- <qiun-data-charts
- :canvasId="userCommentsChart.id"
- :canvas2d="true"
- :ontouch="true"
- :type="userCommentsChart.type"
- :opts="userCommentsChart.opts"
- :chartData="userCommentsChart.data"
- :tooltipFormat="userCommentsChart.tooltipFormat"
- />
- </view>
- </view>
- </view>
- <!-- 热门文章 Top10 -->
- <view class="card bg-white pa-24 round-4">
- <view class="card-head flex items-center justify-between">
- <view class="card-head_title flex items-end uh-gap-x-4">
- <text class="card-head_text">热门文章前10</text>
- <text class="card-head_subtext">(按访问量排序的热门文章)</text>
- </view>
- <view @click="top10ArticlesChart.isExpand = !top10ArticlesChart.isExpand">
- <tm-icons v-if="top10ArticlesChart.isExpand" :size="24" name="icon-angle-down" color="gray"></tm-icons>
- <tm-icons v-else :size="24" name="icon-angle-up" color="gray"></tm-icons>
- </view>
- </view>
- <view class="card-body flex">
- <view class="chart-box">
- <qiun-data-charts
- :canvasId="top10ArticlesChart.id"
- :canvas2d="true"
- :ontouch="true"
- :type="top10ArticlesChart.type"
- :opts="top10ArticlesChart.opts"
- :chartData="top10ArticlesChart.data"
- :tooltipFormat="top10ArticlesChart.tooltipFormat"
- />
- </view>
- </view>
- </view>
- </view>
- </template>
- </view>
- </template>
- <script>
- import dataStatisticsApi from '@/api/v2/plugin.data-statistics.js';
- import { NeedPluginIds, NeedPlugins, checkNeedPluginAvailable } from '@/utils/plugin.js';
- import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
- import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
- import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
- import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
- import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
- import pluginAvailableMixin from '@/common/mixins/pluginAvailable.js';
- import PluginUnavailable from '@/components/plugin-unavailable/plugin-unavailable.vue';
- import heatmap from '@/components/heatmap/heatmap.vue';
- export default {
- mixins: [pluginAvailableMixin],
- name: 'DataVisual',
- components: {
- tmSkeleton,
- tmTranslate,
- tmEmpty,
- tmButton,
- tmIcons,
- heatmap,
- PluginUnavailable
- },
- data() {
- return {
- loading: 'loading',
- statistics: null,
- tagChart: {
- id: 'tagChart',
- isExpand: true,
- type: 'ring',
- data: {},
- tooltipFormat: 'tooltipTag',
- opts: {
- rotate: false,
- rotateLock: false,
- color: ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316', '#ea7ccc', '#0EA5E9'],
- padding: [5, 5, 5, 5],
- dataLabel: false,
- legend: {
- show: false,
- position: 'right',
- lineHeight: 18,
- fontSize: 12
- },
- title: {
- name: '',
- fontSize: 16,
- color: '#666666'
- },
- subtitle: {
- name: '',
- fontSize: 12,
- color: '#eee'
- },
- extra: {
- ring: {
- ringWidth: 36,
- activeOpacity: 0.5,
- activeRadius: 10,
- offsetAngle: -90,
- labelWidth: 15,
- border: true,
- borderWidth: 1,
- borderColor: '#FFFFFF'
- },
- tooltip: {
- legendShape: 'circle',
- fontSize: 11
- }
- }
- }
- },
- categoryChart: {
- id: 'categoryChart',
- isExpand: true,
- type: 'line',
- data: {},
- tooltipFormat: 'tooltipCategory',
- opts: {
- color: ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316', '#ea7ccc', '#0EA5E9'],
- padding: [20, 15, 10, 15],
- touchMoveLimit: 24,
- enableScroll: true,
- legend: {
- show: false
- },
- xAxis: {
- disableGrid: true,
- fontSize: 11,
- scrollShow: true,
- itemCount: 6,
- boundaryGap: 'center',
- format: 'xAxisCategory'
- },
- yAxis: {
- gridType: 'dash',
- dashLength: 4,
- data: [
- {
- fontSize: 11
- }
- ]
- },
- extra: {
- line: {
- type: 'curve',
- width: 2,
- activeType: 'hollow'
- },
- tooltip: {
- legendShape: 'circle',
- fontSize: 11
- }
- }
- }
- },
- trandArticleChart: {
- isExpand: true,
- type: 'hotmap',
- data: [],
- opts: {}
- },
- userCommentsChart: {
- id: 'userCommentsChart',
- isExpand: true,
- loading: true,
- type: 'column',
- data: {},
- tooltipFormat: 'tooltipUserComments',
- opts: {
- color: ['#EF4444', '#F59E0B', '#14B8A6', '#3B82F6', '#10B981', '#8B5CF6', '#EC4899', '#F97316', '#ea7ccc', '#0EA5E9'],
- padding: [20, 15, 10, 10],
- touchMoveLimit: 24,
- enableScroll: true,
- legend: {
- show: false
- },
- xAxis: {
- disableGrid: true,
- boundaryGap: 'justify',
- fontSize: 10,
- scrollShow: true,
- itemCount: 5,
- format: 'xAxisUserComments'
- },
- yAxis: {
- gridType: 'dash',
- dashLength: 4,
- data: [
- {
- min: 0,
- fontSize: 11
- }
- ]
- },
- extra: {
- column: {
- type: 'group',
- width: 22,
- activeBgColor: '#000000',
- activeBgOpacity: 0.08,
- linearType: 'custom',
- seriesGap: 5,
- linearOpacity: 0.7,
- barBorderCircle: true,
- customColor: ['#F59E0B']
- },
- tooltip: {
- legendShape: 'circle',
- fontSize: 11
- }
- }
- }
- },
- top10ArticlesChart: {
- id: 'top10ArticlesChart',
- isExpand: true,
- type: 'column',
- data: {},
- tooltipFormat: 'tooltipTop10Articles',
- opts: {
- color: ['#EF4444', '#F59E0B', '#14B8A6', '#3B82F6', '#10B981', '#8B5CF6', '#EC4899', '#F97316', '#ea7ccc', '#0EA5E9'],
- padding: [20, 15, 10, 10],
- touchMoveLimit: 24,
- enableScroll: true,
- legend: {
- show: false
- },
- xAxis: {
- disableGrid: true,
- boundaryGap: 'justify',
- fontSize: 10,
- scrollShow: true,
- itemCount: 5,
- format: 'xAxisTop10Article'
- },
- yAxis: {
- gridType: 'dash',
- dashLength: 4,
- data: [
- {
- min: 0,
- fontSize: 11
- }
- ]
- },
- extra: {
- column: {
- type: 'group',
- width: 22,
- activeBgColor: '#000000',
- activeBgOpacity: 0.08,
- linearType: 'custom',
- seriesGap: 5,
- linearOpacity: 0.7,
- barBorderCircle: true,
- customColor: ['#F59E0B']
- },
- tooltip: {
- legendShape: 'circle',
- fontSize: 11
- }
- }
- }
- }
- };
- },
- async onReady() {
- // 检查插件
- this.setPluginId(this.NeedPluginIds.PluginDataStatistics);
- this.setPluginError('阿偶,检测到当前插件没有安装或者启用,无法使用功能哦,请联系管理员');
- if (!(await this.checkPluginAvailable())) return;
- this.fnGetData();
- },
- onPullDownRefresh() {
- if (!this.uniHaloPluginAvailable) {
- uni.hideLoading();
- uni.stopPullDownRefresh();
- return;
- }
- this.fnGetData();
- },
- methods: {
- fnGetData() {
- uni.showLoading({
- mask: true,
- title: '加载中...'
- });
- // 设置状态为加载中
- if (!this.isLoadMore) {
- this.loading = 'loading';
- }
- this.loadMoreText = '加载中...';
- dataStatisticsApi
- .getChartData()
- .then((res) => {
- console.log('获取到统计数据:', res);
- this.statistics = res;
- this.handleTagChart();
- this.handleCategoriesChart();
- this.handleTrendArticlesChart();
- this.handleUserCommentsChart();
- this.handleTop10ArticlesChart();
- this.loading = 'success';
- })
- .catch((err) => {
- console.error(err);
- this.loading = 'error';
- this.loadMoreText = '加载失败,请下拉刷新!';
- })
- .finally(() => {
- setTimeout(() => {
- uni.hideLoading();
- uni.stopPullDownRefresh();
- }, 100);
- });
- },
- // 处理标签统计
- handleTagChart() {
- const data = this.statistics.tags.sort((a, b) => b.count - a.count);
- this.tagChart.data = {
- series: [
- {
- data: data.map((item) => {
- return {
- name: item.name,
- value: item.count
- };
- })
- }
- ]
- };
- },
- // 处理分类统计
- handleCategoriesChart() {
- const data = this.statistics.categories.sort((a, b) => b.total - a.total);
- const seriesItemData = data.map((item) => item.total);
- if (Math.max(...seriesItemData) < 10) {
- this.categoryChart.opts.yAxis.data[0].max = 10;
- }
- this.categoryChart.data = {
- categories: data.map((item) => item.name),
- series: [
- {
- name: '分类',
- data: seriesItemData
- }
- ]
- };
- },
- // 处理文章趋势
- handleTrendArticlesChart() {
- this.trandArticleChart.data = this.statistics.articles;
- },
- // 处理评论活跃用户
- handleUserCommentsChart() {
- this.userCommentsChart.loading = true;
- const data = this.statistics.comments.sort((a, b) => b.count - a.count).slice(0, 10);
- const seriesItemData = data.map((item) => item.count);
- if (Math.max(...seriesItemData) < 10) {
- this.userCommentsChart.opts.yAxis.data[0].max = 10;
- }
- this.userCommentsChart.data = {
- categories: data.map((item) => item.username),
- series: [
- {
- name: '评论',
- data: seriesItemData
- }
- ]
- };
- this.userCommentsChart.loading = false;
- },
- // 处理热门文章TOP10
- handleTop10ArticlesChart() {
- const data = this.statistics.top10Articles.sort((a, b) => b.views - a.views).slice(0, 10);
- const seriesItemData = data.map((item) => item.views);
- if (Math.max(...seriesItemData) < 10) {
- this.top10ArticlesChart.opts.yAxis.data[0].max = 10;
- }
- this.top10ArticlesChart.data = {
- categories: data.map((item) => item.name),
- series: [
- {
- name: '评论',
- data: seriesItemData
- }
- ]
- };
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .app-page {
- box-sizing: border-box;
- width: 100vw;
- min-height: 100vh;
- color: #353437;
- }
- .card {
- box-sizing: border-box;
- background-color: rgba(255, 255, 255, 0.95);
- box-shadow: 0 0 12rpx rgba(226, 232, 240, 0.35);
- backdrop-filter: blur(6rpx);
- // border: 2rpx solid #e8edf4;
- }
- .card-head {
- font-size: 32rpx;
- font-weight: bold;
- &_title {
- }
- &_text {
- box-sizing: border-box;
- position: relative;
- padding-left: 24rpx;
- font-size: 30rpx;
- &:before {
- content: '';
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 8rpx;
- height: 70%;
- background-color: #03a9f4;
- border-radius: 12rpx;
- }
- }
- &_subtext {
- font-size: 26rpx;
- font-weight: normal;
- color: #6b7280;
- }
- }
- .card-body {
- box-sizing: border-box;
- margin-top: 24rpx;
- width: 100%;
- overflow: hidden;
- background-color: #fcfdfe;
- border: 2rpx solid #e9eef3;
- border-radius: 12rpx;
- }
- .chart-box {
- width: 100%;
- height: 320rpx;
- }
- </style>
|