Bläddra i källkod

调整最近使用的dashboard

zxlie 5 månader sedan
förälder
incheckning
6611d0cde8
3 ändrade filer med 251 tillägg och 9 borttagningar
  1. 93 1
      apps/background/statistics.js
  2. 1 0
      apps/options/index.html
  3. 157 8
      apps/options/market.js

+ 93 - 1
apps/background/statistics.js

@@ -319,12 +319,104 @@ let Statistics = (function() {
         return toolSet;
     };
     
+    /**
+     * 获取DashBoard统计数据
+     * @returns {Promise<Object>} 统计数据对象
+     */
+    const getDashboardData = async () => {
+        await loadUsageData();
+        // 最近10次使用的工具及时间
+        const recent = [];
+        const recentDetail = [];
+        const dates = Object.keys(usageData.dailyUsage).sort((a, b) => b.localeCompare(a));
+        for (const date of dates) {
+            for (const tool of Object.keys(usageData.dailyUsage[date].tools || {})) {
+                if (!recent.includes(tool)) {
+                    recent.push(tool);
+                    recentDetail.push({ tool, date });
+                    if (recent.length >= 10) break;
+                }
+            }
+            if (recent.length >= 10) break;
+        }
+        // 工具使用总次数排行
+        const mostUsed = Object.entries(usageData.tools)
+            .sort((a, b) => b[1] - a[1])
+            .slice(0, 10)
+            .map(([name, count]) => ({ name, count }));
+        const totalCount = Object.values(usageData.tools).reduce((a, b) => a + b, 0);
+        const activeDays = Object.keys(usageData.dailyUsage).length;
+        const allDates = Object.keys(usageData.dailyUsage).sort();
+        // 最近10天每日使用情况
+        const dailyTrend = allDates.slice(-10).map(date => ({
+            date,
+            count: Object.values(usageData.dailyUsage[date].tools || {}).reduce((a, b) => a + b, 0)
+        }));
+        // 首次和最近活跃日期
+        const firstDate = allDates[0] || '';
+        const lastDate = allDates[allDates.length - 1] || '';
+        // 连续活跃天数
+        let maxStreak = 0, curStreak = 0, prev = '';
+        for (let i = 0; i < allDates.length; i++) {
+            if (i === 0 || (new Date(allDates[i]) - new Date(prev) === 86400000)) {
+                curStreak++;
+            } else {
+                maxStreak = Math.max(maxStreak, curStreak);
+                curStreak = 1;
+            }
+            prev = allDates[i];
+        }
+        maxStreak = Math.max(maxStreak, curStreak);
+        // 本月/本周统计
+        const now = new Date();
+        const thisMonth = now.toISOString().slice(0, 7);
+        const thisWeekMonday = new Date(now.setDate(now.getDate() - now.getDay() + 1)).toISOString().slice(0, 10);
+        let monthCount = 0, weekCount = 0;
+        allDates.forEach(date => {
+            const cnt = Object.values(usageData.dailyUsage[date].tools || {}).reduce((a, b) => a + b, 0);
+            if (date.startsWith(thisMonth)) monthCount += cnt;
+            if (date >= thisWeekMonday) weekCount += cnt;
+        });
+        // 平均每日使用次数
+        const avgPerDay = activeDays ? Math.round(totalCount / activeDays * 10) / 10 : 0;
+        // 最活跃的一天
+        let maxDay = { date: '', count: 0 };
+        allDates.forEach(date => {
+            const cnt = Object.values(usageData.dailyUsage[date].tools || {}).reduce((a, b) => a + b, 0);
+            if (cnt > maxDay.count) maxDay = { date, count: cnt };
+        });
+        // 最近未使用天数
+        let daysSinceLast = 0;
+        if (lastDate) {
+            const diff = Math.floor((new Date() - new Date(lastDate)) / 86400000);
+            daysSinceLast = diff > 0 ? diff : 0;
+        }
+        return {
+            recent,
+            recentDetail,
+            mostUsed,
+            totalCount,
+            activeDays,
+            dailyTrend,
+            firstDate,
+            lastDate,
+            maxStreak,
+            monthCount,
+            weekCount,
+            avgPerDay,
+            maxDay,
+            daysSinceLast,
+            allDates
+        };
+    };
+    
     return {
         init,
         recordInstallation,
         recordUpdate,
         recordToolUsage,
-        getRecentUsedTools
+        getRecentUsedTools,
+        getDashboardData
     };
 })();
 

