index.js 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152
  1. /**
  2. * FeHelper - 专业时间戳工具
  3. * 使用原生JavaScript实现,无需Vue依赖,兼容Chrome扩展CSP策略
  4. */
  5. // 全局状态管理
  6. var AppState = {
  7. // 当前活跃的标签页
  8. activeTab: 'smart-parser',
  9. // 当前时间显示
  10. currentTime: {
  11. local: '',
  12. timestamp: 0,
  13. timestampMs: 0
  14. },
  15. isTimeRunning: true,
  16. // 智能时间解析器
  17. smartParser: {
  18. input: '',
  19. results: [],
  20. error: '',
  21. detectedFormat: ''
  22. },
  23. // 代码生成器
  24. codeGenerator: {
  25. input: '',
  26. codes: [],
  27. selectedLang: 'all'
  28. },
  29. // 时间计算器
  30. calculator: {
  31. startTime: '',
  32. endTime: '',
  33. difference: null
  34. },
  35. // 批量转换器
  36. batchConverter: {
  37. input: '',
  38. results: []
  39. },
  40. // 时区转换
  41. timezoneExpert: {
  42. inputTime: '',
  43. fromTimezone: 'Asia/Shanghai',
  44. toTimezone: 'America/New_York',
  45. result: null
  46. },
  47. // 数据库工具
  48. dbTools: {
  49. inputTime: '',
  50. dbType: 'mysql',
  51. formats: []
  52. }
  53. };
  54. // 工具类 - 简易时间处理
  55. var TimeUtils = {
  56. // 解析时间输入
  57. parseTimeInput: function(input) {
  58. if (!input || !input.trim()) {
  59. throw new Error('请输入时间值');
  60. }
  61. input = input.trim();
  62. // Unix时间戳(秒) - 10位数字
  63. if (/^\d{10}$/.test(input)) {
  64. return {
  65. timestamp: parseInt(input) * 1000,
  66. format: 'Unix时间戳(秒)'
  67. };
  68. }
  69. // Unix时间戳(毫秒) - 13位数字
  70. if (/^\d{13}$/.test(input)) {
  71. return {
  72. timestamp: parseInt(input),
  73. format: 'Unix时间戳(毫秒)'
  74. };
  75. }
  76. // 特殊关键字
  77. var now = new Date();
  78. if (input.toLowerCase() === 'now') {
  79. return {
  80. timestamp: now.getTime(),
  81. format: '当前时间(now)'
  82. };
  83. }
  84. if (input.toLowerCase() === 'today') {
  85. var today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  86. return {
  87. timestamp: today.getTime(),
  88. format: '今天开始时间(today)'
  89. };
  90. }
  91. if (input.toLowerCase() === 'yesterday') {
  92. var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
  93. return {
  94. timestamp: yesterday.getTime(),
  95. format: '昨天开始时间(yesterday)'
  96. };
  97. }
  98. // 尝试解析为日期字符串
  99. var date = new Date(input);
  100. if (!isNaN(date.getTime())) {
  101. return {
  102. timestamp: date.getTime(),
  103. format: '日期字符串'
  104. };
  105. }
  106. throw new Error('无法识别的时间格式');
  107. },
  108. // 格式化时间戳为各种格式
  109. formatTimestamp: function(timestamp) {
  110. var date = new Date(timestamp);
  111. return [
  112. { label: '标准格式', value: this.formatDate(date, 'YYYY-MM-DD HH:mm:ss') },
  113. { label: 'Unix时间戳(秒)', value: Math.floor(timestamp / 1000).toString() },
  114. { label: 'Unix时间戳(毫秒)', value: timestamp.toString() },
  115. { label: 'UTC时间', value: this.formatDate(new Date(date.getTime() + date.getTimezoneOffset() * 60000), 'YYYY-MM-DD HH:mm:ss') + ' UTC' },
  116. { label: '本地格式', value: date.toLocaleString('zh-CN') },
  117. { label: '相对时间', value: this.getRelativeTime(date) },
  118. { label: 'ISO 8601', value: date.toISOString() }
  119. ];
  120. },
  121. // 格式化日期
  122. formatDate: function(date, format) {
  123. var year = date.getFullYear();
  124. var month = (date.getMonth() + 1).toString().padStart(2, '0');
  125. var day = date.getDate().toString().padStart(2, '0');
  126. var hour = date.getHours().toString().padStart(2, '0');
  127. var minute = date.getMinutes().toString().padStart(2, '0');
  128. var second = date.getSeconds().toString().padStart(2, '0');
  129. return format
  130. .replace('YYYY', year)
  131. .replace('MM', month)
  132. .replace('DD', day)
  133. .replace('HH', hour)
  134. .replace('mm', minute)
  135. .replace('ss', second);
  136. },
  137. // 获取相对时间
  138. getRelativeTime: function(date) {
  139. var now = new Date();
  140. var diff = now.getTime() - date.getTime();
  141. var seconds = Math.floor(diff / 1000);
  142. var minutes = Math.floor(seconds / 60);
  143. var hours = Math.floor(minutes / 60);
  144. var days = Math.floor(hours / 24);
  145. if (days > 0) {
  146. return days + '天前';
  147. } else if (hours > 0) {
  148. return hours + '小时前';
  149. } else if (minutes > 0) {
  150. return minutes + '分钟前';
  151. } else if (seconds > 0) {
  152. return seconds + '秒前';
  153. } else {
  154. return '刚刚';
  155. }
  156. },
  157. // 生成各种语言代码
  158. generateCode: function(input, lang) {
  159. var parsed = this.parseTimeInput(input);
  160. var timestamp = Math.floor(parsed.timestamp / 1000); // 转为秒
  161. var codes = {
  162. javascript: 'var date = new Date(' + parsed.timestamp + ');\nconsole.log(date.toISOString());\n// 输出: ' + new Date(parsed.timestamp).toISOString(),
  163. python: 'import datetime\nfrom datetime import timezone\n\ntimestamp = ' + timestamp + '\ndate = datetime.datetime.fromtimestamp(timestamp, timezone.utc)\nprint(date.isoformat())\n# 输出: ' + new Date(parsed.timestamp).toISOString(),
  164. java: 'import java.time.Instant;\nimport java.time.ZoneId;\nimport java.time.format.DateTimeFormatter;\n\nInstant instant = Instant.ofEpochSecond(' + timestamp + ');\nString formatted = instant.atZone(ZoneId.systemDefault())\n .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);\nSystem.out.println(formatted);',
  165. go: 'package main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc main() {\n timestamp := int64(' + timestamp + ')\n t := time.Unix(timestamp, 0)\n fmt.Println(t.Format(time.RFC3339))\n // 输出: ' + new Date(parsed.timestamp).toISOString() + '\n}',
  166. php: '<?php\n$timestamp = ' + timestamp + ';\n$date = new DateTime("@$timestamp");\necho $date->format("c");\n// 输出: ' + new Date(parsed.timestamp).toISOString() + '\n?>',
  167. sql: '-- MySQL\nSELECT FROM_UNIXTIME(' + timestamp + ') AS formatted_date;\n\n-- PostgreSQL\nSELECT to_timestamp(' + timestamp + ') AS formatted_date;\n\n-- SQLite\nSELECT datetime(' + timestamp + ', "unixepoch") AS formatted_date;'
  168. };
  169. return codes[lang] || '不支持的语言';
  170. }
  171. };
  172. // DOM操作工具
  173. var DOMUtils = {
  174. // 查找元素
  175. $: function(selector) {
  176. return document.querySelector(selector);
  177. },
  178. // 查找所有元素
  179. $$: function(selector) {
  180. return document.querySelectorAll(selector);
  181. },
  182. // 设置元素内容
  183. setText: function(element, text) {
  184. if (element) {
  185. element.textContent = text;
  186. }
  187. },
  188. // 设置元素值
  189. setValue: function(element, value) {
  190. if (element) {
  191. element.value = value;
  192. }
  193. },
  194. // 设置元素HTML
  195. setHTML: function(element, html) {
  196. if (element) {
  197. element.innerHTML = html;
  198. }
  199. },
  200. // 添加类
  201. addClass: function(element, className) {
  202. if (element) {
  203. element.classList.add(className);
  204. }
  205. },
  206. // 移除类
  207. removeClass: function(element, className) {
  208. if (element) {
  209. element.classList.remove(className);
  210. }
  211. },
  212. // 切换类
  213. toggleClass: function(element, className) {
  214. if (element) {
  215. element.classList.toggle(className);
  216. }
  217. },
  218. // 显示元素
  219. show: function(element) {
  220. if (element) {
  221. element.style.display = '';
  222. }
  223. },
  224. // 隐藏元素
  225. hide: function(element) {
  226. if (element) {
  227. element.style.display = 'none';
  228. }
  229. }
  230. };
  231. // 应用主类
  232. var TimestampApp = {
  233. // 初始化
  234. init: function() {
  235. console.log('初始化时间戳工具...');
  236. // 移除Vue相关的HTML属性
  237. this.cleanupVueAttributes();
  238. // 初始化事件监听器
  239. this.initEventListeners();
  240. // 初始化界面
  241. this.initUI();
  242. // 启动时间更新
  243. this.startTimeUpdates();
  244. console.log('时间戳工具初始化完成');
  245. },
  246. // 清理Vue属性
  247. cleanupVueAttributes: function() {
  248. // 移除所有Vue相关的属性 - 直接遍历所有元素
  249. var allElements = document.querySelectorAll('*');
  250. allElements.forEach(function(el) {
  251. // 移除Vue指令属性
  252. var attributes = el.attributes;
  253. for (var i = attributes.length - 1; i >= 0; i--) {
  254. var attr = attributes[i];
  255. if (attr.name.startsWith('v-') || attr.name.startsWith(':') || attr.name.startsWith('@')) {
  256. el.removeAttribute(attr.name);
  257. }
  258. }
  259. });
  260. },
  261. // 初始化事件监听器
  262. initEventListeners: function() {
  263. var self = this;
  264. // 标签页切换
  265. var tabLinks = DOMUtils.$$('.nav-tabs a');
  266. tabLinks.forEach(function(link, index) {
  267. link.addEventListener('click', function(e) {
  268. e.preventDefault();
  269. var tabName = ['smart-parser', 'code-generator', 'time-calculator', 'batch-converter', 'timezone-expert', 'database-tools'][index];
  270. self.setActiveTab(tabName);
  271. });
  272. });
  273. // 时间控制按钮
  274. var timeToggleBtn = DOMUtils.$('.time-toggle-btn');
  275. if (timeToggleBtn) {
  276. timeToggleBtn.addEventListener('click', function(e) {
  277. e.preventDefault();
  278. self.toggleTime();
  279. });
  280. }
  281. // 智能解析输入
  282. var smartInput = DOMUtils.$('.smart-input');
  283. if (smartInput) {
  284. smartInput.addEventListener('input', function() {
  285. AppState.smartParser.input = this.value;
  286. self.parseSmartTime();
  287. });
  288. }
  289. // 代码生成输入
  290. var codeInput = DOMUtils.$('.time-input');
  291. if (codeInput) {
  292. codeInput.addEventListener('input', function() {
  293. AppState.codeGenerator.input = this.value;
  294. self.generateCodes();
  295. });
  296. }
  297. // 代码语言选择
  298. var codeLangSelect = DOMUtils.$('.language-selector select');
  299. if (codeLangSelect) {
  300. codeLangSelect.addEventListener('change', function() {
  301. AppState.codeGenerator.selectedLang = this.value;
  302. self.updateCodeDisplay();
  303. });
  304. }
  305. // 时间显示点击复制
  306. var timeDisplays = DOMUtils.$$('.time-display');
  307. timeDisplays.forEach(function(display) {
  308. display.addEventListener('click', function() {
  309. self.copyToClipboard(this.value);
  310. });
  311. });
  312. // 快捷操作按钮
  313. var quickButtons = DOMUtils.$$('.quick-buttons .btn');
  314. quickButtons.forEach(function(btn, index) {
  315. btn.addEventListener('click', function(e) {
  316. e.preventDefault();
  317. var actions = ['now', 'today', 'yesterday', 'week_start', 'month_start', 'clear'];
  318. self.handleQuickAction(actions[index]);
  319. });
  320. });
  321. // 智能解析器按钮
  322. var parseBtn = DOMUtils.$('.parse-btn');
  323. if (parseBtn) {
  324. parseBtn.addEventListener('click', function(e) {
  325. e.preventDefault();
  326. self.parseSmartTime();
  327. });
  328. }
  329. var clearInputBtn = DOMUtils.$('.clear-input-btn');
  330. if (clearInputBtn) {
  331. clearInputBtn.addEventListener('click', function(e) {
  332. e.preventDefault();
  333. var smartInput = DOMUtils.$('.smart-input');
  334. if (smartInput) {
  335. smartInput.value = '';
  336. AppState.smartParser.input = '';
  337. self.parseSmartTime();
  338. }
  339. });
  340. }
  341. // 新的快捷操作按钮
  342. var quickBtns = DOMUtils.$$('.quick-btn');
  343. quickBtns.forEach(function(btn) {
  344. btn.addEventListener('click', function(e) {
  345. e.preventDefault();
  346. var action = this.getAttribute('data-action');
  347. self.handleQuickAction(action);
  348. });
  349. });
  350. // 代码生成器按钮
  351. var generateBtn = DOMUtils.$('.generate-btn');
  352. if (generateBtn) {
  353. generateBtn.addEventListener('click', function(e) {
  354. e.preventDefault();
  355. self.generateCodes();
  356. });
  357. }
  358. // 时间计算器按钮
  359. var calcBtn = DOMUtils.$('.calc-btn');
  360. if (calcBtn) {
  361. calcBtn.addEventListener('click', function(e) {
  362. e.preventDefault();
  363. self.calculateTimeDiff();
  364. });
  365. }
  366. var calcAddBtn = DOMUtils.$('.calc-add-btn');
  367. if (calcAddBtn) {
  368. calcAddBtn.addEventListener('click', function(e) {
  369. e.preventDefault();
  370. self.calculateTimeAddSubtract();
  371. });
  372. }
  373. // 批量转换器按钮
  374. var batchConvertBtn = DOMUtils.$('.batch-convert-btn');
  375. if (batchConvertBtn) {
  376. batchConvertBtn.addEventListener('click', function(e) {
  377. e.preventDefault();
  378. self.batchConvert();
  379. });
  380. }
  381. var batchExportBtn = DOMUtils.$('.batch-export-btn');
  382. if (batchExportBtn) {
  383. batchExportBtn.addEventListener('click', function(e) {
  384. e.preventDefault();
  385. self.exportBatchResults();
  386. });
  387. }
  388. var batchClearBtn = DOMUtils.$('.batch-clear-btn');
  389. if (batchClearBtn) {
  390. batchClearBtn.addEventListener('click', function(e) {
  391. e.preventDefault();
  392. var batchInput = DOMUtils.$('.batch-input');
  393. if (batchInput) {
  394. batchInput.value = '';
  395. }
  396. });
  397. }
  398. // 时区转换器按钮
  399. var timezoneConvertBtn = DOMUtils.$('.timezone-convert-btn');
  400. if (timezoneConvertBtn) {
  401. timezoneConvertBtn.addEventListener('click', function(e) {
  402. e.preventDefault();
  403. self.convertTimezone();
  404. });
  405. }
  406. // 数据库工具按钮
  407. var dbGenerateBtn = DOMUtils.$('.db-generate-btn');
  408. if (dbGenerateBtn) {
  409. dbGenerateBtn.addEventListener('click', function(e) {
  410. e.preventDefault();
  411. self.generateDatabaseFormats();
  412. });
  413. }
  414. },
  415. // 初始化UI
  416. initUI: function() {
  417. // 设置初始标签页
  418. this.setActiveTab(AppState.activeTab);
  419. // 初始化时间显示
  420. this.updateTimeDisplay();
  421. },
  422. // 设置活跃标签页
  423. setActiveTab: function(tabName) {
  424. AppState.activeTab = tabName;
  425. // 更新标签链接样式
  426. var tabLinks = DOMUtils.$$('.nav-tabs li');
  427. var tabPanes = DOMUtils.$$('.tab-pane');
  428. tabLinks.forEach(function(link, index) {
  429. var tabNames = ['smart-parser', 'code-generator', 'time-calculator', 'batch-converter', 'timezone-expert', 'database-tools'];
  430. if (tabNames[index] === tabName) {
  431. DOMUtils.addClass(link, 'active');
  432. } else {
  433. DOMUtils.removeClass(link, 'active');
  434. }
  435. });
  436. // 更新标签页内容显示
  437. tabPanes.forEach(function(pane) {
  438. if (pane.id === tabName) {
  439. DOMUtils.addClass(pane, 'active');
  440. DOMUtils.show(pane);
  441. } else {
  442. DOMUtils.removeClass(pane, 'active');
  443. DOMUtils.hide(pane);
  444. }
  445. });
  446. },
  447. // 启动时间更新
  448. startTimeUpdates: function() {
  449. var self = this;
  450. function updateTime() {
  451. var now = new Date();
  452. AppState.currentTime.local = TimeUtils.formatDate(now, 'YYYY-MM-DD HH:mm:ss');
  453. AppState.currentTime.timestamp = Math.floor(now.getTime() / 1000);
  454. AppState.currentTime.timestampMs = now.getTime();
  455. self.updateTimeDisplay();
  456. }
  457. updateTime();
  458. setInterval(function() {
  459. if (AppState.isTimeRunning) {
  460. updateTime();
  461. }
  462. }, 1000);
  463. },
  464. // 更新时间显示
  465. updateTimeDisplay: function() {
  466. var timeInputs = DOMUtils.$$('.time-display');
  467. if (timeInputs.length >= 3) {
  468. DOMUtils.setValue(timeInputs[0], AppState.currentTime.local);
  469. DOMUtils.setValue(timeInputs[1], AppState.currentTime.timestamp);
  470. DOMUtils.setValue(timeInputs[2], AppState.currentTime.timestampMs);
  471. }
  472. },
  473. // 切换时间运行状态
  474. toggleTime: function() {
  475. AppState.isTimeRunning = !AppState.isTimeRunning;
  476. var toggleBtn = DOMUtils.$('.time-toggle-btn');
  477. if (toggleBtn) {
  478. DOMUtils.setText(toggleBtn, AppState.isTimeRunning ? '⏸️ 暂停' : '▶️ 开始');
  479. toggleBtn.className = AppState.isTimeRunning ? 'btn btn-sm btn-warning time-toggle-btn' : 'btn btn-sm btn-success time-toggle-btn';
  480. }
  481. },
  482. // 智能解析时间
  483. parseSmartTime: function() {
  484. var resultContainer = DOMUtils.$('.result-container');
  485. var formatHints = DOMUtils.$('.format-hints');
  486. if (!AppState.smartParser.input.trim()) {
  487. DOMUtils.setHTML(resultContainer, '');
  488. DOMUtils.setHTML(formatHints, '');
  489. return;
  490. }
  491. try {
  492. var parsed = TimeUtils.parseTimeInput(AppState.smartParser.input);
  493. var results = TimeUtils.formatTimestamp(parsed.timestamp);
  494. AppState.smartParser.results = results;
  495. AppState.smartParser.detectedFormat = parsed.format;
  496. AppState.smartParser.error = '';
  497. // 显示检测到的格式
  498. DOMUtils.setHTML(formatHints, '<span class="badge badge-info">检测到格式: ' + parsed.format + '</span>');
  499. // 显示解析结果 - 使用网格布局
  500. var html = '<div class="parse-results-grid">';
  501. results.forEach(function(result, index) {
  502. var isIsoResult = result.label === 'ISO 8601';
  503. var resultClass = isIsoResult ? 'result-item iso-result' : 'result-item';
  504. html += '<div class="' + resultClass + '">' +
  505. '<label>' + result.label + '</label>' +
  506. '<div class="result-value" onclick="TimestampApp.copyToClipboard(\'' + result.value.replace(/'/g, "\\'") + '\')" title="点击复制">' +
  507. result.value +
  508. '</div>' +
  509. '</div>';
  510. });
  511. html += '</div>';
  512. DOMUtils.setHTML(resultContainer, html);
  513. } catch (error) {
  514. AppState.smartParser.error = error.message;
  515. AppState.smartParser.results = [];
  516. AppState.smartParser.detectedFormat = '';
  517. DOMUtils.setHTML(resultContainer, '<div class="alert alert-danger">❌ ' + error.message + '</div>');
  518. DOMUtils.setHTML(formatHints, '');
  519. }
  520. },
  521. // 生成代码
  522. generateCodes: function() {
  523. if (!AppState.codeGenerator.input.trim()) {
  524. AppState.codeGenerator.codes = [];
  525. this.updateCodeDisplay();
  526. return;
  527. }
  528. try {
  529. var languages = ['javascript', 'python', 'java', 'go', 'php', 'sql'];
  530. AppState.codeGenerator.codes = languages.map(function(lang) {
  531. return {
  532. lang: lang.charAt(0).toUpperCase() + lang.slice(1),
  533. code: TimeUtils.generateCode(AppState.codeGenerator.input, lang)
  534. };
  535. });
  536. this.updateCodeDisplay();
  537. } catch (error) {
  538. AppState.codeGenerator.codes = [{
  539. lang: 'Error',
  540. code: '代码生成失败: ' + error.message
  541. }];
  542. this.updateCodeDisplay();
  543. }
  544. },
  545. // 更新代码显示
  546. updateCodeDisplay: function() {
  547. var codeResults = DOMUtils.$('.code-results');
  548. if (!codeResults) return;
  549. // 过滤代码
  550. var filteredCodes = AppState.codeGenerator.codes;
  551. if (AppState.codeGenerator.selectedLang !== 'all') {
  552. filteredCodes = AppState.codeGenerator.codes.filter(function(code) {
  553. return code.lang.toLowerCase().includes(AppState.codeGenerator.selectedLang.toLowerCase());
  554. });
  555. }
  556. // 显示代码
  557. var html = '';
  558. filteredCodes.forEach(function(code) {
  559. html += '<div class="code-block">' +
  560. '<div class="code-header">' +
  561. '<span class="code-lang">' + code.lang + '</span>' +
  562. '<button class="btn btn-xs btn-default" onclick="TimestampApp.copyToClipboard(\'' + code.code.replace(/'/g, "\\'").replace(/\n/g, '\\n') + '\')">📋 复制</button>' +
  563. '</div>' +
  564. '<pre class="code-content">' + code.code + '</pre>' +
  565. '</div>';
  566. });
  567. DOMUtils.setHTML(codeResults, html);
  568. },
  569. // 复制到剪贴板
  570. copyToClipboard: function(text) {
  571. try {
  572. if (navigator.clipboard && navigator.clipboard.writeText) {
  573. navigator.clipboard.writeText(text).then(function() {
  574. TimestampApp.showToast('已复制到剪贴板');
  575. }).catch(function(error) {
  576. console.error('复制失败:', error);
  577. TimestampApp.fallbackCopy(text);
  578. });
  579. } else {
  580. TimestampApp.fallbackCopy(text);
  581. }
  582. } catch (error) {
  583. console.error('复制失败:', error);
  584. TimestampApp.fallbackCopy(text);
  585. }
  586. },
  587. // 备用复制方法
  588. fallbackCopy: function(text) {
  589. var textarea = document.createElement('textarea');
  590. textarea.value = text;
  591. textarea.style.position = 'fixed';
  592. textarea.style.opacity = '0';
  593. document.body.appendChild(textarea);
  594. textarea.select();
  595. try {
  596. var successful = document.execCommand('copy');
  597. if (successful) {
  598. this.showToast('已复制到剪贴板');
  599. } else {
  600. this.showToast('复制失败');
  601. }
  602. } catch (error) {
  603. console.error('复制失败:', error);
  604. this.showToast('复制失败');
  605. }
  606. document.body.removeChild(textarea);
  607. },
  608. // 快捷操作处理
  609. handleQuickAction: function(action) {
  610. var smartInput = DOMUtils.$('.smart-input');
  611. if (!smartInput) return;
  612. var value = '';
  613. var now = new Date();
  614. switch(action) {
  615. case 'now':
  616. value = 'now';
  617. break;
  618. case 'today':
  619. value = 'today';
  620. break;
  621. case 'yesterday':
  622. value = 'yesterday';
  623. break;
  624. case 'week_start':
  625. var weekStart = new Date(now);
  626. weekStart.setDate(now.getDate() - now.getDay());
  627. weekStart.setHours(0, 0, 0, 0);
  628. value = TimeUtils.formatDate(weekStart, 'YYYY-MM-DD HH:mm:ss');
  629. break;
  630. case 'month_start':
  631. var monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
  632. value = TimeUtils.formatDate(monthStart, 'YYYY-MM-DD HH:mm:ss');
  633. break;
  634. case 'clear':
  635. value = '';
  636. // 清空所有输入框
  637. var allInputs = DOMUtils.$$('input[type="text"], textarea');
  638. allInputs.forEach(function(input) {
  639. if (!input.readOnly) {
  640. input.value = '';
  641. }
  642. });
  643. this.showToast('已清空所有输入');
  644. return;
  645. }
  646. smartInput.value = value;
  647. AppState.smartParser.input = value;
  648. this.parseSmartTime();
  649. if (value) {
  650. this.showToast('已插入: ' + value);
  651. }
  652. },
  653. // 显示提示信息
  654. showToast: function(message) {
  655. // 移除已存在的toast
  656. var existingToast = DOMUtils.$('.toast-message');
  657. if (existingToast) {
  658. document.body.removeChild(existingToast);
  659. }
  660. var toast = document.createElement('div');
  661. toast.className = 'toast-message';
  662. toast.textContent = message;
  663. toast.style.cssText = 'position:fixed;top:20px;right:20px;background:#333;color:#fff;padding:10px 20px;border-radius:4px;z-index:9999;transition:opacity 0.3s;';
  664. document.body.appendChild(toast);
  665. setTimeout(function() {
  666. toast.style.opacity = '0';
  667. setTimeout(function() {
  668. if (toast.parentNode) {
  669. document.body.removeChild(toast);
  670. }
  671. }, 300);
  672. }, 2000);
  673. },
  674. // 计算时间差
  675. calculateTimeDiff: function() {
  676. var startInput = DOMUtils.$('.start-time');
  677. var endInput = DOMUtils.$('.end-time');
  678. var resultsDiv = DOMUtils.$('.calc-results');
  679. if (!startInput || !endInput || !resultsDiv) return;
  680. var startTime = startInput.value.trim();
  681. var endTime = endInput.value.trim();
  682. if (!startTime || !endTime) {
  683. DOMUtils.setHTML(resultsDiv, '<div class="text-warning">请输入开始时间和结束时间</div>');
  684. return;
  685. }
  686. try {
  687. var start = TimeUtils.parseTimeInput(startTime);
  688. var end = TimeUtils.parseTimeInput(endTime);
  689. var diff = Math.floor((end.timestamp - start.timestamp) / 1000); // 转换为秒
  690. var html = '<div class="result-item">';
  691. html += '<strong>时间差:</strong><br>';
  692. html += '秒数:' + diff + ' 秒<br>';
  693. html += '分钟:' + Math.floor(diff / 60) + ' 分钟<br>';
  694. html += '小时:' + Math.floor(diff / 3600) + ' 小时<br>';
  695. html += '天数:' + Math.floor(diff / 86400) + ' 天<br>';
  696. html += '</div>';
  697. DOMUtils.setHTML(resultsDiv, html);
  698. } catch (error) {
  699. DOMUtils.setHTML(resultsDiv, '<div class="text-danger">错误:' + error.message + '</div>');
  700. }
  701. },
  702. // 计算时间加减
  703. calculateTimeAddSubtract: function() {
  704. var baseTimeInput = DOMUtils.$('.base-time');
  705. var operationSelect = DOMUtils.$('.operation-select');
  706. var amountInput = DOMUtils.$('.amount-input');
  707. var unitSelect = DOMUtils.$('.unit-select');
  708. var resultsDiv = DOMUtils.$('.add-subtract-results');
  709. if (!baseTimeInput || !operationSelect || !amountInput || !unitSelect || !resultsDiv) return;
  710. var baseTime = baseTimeInput.value.trim();
  711. var operation = operationSelect.value;
  712. var amount = parseInt(amountInput.value);
  713. var unit = unitSelect.value;
  714. if (!baseTime || isNaN(amount)) {
  715. DOMUtils.setHTML(resultsDiv, '<div class="text-warning">请输入基准时间和数量</div>');
  716. return;
  717. }
  718. try {
  719. var base = TimeUtils.parseTimeInput(baseTime);
  720. var timestamp = base.timestamp;
  721. var multiplier = {
  722. 'seconds': 1000,
  723. 'minutes': 60 * 1000,
  724. 'hours': 3600 * 1000,
  725. 'days': 86400 * 1000,
  726. 'months': 30 * 86400 * 1000,
  727. 'years': 365 * 86400 * 1000
  728. };
  729. var change = amount * multiplier[unit];
  730. if (operation === 'subtract') {
  731. change = -change;
  732. }
  733. var newTimestamp = timestamp + change;
  734. var results = TimeUtils.formatTimestamp(newTimestamp);
  735. var html = '<div class="result-item">';
  736. html += '<strong>计算结果:</strong><br>';
  737. results.forEach(function(result) {
  738. html += result.label + ':' + result.value + '<br>';
  739. });
  740. html += '</div>';
  741. DOMUtils.setHTML(resultsDiv, html);
  742. } catch (error) {
  743. DOMUtils.setHTML(resultsDiv, '<div class="text-danger">错误:' + error.message + '</div>');
  744. }
  745. },
  746. // 批量转换
  747. batchConvert: function() {
  748. var batchInput = DOMUtils.$('.batch-input');
  749. var resultsDiv = DOMUtils.$('.batch-results');
  750. var statsSpan = DOMUtils.$('.result-stats');
  751. if (!batchInput || !resultsDiv) return;
  752. var lines = batchInput.value.split('\n').filter(function(line) {
  753. return line.trim();
  754. });
  755. if (lines.length === 0) {
  756. DOMUtils.setHTML(resultsDiv, '<div class="text-warning">请输入要转换的时间值</div>');
  757. return;
  758. }
  759. var results = [];
  760. var successCount = 0;
  761. var errorCount = 0;
  762. lines.forEach(function(line, index) {
  763. try {
  764. var parsed = TimeUtils.parseTimeInput(line.trim());
  765. var formatted = TimeUtils.formatTimestamp(parsed.timestamp);
  766. results.push({
  767. line: index + 1,
  768. input: line.trim(),
  769. success: true,
  770. results: formatted
  771. });
  772. successCount++;
  773. } catch (error) {
  774. results.push({
  775. line: index + 1,
  776. input: line.trim(),
  777. success: false,
  778. error: error.message
  779. });
  780. errorCount++;
  781. }
  782. });
  783. // 更新统计信息
  784. if (statsSpan) {
  785. DOMUtils.setHTML(statsSpan, '(成功:' + successCount + ',失败:' + errorCount + ')');
  786. }
  787. // 显示结果
  788. var html = '';
  789. results.forEach(function(result) {
  790. if (result.success) {
  791. html += '<div class="mb-3">';
  792. html += '<strong>第' + result.line + '行:</strong> ' + result.input + '<br>';
  793. result.results.forEach(function(format) {
  794. html += '• ' + format.label + ':' + format.value + '<br>';
  795. });
  796. html += '</div>';
  797. } else {
  798. html += '<div class="mb-3 text-danger">';
  799. html += '<strong>第' + result.line + '行错误:</strong> ' + result.input + '<br>';
  800. html += '错误:' + result.error + '<br>';
  801. html += '</div>';
  802. }
  803. });
  804. DOMUtils.setHTML(resultsDiv, html);
  805. },
  806. // 导出批量结果
  807. exportBatchResults: function() {
  808. this.showToast('导出功能开发中...');
  809. },
  810. // 时区转换
  811. convertTimezone: function() {
  812. var timeInput = DOMUtils.$('.timezone-time-input');
  813. var fromSelect = DOMUtils.$('.from-timezone-select');
  814. var toSelect = DOMUtils.$('.to-timezone-select');
  815. var resultsDiv = DOMUtils.$('.timezone-results');
  816. if (!timeInput || !fromSelect || !toSelect || !resultsDiv) return;
  817. var timeValue = timeInput.value.trim();
  818. var fromTimezone = fromSelect.value;
  819. var toTimezone = toSelect.value;
  820. if (!timeValue) {
  821. DOMUtils.setHTML(resultsDiv, '<div class="text-warning">请输入时间</div>');
  822. return;
  823. }
  824. try {
  825. // 1. 解析为UTC时间戳
  826. var utcTimestamp = getUTCTimestampFromLocal(timeValue, fromTimezone);
  827. // 2. 用Intl.DateTimeFormat格式化为目标时区的本地时间
  828. var dt = new Date(utcTimestamp);
  829. var fmt = new Intl.DateTimeFormat('zh-CN', {
  830. timeZone: toTimezone,
  831. year: 'numeric', month: '2-digit', day: '2-digit',
  832. hour: '2-digit', minute: '2-digit', second: '2-digit',
  833. hour12: false
  834. });
  835. var parts = fmt.formatToParts(dt);
  836. var get = t => parts.find(p => p.type === t).value;
  837. var targetStr = `${get('year')}-${get('month')}-${get('day')} ${get('hour')}:${get('minute')}:${get('second')}`;
  838. var html = '<div class="result-item">';
  839. html += '<strong>时区转换结果:</strong><br>';
  840. html += '原时间:' + timeValue + ' (' + fromTimezone + ')<br>';
  841. html += '目标时区:' + toTimezone + '<br>';
  842. html += '转换结果:' + targetStr + '<br>';
  843. html += '</div>';
  844. DOMUtils.setHTML(resultsDiv, html);
  845. } catch (error) {
  846. DOMUtils.setHTML(resultsDiv, '<div class="text-danger">错误:' + error.message + '</div>');
  847. }
  848. },
  849. // 生成数据库格式
  850. generateDatabaseFormats: function() {
  851. var timeInput = DOMUtils.$('.db-time-input');
  852. var dbSelect = DOMUtils.$('.db-type-select');
  853. var resultsDiv = DOMUtils.$('.db-results');
  854. if (!timeInput || !dbSelect || !resultsDiv) return;
  855. var timeValue = timeInput.value.trim();
  856. var dbType = dbSelect.value;
  857. if (!timeValue) {
  858. DOMUtils.setHTML(resultsDiv, '<div class="text-warning">请输入时间值</div>');
  859. return;
  860. }
  861. try {
  862. var parsed = TimeUtils.parseTimeInput(timeValue);
  863. var date = new Date(parsed.timestamp);
  864. var html = '<div class="result-item">';
  865. html += '<strong>' + dbType.toUpperCase() + ' 格式:</strong><br>';
  866. switch (dbType) {
  867. case 'mysql':
  868. html += 'DATETIME:' + TimeUtils.formatDate(date, 'YYYY-MM-DD HH:mm:ss') + '<br>';
  869. html += 'TIMESTAMP:' + Math.floor(parsed.timestamp / 1000) + '<br>';
  870. html += 'SQL示例:<br>';
  871. html += '<code>SELECT * FROM table WHERE created_at = \'' + TimeUtils.formatDate(date, 'YYYY-MM-DD HH:mm:ss') + '\';</code><br>';
  872. break;
  873. case 'postgresql':
  874. html += 'TIMESTAMP:' + TimeUtils.formatDate(date, 'YYYY-MM-DD HH:mm:ss') + '<br>';
  875. html += 'TIMESTAMPTZ:' + date.toISOString() + '<br>';
  876. html += 'EPOCH:' + Math.floor(parsed.timestamp / 1000) + '<br>';
  877. break;
  878. case 'sqlite':
  879. html += 'TEXT:' + TimeUtils.formatDate(date, 'YYYY-MM-DD HH:mm:ss') + '<br>';
  880. html += 'INTEGER:' + Math.floor(parsed.timestamp / 1000) + '<br>';
  881. break;
  882. case 'mongodb':
  883. html += 'ISODate:ISODate("' + date.toISOString() + '")<br>';
  884. html += 'ObjectId时间戳:' + Math.floor(parsed.timestamp / 1000).toString(16).padStart(8, '0') + '0000000000000000<br>';
  885. break;
  886. }
  887. html += '</div>';
  888. DOMUtils.setHTML(resultsDiv, html);
  889. } catch (error) {
  890. DOMUtils.setHTML(resultsDiv, '<div class="text-danger">错误:' + error.message + '</div>');
  891. }
  892. }
  893. };
  894. function loadPatchHotfix() {
  895. // 页面加载时自动获取并注入页面的补丁
  896. chrome.runtime.sendMessage({
  897. type: 'fh-dynamic-any-thing',
  898. thing: 'fh-get-tool-patch',
  899. toolName: 'datetime-calc'
  900. }, patch => {
  901. if (patch) {
  902. if (patch.css) {
  903. const style = document.createElement('style');
  904. style.textContent = patch.css;
  905. document.head.appendChild(style);
  906. }
  907. if (patch.js) {
  908. try {
  909. if (window.evalCore && window.evalCore.getEvalInstance) {
  910. window.evalCore.getEvalInstance(window)(patch.js);
  911. }
  912. } catch (e) {
  913. console.error('datetime-calc补丁JS执行失败', e);
  914. }
  915. }
  916. }
  917. });
  918. }
  919. // 页面加载完成后初始化
  920. document.addEventListener('DOMContentLoaded', function() {
  921. TimestampApp.init();
  922. // 打赏按钮点击事件
  923. var donateBtn = document.querySelector('.x-donate-link');
  924. if (donateBtn) {
  925. donateBtn.addEventListener('click', function(e) {
  926. e.preventDefault();
  927. e.stopPropagation();
  928. chrome.runtime.sendMessage({
  929. type: 'fh-dynamic-any-thing',
  930. thing: 'open-donate-modal',
  931. params: { toolName: 'datetime-calc' }
  932. });
  933. });
  934. }
  935. // 更多工具按钮点击事件
  936. var moreToolsBtn = document.querySelector('.x-other-tools');
  937. if (moreToolsBtn) {
  938. moreToolsBtn.addEventListener('click', function(e) {
  939. e.preventDefault();
  940. e.stopPropagation();
  941. chrome.runtime.openOptionsPage();
  942. });
  943. }
  944. loadPatchHotfix();
  945. });
  946. // 全局暴露主要对象(用于调试)
  947. window.TimestampApp = TimestampApp;
  948. window.AppState = AppState;
  949. window.TimeUtils = TimeUtils;
  950. // === 新增:更准确的原生JS IANA时区转换辅助函数 ===
  951. function getUTCTimestampFromLocal(timeStr, tz) {
  952. // 只支持 yyyy-MM-dd HH:mm:ss
  953. var m = timeStr.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})$/);
  954. if (!m) throw new Error('请输入格式为 yyyy-MM-dd HH:mm:ss 的时间');
  955. var y = Number(m[1]), mon = Number(m[2]), d = Number(m[3]), h = Number(m[4]), min = Number(m[5]), s = Number(m[6]);
  956. // 构造一个"源时区"下的本地时间的UTC时间戳
  957. // 1. 先用Date.UTC得到UTC时间戳
  958. var utcGuess = Date.UTC(y, mon - 1, d, h, min, s);
  959. // 2. 用Intl.DateTimeFormat格式化utcGuess为源时区的本地时间
  960. var fmt = new Intl.DateTimeFormat('en-US', {
  961. timeZone: tz,
  962. year: 'numeric', month: '2-digit', day: '2-digit',
  963. hour: '2-digit', minute: '2-digit', second: '2-digit',
  964. hour12: false
  965. });
  966. var parts = fmt.formatToParts(new Date(utcGuess));
  967. var get = t => parts.find(p => p.type === t).value;
  968. var localStr = `${get('year')}-${get('month')}-${get('day')} ${get('hour')}:${get('minute')}:${get('second')}`;
  969. // 3. 计算本地时间和输入时间的差值(毫秒)
  970. var input = Date.UTC(y, mon - 1, d, h, min, s);
  971. var local = Date.UTC(
  972. Number(get('year')),
  973. Number(get('month')) - 1,
  974. Number(get('day')),
  975. Number(get('hour')),
  976. Number(get('minute')),
  977. Number(get('second'))
  978. );
  979. var diff = input - local;
  980. // 4. 用utcGuess + diff 得到正确的UTC时间戳
  981. return utcGuess + diff;
  982. }
  983. // 事件委托:解析结果区域点击复制
  984. (function() {
  985. document.addEventListener('DOMContentLoaded', function() {
  986. var parseResultsModule = document.querySelector('.parse-results-module');
  987. if (parseResultsModule) {
  988. parseResultsModule.addEventListener('click', function(e) {
  989. var target = e.target;
  990. if (target.classList.contains('result-value')) {
  991. var text = target.textContent;
  992. TimestampApp.copyToClipboard(text);
  993. }
  994. });
  995. }
  996. });
  997. })();