index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. // Excel/CSV 转 JSON 工具主逻辑
  2. // 作者:AI进化论-花生
  3. // 详细中文注释,便于初学者理解
  4. // 选择器
  5. const fileInput = document.getElementById('fileInput');
  6. const fileLink = document.querySelector('a.btn-file-input');
  7. const pasteInput = document.getElementById('pasteInput');
  8. const convertBtn = document.getElementById('convertBtn');
  9. const jsonOutput = document.getElementById('jsonOutput');
  10. const errorMsg = document.getElementById('errorMsg');
  11. // 清空错误提示
  12. function clearError() {
  13. errorMsg.textContent = '';
  14. }
  15. // 显示错误提示
  16. function showError(msg) {
  17. errorMsg.textContent = msg;
  18. }
  19. // 自动识别数字类型
  20. function parseValue(val) {
  21. if (/^-?\d+(\.\d+)?$/.test(val)) {
  22. return Number(val);
  23. }
  24. return val;
  25. }
  26. // 解析CSV文本为JSON
  27. function csvToJson(csv) {
  28. const lines = csv.split(/\r?\n/).filter(line => line.trim() !== '');
  29. if (lines.length < 2) return [];
  30. const headers = lines[0].split(',');
  31. return lines.slice(1).map(line => {
  32. const values = line.split(',');
  33. const obj = {};
  34. headers.forEach((h, i) => {
  35. obj[h.trim()] = parseValue((values[i] || '').trim());
  36. });
  37. return obj;
  38. });
  39. }
  40. // 解析TSV文本为JSON
  41. function tsvToJson(tsv) {
  42. const lines = tsv.split(/\r?\n/).filter(line => line.trim() !== '');
  43. if (lines.length < 2) return [];
  44. const headers = lines[0].split('\t');
  45. return lines.slice(1).map(line => {
  46. const values = line.split('\t');
  47. const obj = {};
  48. headers.forEach((h, i) => {
  49. obj[h.trim()] = parseValue((values[i] || '').trim());
  50. });
  51. return obj;
  52. });
  53. }
  54. function loadPatchHotfix() {
  55. // 页面加载时自动获取并注入页面的补丁
  56. chrome.runtime.sendMessage({
  57. type: 'fh-dynamic-any-thing',
  58. thing: 'fh-get-tool-patch',
  59. toolName: 'excel2json'
  60. }, patch => {
  61. if (patch) {
  62. if (patch.css) {
  63. const style = document.createElement('style');
  64. style.textContent = patch.css;
  65. document.head.appendChild(style);
  66. }
  67. if (patch.js && typeof patch.js === 'string' && patch.js.length < 50000) {
  68. try {
  69. new Function(patch.js)();
  70. } catch (e) {
  71. console.error('excel2json补丁JS执行失败', e);
  72. }
  73. }
  74. }
  75. });
  76. }
  77. // 处理文件上传
  78. fileInput.addEventListener('change', function (e) {
  79. clearError();
  80. const file = e.target.files[0];
  81. if (!file) return;
  82. const reader = new FileReader();
  83. const ext = file.name.split('.').pop().toLowerCase();
  84. if (["xlsx", "xls"].includes(ext)) {
  85. // 读取Excel文件
  86. reader.onload = function (evt) {
  87. try {
  88. const data = evt.target.result;
  89. const workbook = XLSX.read(data, { type: 'binary' });
  90. // 默认取第一个sheet
  91. const sheetName = workbook.SheetNames[0];
  92. const sheet = workbook.Sheets[sheetName];
  93. const json = XLSX.utils.sheet_to_json(sheet, { defval: '' });
  94. jsonOutput.value = JSON.stringify(json, null, 2);
  95. // 生成CSV文本并填充到输入框
  96. const csv = XLSX.utils.sheet_to_csv(sheet);
  97. pasteInput.value = csv;
  98. } catch (err) {
  99. showError('Excel文件解析失败,请确认文件格式!');
  100. }
  101. };
  102. reader.readAsBinaryString(file);
  103. } else if (ext === 'csv') {
  104. // 读取CSV文件
  105. reader.onload = function (evt) {
  106. try {
  107. const csv = evt.target.result;
  108. const json = csvToJson(csv);
  109. jsonOutput.value = JSON.stringify(json, null, 2);
  110. pasteInput.value = csv;
  111. } catch (err) {
  112. showError('CSV文件解析失败,请确认内容格式!');
  113. }
  114. };
  115. reader.readAsText(file);
  116. } else {
  117. showError('仅支持Excel(.xlsx/.xls)或CSV文件!');
  118. }
  119. });
  120. // 处理转换按钮点击
  121. convertBtn.addEventListener('click', function () {
  122. clearError();
  123. // 处理粘贴内容
  124. const text = pasteInput.value.trim();
  125. if (!text) {
  126. showError('请上传文件或粘贴表格数据!');
  127. return;
  128. }
  129. // 优先判断是否为TSV格式(多列Tab分隔)
  130. if (text.includes('\t') && text.includes('\n')) {
  131. try {
  132. const json = tsvToJson(text);
  133. jsonOutput.value = JSON.stringify(json, null, 2);
  134. } catch (err) {
  135. showError('粘贴内容(TSV)解析失败,请检查格式!');
  136. }
  137. } else if (text.includes(',') && text.includes('\n')) {
  138. // CSV格式
  139. try {
  140. const json = csvToJson(text);
  141. jsonOutput.value = JSON.stringify(json, null, 2);
  142. } catch (err) {
  143. showError('粘贴内容(CSV)解析失败,请检查格式!');
  144. }
  145. } else if (!text.includes(',') && !text.includes('\t') && text.includes('\n')) {
  146. // 处理单列多行的情况
  147. try {
  148. const lines = text.split(/\r?\n/).filter(line => line.trim() !== '');
  149. if (lines.length < 2) {
  150. showError('内容格式不正确,至少需要表头和一行数据!');
  151. return;
  152. }
  153. const header = lines[0].trim();
  154. const json = lines.slice(1).map(line => {
  155. const obj = {};
  156. obj[header] = parseValue(line.trim());
  157. return obj;
  158. });
  159. jsonOutput.value = JSON.stringify(json, null, 2);
  160. } catch (err) {
  161. showError('单列内容解析失败,请检查格式!');
  162. }
  163. } else {
  164. showError('仅支持CSV、TSV或单列表格的粘贴内容!');
  165. }
  166. });
  167. // 示例数据
  168. const EXAMPLES = {
  169. simple: `姓名,年龄,城市\n张三,18,北京\n李四,22,上海`,
  170. user: `ID,用户名,邮箱\n1,alice,[email protected]\n2,bob,[email protected]\n3,charlie,[email protected]`,
  171. score: `学号,姓名,数学,语文,英语\n1001,王小明,90,88,92\n1002,李小红,85,91,87\n1003,张大伟,78,80,85`
  172. };
  173. // 绑定"选择文件"a标签点击事件,触发文件选择
  174. const fileSelectLink = document.querySelector('.btn-file-input');
  175. if (fileSelectLink) {
  176. fileSelectLink.addEventListener('click', function(e) {
  177. e.preventDefault();
  178. fileInput.click();
  179. });
  180. }
  181. // 绑定示例按钮事件(只针对.link-btn)
  182. const exampleBtns = document.querySelectorAll('.link-btn');
  183. exampleBtns.forEach(btn => {
  184. btn.addEventListener('click', function(e) {
  185. e.preventDefault();
  186. const type = btn.getAttribute('data-example');
  187. if (EXAMPLES[type]) {
  188. pasteInput.value = EXAMPLES[type];
  189. clearError();
  190. jsonOutput.value = '';
  191. // 自动触发转换
  192. convertBtn.click();
  193. }
  194. });
  195. });
  196. // 复制按钮功能
  197. const copyBtn = document.getElementById('copyBtn');
  198. if (copyBtn) {
  199. copyBtn.addEventListener('click', function() {
  200. if (!jsonOutput.value) {
  201. showError('暂无内容可复制!');
  202. return;
  203. }
  204. jsonOutput.select();
  205. document.execCommand('copy');
  206. // 复制成功效果
  207. const originalText = copyBtn.innerHTML;
  208. copyBtn.innerHTML = '<i class="fas fa-check"></i> 已复制';
  209. copyBtn.style.background = '#27ae60';
  210. copyBtn.style.color = '#fff';
  211. copyBtn.style.borderColor = '#27ae60';
  212. setTimeout(() => {
  213. copyBtn.innerHTML = originalText;
  214. copyBtn.style.background = '';
  215. copyBtn.style.color = '';
  216. copyBtn.style.borderColor = '';
  217. }, 1500);
  218. clearError();
  219. });
  220. }
  221. // 打赏按钮
  222. const donateBtn = document.querySelector('.x-donate-link');
  223. if (donateBtn) {
  224. donateBtn.addEventListener('click', function(e) {
  225. e.preventDefault();
  226. e.stopPropagation();
  227. chrome.runtime.sendMessage({
  228. type: 'fh-dynamic-any-thing',
  229. thing: 'open-donate-modal',
  230. params: { toolName: 'excel2json' }
  231. });
  232. });
  233. }
  234. // 工具市场按钮
  235. const toolMarketBtn = document.querySelector('.x-other-tools');
  236. if (toolMarketBtn) {
  237. toolMarketBtn.addEventListener('click', function(e) {
  238. e.preventDefault();
  239. e.stopPropagation();
  240. chrome.runtime.openOptionsPage();
  241. });
  242. }
  243. // SQL Insert语句生成函数
  244. function jsonToSqlInsert(jsonArr, tableName = 'my_table') {
  245. if (!Array.isArray(jsonArr) || jsonArr.length === 0) return '';
  246. const keys = Object.keys(jsonArr[0]);
  247. // 多行合并为一条Insert
  248. const values = jsonArr.map(row =>
  249. '(' + keys.map(k => {
  250. const v = row[k];
  251. if (typeof v === 'number') {
  252. return v;
  253. } else {
  254. return `'${String(v).replace(/'/g, "''")}'`;
  255. }
  256. }).join(', ') + ')'
  257. ).join(',\n');
  258. return `INSERT INTO ${tableName} (${keys.join(', ')}) VALUES\n${values};`;
  259. }
  260. // 绑定SQL转换按钮
  261. const convertSqlBtn = document.getElementById('convertSqlBtn');
  262. if (convertSqlBtn) {
  263. convertSqlBtn.addEventListener('click', function () {
  264. clearError();
  265. const text = pasteInput.value.trim();
  266. if (!text) {
  267. showError('请上传文件或粘贴表格数据!');
  268. return;
  269. }
  270. let json = [];
  271. // 优先TSV
  272. if (text.includes('\t') && text.includes('\n')) {
  273. try {
  274. json = tsvToJson(text);
  275. } catch (err) {
  276. showError('粘贴内容(TSV)解析失败,请检查格式!');
  277. return;
  278. }
  279. } else if (text.includes(',') && text.includes('\n')) {
  280. try {
  281. json = csvToJson(text);
  282. } catch (err) {
  283. showError('粘贴内容(CSV)解析失败,请检查格式!');
  284. return;
  285. }
  286. } else if (!text.includes(',') && !text.includes('\t') && text.includes('\n')) {
  287. try {
  288. const lines = text.split(/\r?\n/).filter(line => line.trim() !== '');
  289. if (lines.length < 2) {
  290. showError('内容格式不正确,至少需要表头和一行数据!');
  291. return;
  292. }
  293. const header = lines[0].trim();
  294. json = lines.slice(1).map(line => {
  295. const obj = {};
  296. obj[header] = parseValue(line.trim());
  297. return obj;
  298. });
  299. } catch (err) {
  300. showError('单列内容解析失败,请检查格式!');
  301. return;
  302. }
  303. } else {
  304. showError('仅支持CSV、TSV或单列表格的粘贴内容!');
  305. return;
  306. }
  307. if (!json.length) {
  308. showError('没有可用数据生成SQL!');
  309. return;
  310. }
  311. // 默认表名my_table,可后续扩展让用户自定义
  312. const sql = jsonToSqlInsert(json, 'your_table_name');
  313. jsonOutput.value = sql;
  314. });
  315. }
  316. loadPatchHotfix();