index.js 60 KB

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