+ 1 - 0
apps/options/index.html

@@ -280,6 +280,7 @@
             </div>
 
             <!-- 工具展示区 -->
+            <div id="fh-dashboard-panel" style="display:none;"></div>
             <div class="tools-grid">
                 <div :class="['tools-container', viewMode]">
                     <div v-for="tool in filteredTools" 

+ 157 - 8
apps/options/market.js

@@ -2,6 +2,7 @@ import Awesome from '../background/awesome.js'
 import MSG_TYPE from '../static/js/common.js';
 import Settings from './settings.js';
 import Statistics from '../background/statistics.js';
+import toolMap from '../background/tools.js';
 
 // 工具分类定义
 const TOOL_CATEGORIES = [
@@ -61,6 +62,8 @@ new Vue({
         },
 
         recentCount: 0,
+        showDashboard: false, // 是否显示DashBoard
+        dashboardData: null, // DashBoard数据
     },
 
     async created() {
@@ -609,12 +612,11 @@ new Vue({
                 this.currentView = 'all';
                 this.updateActiveTools('all');
             }
-            
             this.currentCategory = category;
             this.searchKey = '';
-            
             // 确保工具显示正确
             this.activeTools = { ...this.originalTools };
+            this.showDashboard = false;
         },
 
         handleSort() {
@@ -661,6 +663,7 @@ new Vue({
             await this.updateActiveTools('installed');
             // 更新已安装工具数量
             await this.updateInstalledCount();
+            this.showDashboard = false;
         },
 
         showMyFavorites() {
@@ -668,16 +671,24 @@ new Vue({
             this.currentCategory = '';
             this.searchKey = '';
             this.updateActiveTools('favorites');
+            this.showDashboard = false;
         },
 
         async showRecentUsed() {
             this.currentView = 'recent';
             this.currentCategory = '';
             this.searchKey = '';
-            // 实时获取最近使用
-            this.recentUsed = await Statistics.getRecentUsedTools(10);
-            this.recentCount = this.recentUsed.length;
-            await this.updateActiveTools('recent');
+            // 拉取DashBoard数据并显示
+            this.dashboardData = await Statistics.getDashboardData();
+            this.showDashboard = true;
+            // 不再更新工具列表
+        },
+
+        // 关闭DashBoard,恢复工具列表
+        closeDashboard() {
+            this.showDashboard = false;
+            this.currentView = 'all';
+            this.updateActiveTools('all');
         },
 
         // 重置工具列表到原始状态
@@ -1186,6 +1197,132 @@ new Vue({
             const recent = await Statistics.getRecentUsedTools(10);
             return recent.length;
         },
+
+        renderDashboard() {
+            const dashboardContainerId = 'fh-dashboard-panel';
+            let container = document.getElementById(dashboardContainerId);
+            // 只在showDashboard且currentView为recent时隐藏工具列表
+            const grid = document.querySelector('.tools-grid');
+            if (!this.showDashboard || this.currentView !== 'recent') {
+                if (container) container.style.display = 'none';
+                if (grid) grid.style.display = '';
+                return;
+            }
+            if (grid) grid.style.display = 'none';
+            if (!container) {
+                container = document.createElement('div');
+                container.id = dashboardContainerId;
+                container.style = 'padding:32px; background:#fff; border-radius:8px; margin:24px; box-shadow:0 2px 12px #eee; min-width:700px;';
+                const main = document.querySelector('.market-main') || document.querySelector('.market-content');
+                if (main) main.prepend(container);
+                else document.body.appendChild(container);
+            }
+            container.style.display = 'block';
+            const data = this.dashboardData || {};
+            // 工具ID转中文名和icon
+            const toolName = (key) => (this.originalTools && this.originalTools[key] && this.originalTools[key].name) ? this.originalTools[key].name : key;
+            const toolIcon = (key) => {
+                if (toolMap[key] && toolMap[key].menuConfig && toolMap[key].menuConfig[0] && toolMap[key].menuConfig[0].icon) {
+                    return toolMap[key].menuConfig[0].icon;
+                }
+                return toolName(key).slice(0,1);
+            };
+            // 插入美观样式
+            if (!document.getElementById('fh-dashboard-style')) {
+                const style = document.createElement('style');
+                style.id = 'fh-dashboard-style';
+                style.innerHTML = `
+                .fh-dashboard-cards { display: flex; flex-wrap: wrap; gap: 18px; margin-bottom: 24px;}
+                .fh-card { background: linear-gradient(135deg,#f7f9fa 60%,#e3eafc 100%); border-radius: 12px; box-shadow:0 2px 8px #f0f0f0; padding:18px 24px; min-width:120px; flex:1; text-align:center; font-size:15px;}
+                .fh-card.main { background: linear-gradient(135deg,#e3fcec 60%,#e3eafc 100%);}
+                .fh-card-num { font-size:32px; font-weight:bold; margin-bottom:4px;}
+                .fh-calendar { display:inline-block; margin-left:12px; }
+                .fh-cal-cell { display:inline-block; width:18px; height:18px; line-height:18px; text-align:center; border-radius:3px; margin:1px; background:#eee; color:#888; font-size:12px;}
+                .fh-cal-cell.used { background:#4285f4; color:#fff; font-weight:bold;}
+                .fh-dashboard-section { background:#fff; border-radius:12px; box-shadow:0 1px 4px #f0f0f0; padding:18px 24px; margin-bottom:24px;}
+                .fh-dashboard-header { margin-bottom:24px; }
+                .fh-dashboard-header h2 { font-size:22px; margin:0; }
+                .fh-tool-bar { display:inline-block; width:18px; height:18px; border-radius:3px; background:#e3eafc; margin-right:6px; vertical-align:middle; }
+                .fh-tool-bar-inner { display:inline-block; height:100%; border-radius:3px; background:#4285f4; }
+                .fh-tool-list { margin:0; padding:0; list-style:none; }
+                .fh-tool-list li { margin-bottom:10px; }
+                .fh-tool-icon { display:inline-block; width:18px; height:18px; border-radius:3px; background:#e3eafc; margin-right:6px; vertical-align:middle; text-align:center; font-size:14px; }
+                .fh-dashboard-sub { color:#888; font-size:13px; margin-bottom:8px; }
+                `;
+                document.head.appendChild(style);
+            }
+            // 30天活跃日历
+            const today = new Date();
+            let calendar = '<div class="fh-calendar">';
+            for(let i=29;i>=0;i--){
+                const d = new Date(today.getTime()-i*86400000);
+                const ds = d.toISOString().slice(0,10);
+                const used = data.allDates && data.allDates.includes(ds);
+                calendar += `<span class="fh-cal-cell${used?' used':''}" title="${ds}">${d.getDate()}</span>`;
+            }
+            calendar += '</div>';
+            // 主卡片区块
+            let html = `
+            <div class="fh-dashboard-header">
+              <h2>FeHelper 使用统计仪表盘 <span style="font-size:16px;color:#bbb;">(近30天)</span></h2>
+            </div>
+            <div class="fh-dashboard-cards">
+              <div class="fh-card main"><div class="fh-card-num">${data.totalCount||0}</div><div>总使用次数</div></div>
+              <div class="fh-card main"><div class="fh-card-num">${data.activeDays||0}</div><div>活跃天数</div></div>
+              <div class="fh-card"><div>${data.firstDate||'-'}<br>~<br>${data.lastDate||'-'}</div><div>统计区间</div></div>
+              <div class="fh-card"><div class="fh-card-num">${data.maxStreak||0}</div><div>最长连续活跃天数</div></div>
+              <div class="fh-card"><div class="fh-card-num">${data.monthCount||0}</div><div>本月使用次数</div></div>
+              <div class="fh-card"><div class="fh-card-num">${data.weekCount||0}</div><div>本周使用次数</div></div>
+              <div class="fh-card"><div class="fh-card-num">${data.avgPerDay||0}</div><div>平均每日使用</div></div>
+              <div class="fh-card"><div>${data.maxDay.date||'-'}<br><b>${data.maxDay.count||0}</b></div><div>最活跃日</div></div>
+              <div class="fh-card"><div class="fh-card-num">${data.daysSinceLast||0}</div><div>最近未使用天数</div></div>
+            </div>
+            <div class="fh-dashboard-section">
+                <div class="fh-dashboard-sub">近30天活跃日历:</div>${calendar}
+            </div>
+            <div class="fh-dashboard-section" style="display:flex;gap:32px;flex-wrap:wrap;">
+                <div style="flex:2;min-width:320px;">
+                    <div class="fh-dashboard-sub"><b>最近10天活跃趋势:</b></div>
+                    <div style="display:flex;align-items:end;height:80px;margin-top:8px;">
+                        ${
+                            (data.dailyTrend||[]).map(d=>{
+                                const max = Math.max(...(data.dailyTrend||[]).map(x=>x.count),1);
+                                return `<div title='${d.date}: ${d.count}' style='width:20px;height:${d.count/max*60}px;background:#4285f4;margin-right:4px;border-radius:2px;'></div>`;
+                            }).join('')
+                        }
+                    </div>
+                    <div style="font-size:12px;color:#888;margin-top:4px;">
+                        ${(data.dailyTrend||[]).map(d=>`<span style='display:inline-block;width:20px;text-align:center;'>${d.date.slice(5)}</span>`).join('')}
+                    </div>
+                </div>
+                <div style="flex:3;min-width:320px;">
+                    <div class="fh-dashboard-sub"><b>使用最多的工具:</b></div>
+                    <ul class="fh-tool-list">
+                        ${(data.mostUsed||[]).map(t=>{
+                            const percent = data.totalCount ? Math.round(t.count/data.totalCount*100) : 0;
+                            return `<li style='margin-bottom:12px;display:flex;align-items:center;'>
+                                <span class='fh-tool-icon'>${toolIcon(t.name)}</span>
+                                <span style='display:inline-block;width:100px;'>${toolName(t.name)}</span>
+                                <span style='display:inline-block;width:60px;color:#888;'>(x${t.count})</span>
+                                <span class='fh-tool-bar' style='width:80px;height:10px;margin:0 8px;'>
+                                    <span class='fh-tool-bar-inner' style='width:${percent*0.8}px;'></span>
+                                </span>
+                                <span style='color:#888;'>${percent}%</span>
+                            </li>`;
+                        }).join('')}
+                    </ul>
+                </div>
+            </div>
+            <div class="fh-dashboard-section">
+                <div class="fh-dashboard-sub"><b>最近10次使用的工具:</b></div>
+                <ul style="margin:8px 0 0 0;padding:0;list-style:none;">
+                    ${(data.recentDetail||[]).map(t=>`<li style='display:inline-block;margin-right:24px;'>${toolName(t.tool)} <span style='color:#888;'>(${t.date})</span></li>`).join('')}
+                </ul>
+            </div>
+            `;
+            container.innerHTML = html;
+            window.__vue__ = this;
+        },
     },
 
     watch: {
@@ -1209,8 +1346,20 @@ new Vue({
                     this.searchKey = '';
                 }
             }
-        }
-    }
+        },
+        showDashboard(val) {
+            this.renderDashboard();
+        },
+        dashboardData(val) {
+            this.renderDashboard();
+        },
+    },
+
+    mounted() {
+        this.$nextTick(() => {
+            this.renderDashboard();
+        });
+    },
 });
 
 // 添加滚动事件监听