market.js 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377
  1. import Awesome from '../background/awesome.js'
  2. import MSG_TYPE from '../static/js/common.js';
  3. import Settings from './settings.js';
  4. import Statistics from '../background/statistics.js';
  5. import toolMap from '../background/tools.js';
  6. // 工具分类定义
  7. const TOOL_CATEGORIES = [
  8. { key: 'dev', name: '开发工具类', tools: ['json-format', 'json-diff', 'code-beautify', 'code-compress', 'postman', 'websocket', 'regexp','page-timing'] },
  9. { key: 'encode', name: '编解码转换类', tools: ['en-decode', 'trans-radix', 'timestamp', 'trans-color'] },
  10. { key: 'image', name: '图像处理类', tools: ['qr-code', 'image-base64', 'svg-converter', 'chart-maker', 'poster-maker' ,'screenshot', 'color-picker'] },
  11. { key: 'productivity', name: '效率工具类', tools: ['aiagent', 'sticky-notes', 'html2markdown', 'page-monkey'] },
  12. { key: 'calculator', name: '计算工具类', tools: ['crontab', 'loan-rate', 'password'] },
  13. { key: 'other', name: '其他工具', tools: [] }
  14. ];
  15. // Vue实例
  16. new Vue({
  17. el: '#marketContainer',
  18. data: {
  19. manifest: { version: '0.0.0' },
  20. searchKey: '',
  21. currentCategory: '',
  22. sortType: 'default',
  23. viewMode: 'list', // 默认网格视图
  24. categories: TOOL_CATEGORIES,
  25. favorites: new Set(),
  26. recentUsed: [],
  27. loading: true,
  28. originalTools: {}, // 保存原始工具数据
  29. currentView: 'all', // 当前视图类型(all/installed/favorites/recent)
  30. activeTools: {}, // 当前显示的工具列表
  31. installedCount: 0, // 已安装工具数量
  32. // 版本相关
  33. latestVersion: '', // 最新版本号
  34. needUpdate: false, // 是否需要更新
  35. // 设置相关
  36. showSettingsModal: false,
  37. defaultKey: 'Alt+Shift+J', // 默认快捷键
  38. countDown: 0, // 夜间模式倒计时
  39. selectedOpts: [], // 选中的选项
  40. menuDownloadCrx: false, // 菜单-插件下载
  41. menuFeHelperSeting: false, // 菜单-FeHelper设置
  42. isFirefox: false, // 是否Firefox浏览器
  43. // 打赏相关
  44. showDonateModal: false,
  45. donate: {
  46. text: '感谢你对FeHelper的认可和支持!',
  47. image: './donate.jpeg'
  48. },
  49. // 确认对话框
  50. confirmDialog: {
  51. show: false,
  52. title: '操作确认',
  53. message: '',
  54. callback: null,
  55. data: null
  56. },
  57. recentCount: 0,
  58. showDashboard: false, // 是否显示DashBoard
  59. dashboardData: null, // DashBoard数据
  60. },
  61. async created() {
  62. await this.initData();
  63. this.recentCount = (await Statistics.getRecentUsedTools(10)).length;
  64. // 初始化后更新已安装工具数量
  65. this.updateInstalledCount();
  66. // 恢复用户的视图模式设置
  67. this.loadViewMode();
  68. // 加载设置项
  69. this.loadSettings();
  70. // 检查浏览器类型
  71. this.checkBrowserType();
  72. // 检查版本更新
  73. this.checkVersionUpdate();
  74. // 检查URL中是否有donate_from参数
  75. this.checkDonateParam();
  76. },
  77. computed: {
  78. filteredTools() {
  79. if (this.loading) {
  80. return [];
  81. }
  82. // 获取当前工具列表
  83. let result = Object.values(this.activeTools).map(tool => ({
  84. ...tool,
  85. favorite: this.favorites.has(tool.key)
  86. }));
  87. // 搜索过滤
  88. if (this.searchKey) {
  89. const key = this.searchKey.toLowerCase();
  90. result = result.filter(tool =>
  91. tool.name.toLowerCase().includes(key) ||
  92. tool.tips.toLowerCase().includes(key)
  93. );
  94. }
  95. // 分类过滤,在所有视图下生效
  96. if (this.currentCategory) {
  97. const category = TOOL_CATEGORIES.find(c => c.key === this.currentCategory);
  98. const categoryTools = category ? category.tools : [];
  99. result = result.filter(tool => categoryTools.includes(tool.key));
  100. }
  101. // 排序
  102. switch (this.sortType) {
  103. case 'newest':
  104. result.sort((a, b) => (b.updateTime || 0) - (a.updateTime || 0));
  105. break;
  106. case 'hot':
  107. result.sort((a, b) => (b.updateTime || 0) - (a.updateTime || 0));
  108. break;
  109. default:
  110. const allTools = TOOL_CATEGORIES.reduce((acc, category) => {
  111. acc.push(...category.tools);
  112. return acc;
  113. }, []);
  114. result.sort((a, b) => {
  115. const indexA = allTools.indexOf(a.key);
  116. const indexB = allTools.indexOf(b.key);
  117. // 如果工具不在任何类别中,放到最后
  118. if (indexA === -1 && indexB === -1) {
  119. return a.key.localeCompare(b.key); // 字母顺序排序
  120. }
  121. if (indexA === -1) return 1;
  122. if (indexB === -1) return -1;
  123. return indexA - indexB;
  124. });
  125. }
  126. return result;
  127. }
  128. },
  129. methods: {
  130. async initData() {
  131. try {
  132. this.loading = true;
  133. // 获取manifest信息
  134. const manifest = await chrome.runtime.getManifest();
  135. this.manifest = manifest;
  136. // 从 Awesome.getAllTools 获取工具列表
  137. const tools = await Awesome.getAllTools();
  138. // 获取收藏数据
  139. const favorites = await this.getFavoritesData();
  140. this.favorites = new Set(favorites);
  141. // 获取最近使用数据
  142. const recentUsed = await this.getRecentUsedData();
  143. this.recentUsed = recentUsed;
  144. this.recentCount = recentUsed.length;
  145. // 获取已安装工具列表
  146. const installedTools = await Awesome.getInstalledTools();
  147. // 处理工具数据
  148. const processedTools = {};
  149. Object.entries(tools).forEach(([key, tool]) => {
  150. // 检查工具是否已安装
  151. const isInstalled = installedTools.hasOwnProperty(key);
  152. // 检查是否有右键菜单
  153. const hasMenu = tool.menu || false;
  154. processedTools[key] = {
  155. ...tool,
  156. key, // 添加key到工具对象中
  157. updateTime: Date.now() - Math.floor(Math.random() * 30) * 24 * 60 * 60 * 1000,
  158. installed: isInstalled, // 使用实时安装状态
  159. inContextMenu: hasMenu, // 使用实时菜单状态
  160. systemInstalled: tool.systemInstalled || false, // 是否系统预装
  161. favorite: this.favorites.has(key)
  162. };
  163. });
  164. this.originalTools = processedTools;
  165. // 初始化activeTools为所有工具
  166. this.activeTools = { ...processedTools };
  167. // 更新"其他工具"类别
  168. this.updateOtherCategory(Object.keys(processedTools));
  169. // 默认选中"全部分类"
  170. this.currentCategory = '';
  171. } catch (error) {
  172. console.error('初始化数据失败:', error);
  173. } finally {
  174. this.loading = false;
  175. }
  176. },
  177. // 更新"其他工具"类别,将未分类的工具添加到此类别
  178. updateOtherCategory(allToolKeys) {
  179. // 获取所有已分类的工具
  180. const categorizedTools = new Set();
  181. TOOL_CATEGORIES.forEach(category => {
  182. if (category.key !== 'other') {
  183. category.tools.forEach(tool => categorizedTools.add(tool));
  184. }
  185. });
  186. // 找出未分类的工具
  187. const uncategorizedTools = allToolKeys.filter(key => !categorizedTools.has(key));
  188. // 更新"其他工具"类别
  189. const otherCategory = TOOL_CATEGORIES.find(category => category.key === 'other');
  190. if (otherCategory) {
  191. otherCategory.tools = uncategorizedTools;
  192. }
  193. },
  194. // 检查版本更新
  195. async checkVersionUpdate() {
  196. try {
  197. // 获取已安装的版本号
  198. const currentVersion = this.manifest.version;
  199. // 尝试从本地存储获取最新版本信息,避免频繁请求
  200. const cachedData = await new Promise(resolve => {
  201. chrome.storage.local.get('fehelper_latest_version_data', data => {
  202. resolve(data.fehelper_latest_version_data || null);
  203. });
  204. });
  205. // 检查是否需要重新获取版本信息:
  206. // 1. 缓存不存在
  207. // 2. 缓存已过期(超过24小时)
  208. // 3. 缓存的当前版本与实际版本不同(说明插件已更新)
  209. const now = Date.now();
  210. const cacheExpired = !cachedData || !cachedData.timestamp || (now - cachedData.timestamp > 24 * 60 * 60 * 1000);
  211. const versionChanged = cachedData && cachedData.currentVersion !== currentVersion;
  212. if (cacheExpired || versionChanged) {
  213. try {
  214. console.log('开始获取最新版本信息...');
  215. // 使用shields.io的JSON API获取最新版本号
  216. const response = await fetch('https://img.shields.io/chrome-web-store/v/pkgccpejnmalmdinmhkkfafefagiiiad.json');
  217. if (!response.ok) {
  218. throw new Error(`HTTP错误:${response.status}`);
  219. }
  220. const data = await response.json();
  221. // 提取版本号 - shields.io返回的数据中包含版本信息
  222. let latestVersion = '';
  223. if (data && data.value) {
  224. // 去掉版本号前的'v'字符(如果有)
  225. latestVersion = data.value.replace(/^v/, '');
  226. console.log('获取到最新版本号:', latestVersion);
  227. }
  228. // 比较版本号
  229. const needUpdate = this.compareVersions(currentVersion, latestVersion) < 0;
  230. console.log('当前版本:', currentVersion, '最新版本:', latestVersion, '需要更新:', needUpdate);
  231. // 保存到本地存储中
  232. await chrome.storage.local.set({
  233. 'fehelper_latest_version_data': {
  234. timestamp: now,
  235. currentVersion, // 保存当前检查时的版本号
  236. latestVersion,
  237. needUpdate
  238. }
  239. });
  240. this.latestVersion = latestVersion;
  241. this.needUpdate = needUpdate;
  242. } catch (fetchError) {
  243. console.error('获取最新版本信息失败:', fetchError);
  244. // 获取失败时不显示更新按钮
  245. this.needUpdate = false;
  246. // 如果是版本变更导致的重新检查,但获取失败,则使用缓存数据
  247. if (versionChanged && cachedData) {
  248. this.latestVersion = cachedData.latestVersion || '';
  249. // 比较新的currentVersion和缓存的latestVersion
  250. this.needUpdate = this.compareVersions(currentVersion, cachedData.latestVersion) < 0;
  251. }
  252. }
  253. } else {
  254. // 使用缓存数据
  255. console.log('使用缓存的版本信息');
  256. this.latestVersion = cachedData.latestVersion || '';
  257. this.needUpdate = cachedData.needUpdate || false;
  258. }
  259. } catch (error) {
  260. console.error('检查版本更新失败:', error);
  261. this.needUpdate = false; // 出错时不显示更新提示
  262. }
  263. },
  264. // 比较版本号:如果v1 < v2返回-1,v1 = v2返回0,v1 > v2返回1
  265. compareVersions(v1, v2) {
  266. // 将版本号拆分为数字数组
  267. const v1Parts = v1.split('.').map(Number);
  268. const v2Parts = v2.split('.').map(Number);
  269. // 计算两个版本号中较长的长度
  270. const maxLength = Math.max(v1Parts.length, v2Parts.length);
  271. // 比较每一部分
  272. for (let i = 0; i < maxLength; i++) {
  273. // 获取当前部分,如果不存在则视为0
  274. const part1 = v1Parts[i] || 0;
  275. const part2 = v2Parts[i] || 0;
  276. // 比较当前部分
  277. if (part1 < part2) return -1;
  278. if (part1 > part2) return 1;
  279. }
  280. // 所有部分都相等
  281. return 0;
  282. },
  283. // 打开Chrome商店页面
  284. openStorePage() {
  285. try {
  286. console.log('开始请求检查更新...');
  287. // 使用Chrome Extension API请求检查更新
  288. // Manifest V3中requestUpdateCheck返回Promise,结果是一个对象而不是数组
  289. chrome.runtime.requestUpdateCheck().then(result => {
  290. // 正确获取status和details,它们是result对象的属性
  291. console.log('更新检查结果:', result);
  292. const status = result.status;
  293. const details = result.details;
  294. console.log('更新检查状态:', status, '详情:', details);
  295. this.handleUpdateStatus(status, details);
  296. }).catch(error => {
  297. console.error('更新检查失败:', error);
  298. this.handleUpdateError(error);
  299. });
  300. } catch (error) {
  301. console.error('请求更新出错:', error);
  302. this.handleUpdateError(error);
  303. }
  304. },
  305. // 处理更新状态
  306. handleUpdateStatus(status, details) {
  307. console.log(`处理更新状态: ${status}`, details);
  308. if (status === 'update_available') {
  309. console.log('发现更新:', details);
  310. // 显示更新通知
  311. this.showNotification({
  312. title: 'FeHelper 更新',
  313. message: '已发现新版本,正在更新...'
  314. });
  315. // 重新加载扩展以应用更新
  316. setTimeout(() => {
  317. console.log('重新加载扩展...');
  318. chrome.runtime.reload();
  319. }, 1000);
  320. } else if (status === 'no_update') {
  321. // 如果没有可用更新,但用户点击了更新按钮
  322. this.showNotification({
  323. title: 'FeHelper 更新',
  324. message: '您的FeHelper已经是最新版本。'
  325. });
  326. } else {
  327. // 其他情况,如更新检查失败等
  328. console.log('其他更新状态:', status);
  329. // 备选方案:跳转到官方网站
  330. chrome.tabs.create({
  331. url: 'https://baidufe.com/fehelper'
  332. });
  333. this.showNotification({
  334. title: 'FeHelper 更新',
  335. message: '自动更新失败,请访问FeHelper官网手动获取最新版本。'
  336. });
  337. }
  338. },
  339. // 处理更新错误
  340. handleUpdateError(error) {
  341. console.error('更新过程中出错:', error);
  342. // 出错时跳转到官方网站
  343. chrome.tabs.create({
  344. url: 'https://baidufe.com/fehelper'
  345. });
  346. this.showNotification({
  347. title: 'FeHelper 更新错误',
  348. message: '更新过程中出现错误,请手动检查更新。'
  349. });
  350. },
  351. // 显示通知的统一方法
  352. showNotification(options) {
  353. try {
  354. console.log('准备显示通知:', options);
  355. // 定义通知ID,方便后续关闭
  356. const notificationId = 'fehelper-update-notification';
  357. const simpleNotificationId = 'fehelper-simple-notification';
  358. // 直接尝试创建通知,不检查权限
  359. // Chrome扩展在manifest中已声明notifications权限,应该可以直接使用
  360. const notificationOptions = {
  361. type: 'basic',
  362. iconUrl: chrome.runtime.getURL('static/img/fe-48.png'),
  363. title: options.title || 'FeHelper',
  364. message: options.message || '',
  365. priority: 2,
  366. requireInteraction: false, // 改为false,因为我们会手动关闭
  367. silent: false // 播放音效
  368. };
  369. console.log('通知选项:', notificationOptions);
  370. // 首先尝试直接创建通知
  371. chrome.notifications.create(notificationId, notificationOptions, (createdId) => {
  372. const error = chrome.runtime.lastError;
  373. if (error) {
  374. console.error('创建通知出错:', error);
  375. // 通知创建失败,尝试使用alert作为备选方案
  376. alert(`${options.title}: ${options.message}`);
  377. // 再尝试使用不同的选项创建通知
  378. const simpleOptions = {
  379. type: 'basic',
  380. iconUrl: chrome.runtime.getURL('static/img/fe-48.png'),
  381. title: options.title || 'FeHelper',
  382. message: options.message || ''
  383. };
  384. // 使用简化选项再次尝试
  385. chrome.notifications.create(simpleNotificationId, simpleOptions, (simpleId) => {
  386. if (chrome.runtime.lastError) {
  387. console.error('简化通知创建也失败:', chrome.runtime.lastError);
  388. } else {
  389. console.log('简化通知已创建,ID:', simpleId);
  390. // 3秒后自动关闭简化通知
  391. setTimeout(() => {
  392. chrome.notifications.clear(simpleId, (wasCleared) => {
  393. console.log('简化通知已关闭:', wasCleared);
  394. });
  395. }, 3000);
  396. }
  397. });
  398. } else {
  399. console.log('通知已成功创建,ID:', createdId);
  400. // 3秒后自动关闭通知
  401. setTimeout(() => {
  402. chrome.notifications.clear(createdId, (wasCleared) => {
  403. console.log('通知已关闭:', wasCleared);
  404. });
  405. }, 3000);
  406. }
  407. });
  408. // 同时使用内置UI显示消息
  409. this.showInPageNotification(options);
  410. } catch (error) {
  411. console.error('显示通知时出错:', error);
  412. // 降级为alert
  413. alert(`${options.title}: ${options.message}`);
  414. }
  415. },
  416. // 在页面内显示通知消息
  417. showInPageNotification(options) {
  418. try {
  419. // 创建一个通知元素
  420. const notificationEl = document.createElement('div');
  421. notificationEl.className = 'in-page-notification';
  422. notificationEl.innerHTML = `
  423. <div class="notification-content">
  424. <div class="notification-title">${options.title || 'FeHelper'}</div>
  425. <div class="notification-message">${options.message || ''}</div>
  426. </div>
  427. <button class="notification-close">×</button>
  428. `;
  429. // 添加样式
  430. const style = document.createElement('style');
  431. style.textContent = `
  432. .in-page-notification {
  433. position: fixed;
  434. bottom: 20px;
  435. right: 20px;
  436. background-color: #4285f4;
  437. color: white;
  438. padding: 15px;
  439. border-radius: 8px;
  440. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  441. z-index: 9999;
  442. display: flex;
  443. align-items: center;
  444. justify-content: space-between;
  445. min-width: 300px;
  446. animation: slideIn 0.3s ease-out;
  447. }
  448. .notification-content {
  449. flex: 1;
  450. }
  451. .notification-title {
  452. font-weight: bold;
  453. margin-bottom: 5px;
  454. }
  455. .notification-message {
  456. font-size: 14px;
  457. }
  458. .notification-close {
  459. background: none;
  460. border: none;
  461. color: white;
  462. font-size: 20px;
  463. cursor: pointer;
  464. margin-left: 10px;
  465. padding: 0 5px;
  466. }
  467. @keyframes slideIn {
  468. from { transform: translateX(100%); opacity: 0; }
  469. to { transform: translateX(0); opacity: 1; }
  470. }
  471. @keyframes slideOut {
  472. from { transform: translateX(0); opacity: 1; }
  473. to { transform: translateX(100%); opacity: 0; }
  474. }
  475. `;
  476. // 添加到页面
  477. document.head.appendChild(style);
  478. document.body.appendChild(notificationEl);
  479. // 点击关闭按钮移除通知
  480. const closeBtn = notificationEl.querySelector('.notification-close');
  481. if (closeBtn) {
  482. closeBtn.addEventListener('click', () => {
  483. notificationEl.style.animation = 'slideOut 0.3s ease-out forwards';
  484. notificationEl.addEventListener('animationend', () => {
  485. notificationEl.remove();
  486. });
  487. });
  488. }
  489. // 3秒后自动移除(从5秒改为3秒)
  490. setTimeout(() => {
  491. notificationEl.style.animation = 'slideOut 0.3s ease-out forwards';
  492. notificationEl.addEventListener('animationend', () => {
  493. notificationEl.remove();
  494. });
  495. }, 3000);
  496. console.log('页内通知已显示,将在3秒后自动关闭');
  497. } catch (error) {
  498. console.error('创建页内通知出错:', error);
  499. }
  500. },
  501. async getFavoritesData() {
  502. return new Promise((resolve) => {
  503. chrome.storage.local.get('favorites', (result) => {
  504. resolve(result.favorites || []);
  505. });
  506. });
  507. },
  508. async getRecentUsedData() {
  509. // 直接从Statistics模块获取最近使用的工具
  510. return await Statistics.getRecentUsedTools(10);
  511. },
  512. async saveFavorites() {
  513. try {
  514. await chrome.storage.local.set({
  515. favorites: Array.from(this.favorites)
  516. });
  517. // 更新工具的收藏状态
  518. Object.keys(this.originalTools).forEach(key => {
  519. this.originalTools[key].favorite = this.favorites.has(key);
  520. });
  521. } catch (error) {
  522. console.error('保存收藏失败:', error);
  523. }
  524. },
  525. handleSearch() {
  526. // 搜索时不重置视图类型,允许在已过滤的结果中搜索
  527. },
  528. handleCategoryChange(category) {
  529. // 切换到全部工具视图
  530. if (this.currentView !== 'all') {
  531. this.currentView = 'all';
  532. this.updateActiveTools('all');
  533. }
  534. this.currentCategory = category;
  535. this.searchKey = '';
  536. // 确保工具显示正确
  537. this.activeTools = { ...this.originalTools };
  538. this.showDashboard = false;
  539. },
  540. handleSort() {
  541. // 排序逻辑已在computed中实现
  542. },
  543. getCategoryCount(categoryKey) {
  544. const category = TOOL_CATEGORIES.find(c => c.key === categoryKey);
  545. const categoryTools = category ? category.tools : [];
  546. return categoryTools.length;
  547. },
  548. async getInstalledCount() {
  549. try {
  550. // 使用Awesome.getInstalledTools实时获取已安装工具数量
  551. const installedTools = await Awesome.getInstalledTools();
  552. return Object.keys(installedTools).length;
  553. } catch (error) {
  554. console.error('获取已安装工具数量失败:', error);
  555. // 回退到本地数据
  556. return Object.values(this.originalTools).filter(tool =>
  557. tool.installed || tool.systemInstalled || false
  558. ).length;
  559. }
  560. },
  561. getFavoritesCount() {
  562. return this.favorites.size;
  563. },
  564. getToolCategory(toolKey) {
  565. for (const category of TOOL_CATEGORIES) {
  566. if (category.tools.includes(toolKey)) {
  567. return category.key;
  568. }
  569. }
  570. return 'other';
  571. },
  572. async showMyInstalled() {
  573. this.currentView = 'installed';
  574. this.currentCategory = '';
  575. this.searchKey = '';
  576. await this.updateActiveTools('installed');
  577. // 更新已安装工具数量
  578. await this.updateInstalledCount();
  579. this.showDashboard = false;
  580. },
  581. showMyFavorites() {
  582. this.currentView = 'favorites';
  583. this.currentCategory = '';
  584. this.searchKey = '';
  585. this.updateActiveTools('favorites');
  586. this.showDashboard = false;
  587. },
  588. async showRecentUsed() {
  589. this.currentView = 'recent';
  590. this.currentCategory = '';
  591. this.searchKey = '';
  592. // 拉取DashBoard数据并显示
  593. this.dashboardData = await Statistics.getDashboardData();
  594. this.showDashboard = true;
  595. // 不再更新工具列表
  596. },
  597. // 关闭DashBoard,恢复工具列表
  598. closeDashboard() {
  599. this.showDashboard = false;
  600. this.currentView = 'all';
  601. this.updateActiveTools('all');
  602. },
  603. // 重置工具列表到原始状态
  604. resetTools() {
  605. this.currentView = 'all';
  606. },
  607. // 安装工具
  608. async installTool(toolKey) {
  609. try {
  610. // 查找可能存在的按钮元素
  611. const btnElement = document.querySelector(`button[data-tool="${toolKey}"]`);
  612. let elProgress = null;
  613. // 如果是通过按钮点击调用的,获取进度条元素
  614. if (btnElement) {
  615. if (btnElement.getAttribute('data-undergoing') === '1') {
  616. return false;
  617. }
  618. btnElement.setAttribute('data-undergoing', '1');
  619. elProgress = btnElement.querySelector('span.x-progress');
  620. }
  621. // 显示安装进度
  622. let pt = 1;
  623. await Awesome.install(toolKey);
  624. // 只有当进度条元素存在时才更新文本内容
  625. if (elProgress) {
  626. elProgress.textContent = `(${pt}%)`;
  627. let ptInterval = setInterval(() => {
  628. elProgress.textContent = `(${pt}%)`;
  629. pt += Math.floor(Math.random() * 20);
  630. if(pt > 100) {
  631. clearInterval(ptInterval);
  632. elProgress.textContent = ``;
  633. // 在进度条完成后显示安装成功的通知
  634. this.showInPageNotification({
  635. message: `${this.originalTools[toolKey].name} 安装成功!`,
  636. type: 'success',
  637. duration: 3000
  638. });
  639. }
  640. }, 100);
  641. } else {
  642. // 如果没有进度条元素,直接显示通知
  643. this.showInPageNotification({
  644. message: `${this.originalTools[toolKey].name} 安装成功!`,
  645. type: 'success',
  646. duration: 3000
  647. });
  648. }
  649. // 更新原始数据和当前活动数据
  650. this.originalTools[toolKey].installed = true;
  651. if (this.activeTools[toolKey]) {
  652. this.activeTools[toolKey].installed = true;
  653. }
  654. // 更新已安装工具数量
  655. this.updateInstalledCount();
  656. // 如果按钮存在,更新其状态
  657. if (btnElement) {
  658. btnElement.setAttribute('data-undergoing', '0');
  659. }
  660. // 发送消息通知后台更新
  661. chrome.runtime.sendMessage({
  662. type: MSG_TYPE.DYNAMIC_TOOL_INSTALL_OR_OFFLOAD,
  663. toolName: toolKey,
  664. action: 'install',
  665. showTips: true
  666. });
  667. } catch (error) {
  668. console.error('安装工具失败:', error);
  669. // 显示安装失败的通知
  670. this.showInPageNotification({
  671. message: `安装失败:${error.message || '未知错误'}`,
  672. type: 'error',
  673. duration: 5000
  674. });
  675. }
  676. },
  677. // 卸载工具
  678. async uninstallTool(toolKey) {
  679. try {
  680. // 使用自定义确认对话框而非浏览器原生的confirm
  681. this.showConfirm({
  682. title: '卸载确认',
  683. message: `确定要卸载"${this.originalTools[toolKey].name}"工具吗?`,
  684. callback: async (key) => {
  685. try {
  686. await chrome.runtime.sendMessage({
  687. type: MSG_TYPE.DYNAMIC_TOOL_INSTALL_OR_OFFLOAD,
  688. toolName: key,
  689. action: 'offload',
  690. showTips: true
  691. });
  692. // 调用Awesome.offLoad卸载工具
  693. await Awesome.offLoad(key);
  694. // 更新原始数据和当前活动数据
  695. this.originalTools[key].installed = false;
  696. this.originalTools[key].inContextMenu = false;
  697. if (this.activeTools[key]) {
  698. this.activeTools[key].installed = false;
  699. this.activeTools[key].inContextMenu = false;
  700. }
  701. // 更新已安装工具数量
  702. this.updateInstalledCount();
  703. // 显示卸载成功的通知
  704. this.showInPageNotification({
  705. message: `${this.originalTools[key].name} 已成功卸载!`,
  706. type: 'success',
  707. duration: 3000
  708. });
  709. } catch (error) {
  710. console.error('卸载工具失败:', error);
  711. // 显示卸载失败的通知
  712. this.showInPageNotification({
  713. message: `卸载失败:${error.message || '未知错误'}`,
  714. type: 'error',
  715. duration: 5000
  716. });
  717. }
  718. },
  719. data: toolKey
  720. });
  721. } catch (error) {
  722. console.error('准备卸载过程中出错:', error);
  723. }
  724. },
  725. // 切换右键菜单
  726. async toggleContextMenu(toolKey) {
  727. try {
  728. const tool = this.originalTools[toolKey];
  729. const newState = !tool.inContextMenu;
  730. // 更新菜单状态
  731. await Awesome.menuMgr(toolKey, newState ? 'install' : 'offload');
  732. // 更新原始数据和当前活动数据
  733. tool.inContextMenu = newState;
  734. if (this.activeTools[toolKey]) {
  735. this.activeTools[toolKey].inContextMenu = newState;
  736. }
  737. // 发送消息通知后台更新右键菜单
  738. chrome.runtime.sendMessage({
  739. type: MSG_TYPE.DYNAMIC_TOOL_INSTALL_OR_OFFLOAD,
  740. action: `menu-${newState ? 'install' : 'offload'}`,
  741. showTips: false,
  742. menuOnly: true
  743. });
  744. } catch (error) {
  745. console.error('切换右键菜单失败:', error);
  746. }
  747. },
  748. // 切换收藏状态
  749. async toggleFavorite(toolKey) {
  750. try {
  751. if (this.favorites.has(toolKey)) {
  752. this.favorites.delete(toolKey);
  753. // 更新原始数据和当前活动数据
  754. this.originalTools[toolKey].favorite = false;
  755. if (this.activeTools[toolKey]) {
  756. this.activeTools[toolKey].favorite = false;
  757. }
  758. } else {
  759. this.favorites.add(toolKey);
  760. // 更新原始数据和当前活动数据
  761. this.originalTools[toolKey].favorite = true;
  762. if (this.activeTools[toolKey]) {
  763. this.activeTools[toolKey].favorite = true;
  764. }
  765. }
  766. await this.saveFavorites();
  767. // 如果是在收藏视图,需要更新视图
  768. if (this.currentView === 'favorites') {
  769. this.updateActiveTools('favorites');
  770. }
  771. } catch (error) {
  772. console.error('切换收藏状态失败:', error);
  773. }
  774. },
  775. async updateActiveTools(view) {
  776. if (this.loading || Object.keys(this.originalTools).length === 0) {
  777. return;
  778. }
  779. switch (view) {
  780. case 'installed':
  781. // 使用Awesome.getInstalledTools实时获取已安装工具
  782. try {
  783. const installedTools = await Awesome.getInstalledTools();
  784. // 合并installedTools与originalTools的数据
  785. this.activeTools = Object.fromEntries(
  786. Object.entries(this.originalTools).filter(([key]) =>
  787. installedTools.hasOwnProperty(key)
  788. )
  789. );
  790. } catch (error) {
  791. console.error('获取已安装工具失败:', error);
  792. // 回退到本地数据
  793. this.activeTools = Object.fromEntries(
  794. Object.entries(this.originalTools).filter(([_, tool]) =>
  795. tool.installed || tool.systemInstalled || false
  796. )
  797. );
  798. }
  799. break;
  800. case 'favorites':
  801. this.activeTools = Object.fromEntries(
  802. Object.entries(this.originalTools).filter(([key]) => this.favorites.has(key))
  803. );
  804. break;
  805. case 'recent':
  806. // 切换recent时,recentUsed已在showRecentUsed中实时拉取
  807. this.activeTools = Object.fromEntries(
  808. Object.entries(this.originalTools).filter(([key]) => this.recentUsed.includes(key))
  809. );
  810. break;
  811. case 'all':
  812. default:
  813. this.activeTools = { ...this.originalTools };
  814. // 分类过滤在computed属性中处理
  815. break;
  816. }
  817. },
  818. // 新增更新已安装工具数量的方法
  819. async updateInstalledCount() {
  820. this.installedCount = await this.getInstalledCount();
  821. },
  822. // 加载用户保存的视图模式
  823. async loadViewMode() {
  824. try {
  825. const result = await new Promise(resolve => {
  826. chrome.storage.local.get('fehelper_view_mode', result => {
  827. resolve(result.fehelper_view_mode);
  828. });
  829. });
  830. if (result) {
  831. this.viewMode = result;
  832. }
  833. } catch (error) {
  834. console.error('加载视图模式失败:', error);
  835. }
  836. },
  837. // 保存用户的视图模式选择
  838. async saveViewMode(mode) {
  839. try {
  840. this.viewMode = mode;
  841. await chrome.storage.local.set({
  842. 'fehelper_view_mode': mode
  843. });
  844. } catch (error) {
  845. console.error('保存视图模式失败:', error);
  846. }
  847. },
  848. // 加载设置项
  849. async loadSettings() {
  850. try {
  851. Settings.getOptions(async (opts) => {
  852. let selectedOpts = [];
  853. Object.keys(opts).forEach(key => {
  854. if(String(opts[key]) === 'true') {
  855. selectedOpts.push(key);
  856. }
  857. });
  858. this.selectedOpts = selectedOpts;
  859. // 加载右键菜单设置
  860. this.menuDownloadCrx = await Awesome.menuMgr('download-crx', 'get') === '1';
  861. this.menuFeHelperSeting = await Awesome.menuMgr('fehelper-setting', 'get') !== '0';
  862. // 获取快捷键
  863. chrome.commands.getAll((commands) => {
  864. for (let command of commands) {
  865. if (command.name === '_execute_action') {
  866. this.defaultKey = command.shortcut || 'Alt+Shift+J';
  867. break;
  868. }
  869. }
  870. });
  871. });
  872. } catch (error) {
  873. console.error('加载设置项失败:', error);
  874. }
  875. },
  876. // 检查浏览器类型
  877. checkBrowserType() {
  878. try {
  879. this.isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
  880. } catch (error) {
  881. console.error('检查浏览器类型失败:', error);
  882. this.isFirefox = false;
  883. }
  884. },
  885. // 显示设置模态框
  886. showSettings() {
  887. this.showSettingsModal = true;
  888. },
  889. // 关闭设置模态框
  890. closeSettings() {
  891. this.showSettingsModal = false;
  892. },
  893. // 显示打赏模态框
  894. openDonateModal() {
  895. this.showDonateModal = true;
  896. },
  897. // 关闭打赏模态框
  898. closeDonateModal() {
  899. this.showDonateModal = false;
  900. },
  901. // 显示确认对话框
  902. showConfirm(options) {
  903. this.confirmDialog = {
  904. show: true,
  905. title: options.title || '操作确认',
  906. message: options.message || '确定要执行此操作吗?',
  907. callback: options.callback || null,
  908. data: options.data || null
  909. };
  910. },
  911. // 确认操作
  912. confirmAction() {
  913. if (this.confirmDialog.callback) {
  914. this.confirmDialog.callback(this.confirmDialog.data);
  915. }
  916. this.confirmDialog.show = false;
  917. },
  918. // 取消确认
  919. cancelConfirm() {
  920. this.confirmDialog.show = false;
  921. },
  922. // 保存设置
  923. async saveSettings() {
  924. try {
  925. // 构建设置对象
  926. let opts = {};
  927. ['OPT_ITEM_CONTEXTMENUS', 'FORBID_OPEN_IN_NEW_TAB', 'CONTENT_SCRIPT_ALLOW_ALL_FRAMES',
  928. 'JSON_PAGE_FORMAT', 'AUTO_DARK_MODE', 'ALWAYS_DARK_MODE'].forEach(key => {
  929. opts[key] = this.selectedOpts.includes(key).toString();
  930. });
  931. // 保存设置 - 直接传递对象,settings.js已增加对对象类型的支持
  932. Settings.setOptions(opts, async () => {
  933. try {
  934. // 处理右键菜单
  935. const crxAction = this.menuDownloadCrx ? 'install' : 'offload';
  936. const settingAction = this.menuFeHelperSeting ? 'install' : 'offload';
  937. await Promise.all([
  938. Awesome.menuMgr('download-crx', crxAction),
  939. Awesome.menuMgr('fehelper-setting', settingAction)
  940. ]);
  941. // 通知后台更新右键菜单
  942. chrome.runtime.sendMessage({
  943. type: MSG_TYPE.DYNAMIC_TOOL_INSTALL_OR_OFFLOAD,
  944. action: 'menu-change',
  945. menuOnly: true
  946. });
  947. // 关闭弹窗
  948. this.closeSettings();
  949. // 显示提示
  950. this.showNotification({
  951. title: 'FeHelper 设置',
  952. message: '设置已保存!'
  953. });
  954. } catch (innerError) {
  955. console.error('保存菜单设置失败:', innerError);
  956. this.showNotification({
  957. title: 'FeHelper 设置错误',
  958. message: '保存菜单设置失败: ' + innerError.message
  959. });
  960. }
  961. });
  962. } catch (error) {
  963. console.error('保存设置失败:', error);
  964. this.showNotification({
  965. title: 'FeHelper 设置错误',
  966. message: '保存设置失败: ' + error.message
  967. });
  968. }
  969. },
  970. // 设置快捷键
  971. setShortcuts() {
  972. chrome.tabs.create({
  973. url: 'chrome://extensions/shortcuts'
  974. });
  975. },
  976. // 体验夜间模式
  977. turnLight(event) {
  978. event.preventDefault();
  979. // 获取body元素
  980. const body = document.body;
  981. // 切换夜间模式
  982. if (body.classList.contains('dark-mode')) {
  983. body.classList.remove('dark-mode');
  984. } else {
  985. body.classList.add('dark-mode');
  986. // 设置倒计时
  987. this.countDown = 10;
  988. // 启动倒计时
  989. const timer = setInterval(() => {
  990. this.countDown--;
  991. if (this.countDown <= 0) {
  992. clearInterval(timer);
  993. body.classList.remove('dark-mode');
  994. }
  995. }, 1000);
  996. }
  997. },
  998. // 检查URL中的donate_from参数并显示打赏弹窗
  999. checkDonateParam() {
  1000. try {
  1001. const urlParams = new URLSearchParams(window.location.search);
  1002. const donateFrom = urlParams.get('donate_from');
  1003. if (donateFrom) {
  1004. console.log('检测到打赏来源参数:', donateFrom);
  1005. // 记录打赏来源
  1006. chrome.storage.local.set({
  1007. 'fehelper_donate_from': donateFrom,
  1008. 'fehelper_donate_time': Date.now()
  1009. });
  1010. // 等待工具数据加载完成
  1011. this.$nextTick(() => {
  1012. // 在所有工具中查找匹配项
  1013. let matchedTool = null;
  1014. // 首先尝试直接匹配工具key
  1015. if (this.originalTools && this.originalTools[donateFrom]) {
  1016. matchedTool = this.originalTools[donateFrom];
  1017. } else if (this.originalTools) {
  1018. // 如果没有直接匹配,尝试在所有工具中查找部分匹配
  1019. for (const [key, tool] of Object.entries(this.originalTools)) {
  1020. if (key.includes(donateFrom) || donateFrom.includes(key) ||
  1021. (tool.name && tool.name.includes(donateFrom)) ||
  1022. (donateFrom && donateFrom.includes(tool.name))) {
  1023. matchedTool = tool;
  1024. break;
  1025. }
  1026. }
  1027. }
  1028. // 更新打赏文案
  1029. if (matchedTool) {
  1030. this.donate.text = `看起来【${matchedTool.name}】工具帮助到了你,感谢你的认可!`;
  1031. } else {
  1032. // 没有匹配到特定工具,使用通用文案
  1033. this.donate.text = `感谢你对FeHelper的认可和支持!`;
  1034. }
  1035. // 显示打赏弹窗
  1036. this.showDonateModal = true;
  1037. });
  1038. }
  1039. } catch (error) {
  1040. console.error('处理打赏参数时出错:', error);
  1041. }
  1042. },
  1043. // 补充 getRecentCount,保证模板调用不报错,且数据源唯一
  1044. async getRecentCount() {
  1045. const recent = await Statistics.getRecentUsedTools(10);
  1046. return recent.length;
  1047. },
  1048. renderDashboard() {
  1049. const dashboardContainerId = 'fh-dashboard-panel';
  1050. let container = document.getElementById(dashboardContainerId);
  1051. // 只在showDashboard且currentView为recent时隐藏工具列表
  1052. const grid = document.querySelector('.tools-grid');
  1053. if (!this.showDashboard || this.currentView !== 'recent') {
  1054. if (container) container.style.display = 'none';
  1055. if (grid) grid.style.display = '';
  1056. return;
  1057. }
  1058. if (grid) grid.style.display = 'none';
  1059. if (!container) {
  1060. container = document.createElement('div');
  1061. container.id = dashboardContainerId;
  1062. container.style = 'padding:32px; background:#fff; border-radius:8px; margin:24px; box-shadow:0 2px 12px #eee; min-width:700px;';
  1063. const main = document.querySelector('.market-main') || document.querySelector('.market-content');
  1064. if (main) main.prepend(container);
  1065. else document.body.appendChild(container);
  1066. }
  1067. container.style.display = 'block';
  1068. const data = this.dashboardData || {};
  1069. // 工具ID转中文名和icon
  1070. const toolName = (key) => (this.originalTools && this.originalTools[key] && this.originalTools[key].name) ? this.originalTools[key].name : key;
  1071. const toolIcon = (key) => {
  1072. if (toolMap[key] && toolMap[key].menuConfig && toolMap[key].menuConfig[0] && toolMap[key].menuConfig[0].icon) {
  1073. return toolMap[key].menuConfig[0].icon;
  1074. }
  1075. return toolName(key).slice(0,1);
  1076. };
  1077. // 插入美观样式
  1078. if (!document.getElementById('fh-dashboard-style')) {
  1079. const style = document.createElement('style');
  1080. style.id = 'fh-dashboard-style';
  1081. style.innerHTML = `
  1082. .fh-dashboard-cards { display: flex; flex-wrap: wrap; gap: 18px; margin-bottom: 24px;}
  1083. .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;}
  1084. .fh-card.main { background: linear-gradient(135deg,#e3fcec 60%,#e3eafc 100%);}
  1085. .fh-card-num { font-size:32px; font-weight:bold; margin-bottom:4px;}
  1086. .fh-calendar { display:inline-block; margin-left:12px; }
  1087. .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;}
  1088. .fh-cal-cell.used { background:#4285f4; color:#fff; font-weight:bold;}
  1089. .fh-dashboard-section { background:#fff; border-radius:12px; box-shadow:0 1px 4px #f0f0f0; padding:18px 24px; margin-bottom:24px;}
  1090. .fh-dashboard-header { margin-bottom:24px; }
  1091. .fh-dashboard-header h2 { font-size:22px; margin:0; }
  1092. .fh-tool-bar { display:inline-block; width:18px; height:18px; border-radius:3px; background:#e3eafc; margin-right:6px; vertical-align:middle; }
  1093. .fh-tool-bar-inner { display:inline-block; height:100%; border-radius:3px; background:#4285f4; }
  1094. .fh-tool-list { margin:0; padding:0; list-style:none; }
  1095. .fh-tool-list li { margin-bottom:10px; }
  1096. .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; }
  1097. .fh-dashboard-sub { color:#888; font-size:13px; margin-bottom:8px; }
  1098. `;
  1099. document.head.appendChild(style);
  1100. }
  1101. // 30天活跃日历
  1102. const today = new Date();
  1103. let calendar = '<div class="fh-calendar">';
  1104. for(let i=29;i>=0;i--){
  1105. const d = new Date(today.getTime()-i*86400000);
  1106. const ds = d.toISOString().slice(0,10);
  1107. const used = data.allDates && data.allDates.includes(ds);
  1108. calendar += `<span class="fh-cal-cell${used?' used':''}" title="${ds}">${d.getDate()}</span>`;
  1109. }
  1110. calendar += '</div>';
  1111. // 主卡片区块
  1112. let html = `
  1113. <div class="fh-dashboard-header">
  1114. <h2>FeHelper 使用统计仪表盘 <span style="font-size:16px;color:#bbb;">(近30天)</span></h2>
  1115. </div>
  1116. <div class="fh-dashboard-cards">
  1117. <div class="fh-card main"><div class="fh-card-num">${data.totalCount||0}</div><div>总使用次数</div></div>
  1118. <div class="fh-card main"><div class="fh-card-num">${data.activeDays||0}</div><div>活跃天数</div></div>
  1119. <div class="fh-card"><div>${data.firstDate||'-'}<br>~<br>${data.lastDate||'-'}</div><div>统计区间</div></div>
  1120. <div class="fh-card"><div class="fh-card-num">${data.maxStreak||0}</div><div>最长连续活跃天数</div></div>
  1121. <div class="fh-card"><div class="fh-card-num">${data.monthCount||0}</div><div>本月使用次数</div></div>
  1122. <div class="fh-card"><div class="fh-card-num">${data.weekCount||0}</div><div>本周使用次数</div></div>
  1123. <div class="fh-card"><div class="fh-card-num">${data.avgPerDay||0}</div><div>平均每日使用</div></div>
  1124. <div class="fh-card"><div>${data.maxDay.date||'-'}<br><b>${data.maxDay.count||0}</b></div><div>最活跃日</div></div>
  1125. <div class="fh-card"><div class="fh-card-num">${data.daysSinceLast||0}</div><div>最近未使用天数</div></div>
  1126. </div>
  1127. <div class="fh-dashboard-section">
  1128. <div class="fh-dashboard-sub">近30天活跃日历:</div>${calendar}
  1129. </div>
  1130. <div class="fh-dashboard-section" style="display:flex;gap:32px;flex-wrap:wrap;">
  1131. <div style="flex:2;min-width:320px;">
  1132. <div class="fh-dashboard-sub"><b>最近10天活跃趋势:</b></div>
  1133. <div style="display:flex;align-items:end;height:80px;margin-top:8px;">
  1134. ${
  1135. (data.dailyTrend||[]).map(d=>{
  1136. const max = Math.max(...(data.dailyTrend||[]).map(x=>x.count),1);
  1137. 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>`;
  1138. }).join('')
  1139. }
  1140. </div>
  1141. <div style="font-size:12px;color:#888;margin-top:4px;">
  1142. ${(data.dailyTrend||[]).map(d=>`<span style='display:inline-block;width:20px;text-align:center;'>${d.date.slice(5)}</span>`).join('')}
  1143. </div>
  1144. </div>
  1145. <div style="flex:3;min-width:320px;">
  1146. <div class="fh-dashboard-sub"><b>使用最多的工具:</b></div>
  1147. <ul class="fh-tool-list">
  1148. ${(data.mostUsed||[]).map(t=>{
  1149. const percent = data.totalCount ? Math.round(t.count/data.totalCount*100) : 0;
  1150. return `<li style='margin-bottom:12px;display:flex;align-items:center;'>
  1151. <span class='fh-tool-icon'>${toolIcon(t.name)}</span>
  1152. <span style='display:inline-block;width:100px;'>${toolName(t.name)}</span>
  1153. <span style='display:inline-block;width:60px;color:#888;'>(x${t.count})</span>
  1154. <span class='fh-tool-bar' style='width:80px;height:10px;margin:0 8px;'>
  1155. <span class='fh-tool-bar-inner' style='width:${percent*0.8}px;'></span>
  1156. </span>
  1157. <span style='color:#888;'>${percent}%</span>
  1158. </li>`;
  1159. }).join('')}
  1160. </ul>
  1161. </div>
  1162. </div>
  1163. <div class="fh-dashboard-section">
  1164. <div class="fh-dashboard-sub"><b>最近10次使用的工具:</b></div>
  1165. <ul style="margin:8px 0 0 0;padding:0;list-style:none;">
  1166. ${(data.recentDetail||[]).map(t=>`<li style='display:inline-block;margin-right:24px;'>${toolName(t.tool)} <span style='color:#888;'>(${t.date})</span></li>`).join('')}
  1167. </ul>
  1168. </div>
  1169. `;
  1170. container.innerHTML = html;
  1171. window.__vue__ = this;
  1172. },
  1173. },
  1174. watch: {
  1175. // 监听currentView变化
  1176. currentView: {
  1177. immediate: true,
  1178. handler(newView) {
  1179. this.updateActiveTools(newView);
  1180. }
  1181. },
  1182. // 监听currentCategory变化
  1183. currentCategory: {
  1184. handler(newCategory) {
  1185. // 保证在视图模式之外的分类切换也能正确显示
  1186. if (this.currentView === 'all') {
  1187. this.activeTools = { ...this.originalTools };
  1188. }
  1189. // 重置搜索条件
  1190. if (this.searchKey) {
  1191. this.searchKey = '';
  1192. }
  1193. }
  1194. },
  1195. showDashboard(val) {
  1196. this.renderDashboard();
  1197. },
  1198. dashboardData(val) {
  1199. this.renderDashboard();
  1200. },
  1201. },
  1202. mounted() {
  1203. this.$nextTick(() => {
  1204. this.renderDashboard();
  1205. });
  1206. },
  1207. });
  1208. // 添加滚动事件监听
  1209. window.addEventListener('scroll', () => {
  1210. const header = document.querySelector('.market-header');
  1211. const sidebar = document.querySelector('.market-sidebar');
  1212. if (window.scrollY > 10) {
  1213. header.classList.add('scrolled');
  1214. sidebar && sidebar.classList.add('scrolled');
  1215. } else {
  1216. header.classList.remove('scrolled');
  1217. sidebar && sidebar.classList.remove('scrolled');
  1218. }
  1219. });