index.js 57 KB

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