index.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. /**
  2. * 假数据生成器主逻辑
  3. * 使用Vue.js构建交互式界面
  4. */
  5. new Vue({
  6. el: '#pageContainer',
  7. data: {
  8. // 当前激活的标签页
  9. activeTab: 'personal',
  10. // 选中的字段
  11. selectedFields: {
  12. // 个人信息
  13. name: false,
  14. email: false,
  15. phone: false,
  16. idCard: false,
  17. gender: false,
  18. age: false,
  19. birthday: false,
  20. address: false,
  21. // 商业数据
  22. company: false,
  23. department: false,
  24. position: false,
  25. salary: false,
  26. bankCard: false,
  27. creditCard: false,
  28. price: false,
  29. currency: false,
  30. // 技术数据
  31. uuid: false,
  32. ip: false,
  33. mac: false,
  34. userAgent: false,
  35. url: false,
  36. domain: false,
  37. password: false,
  38. token: false,
  39. color: false,
  40. timestamp: false,
  41. filename: false,
  42. mimeType: false
  43. },
  44. // 自定义字段
  45. customField: {
  46. name: '',
  47. type: 'string',
  48. rule: ''
  49. },
  50. customFields: [],
  51. // 生成配置
  52. generateCount: 10,
  53. outputFormat: 'json',
  54. // 生成的数据
  55. generatedData: '',
  56. dataSize: '0 B',
  57. // 数据生成器实例
  58. generator: null,
  59. // 预设模板
  60. templates: {
  61. user: {
  62. name: '用户信息模板',
  63. fields: ['name', 'email', 'phone', 'gender', 'age', 'address']
  64. },
  65. employee: {
  66. name: '员工信息模板',
  67. fields: ['name', 'email', 'phone', 'company', 'department', 'position', 'salary']
  68. },
  69. product: {
  70. name: '商品信息模板',
  71. fields: ['name', 'price', 'currency', 'uuid', 'timestamp']
  72. },
  73. order: {
  74. name: '订单信息模板',
  75. fields: ['uuid', 'name', 'email', 'phone', 'address', 'price', 'timestamp']
  76. },
  77. api: {
  78. name: 'API测试数据模板',
  79. fields: ['uuid', 'token', 'ip', 'userAgent', 'timestamp', 'boolean']
  80. }
  81. }
  82. },
  83. mounted() {
  84. // 初始化数据生成器
  85. this.generator = new FakeDataGenerator();
  86. // 检查URL参数,如果有模板参数则自动加载
  87. const urlParams = new URLSearchParams(window.location.search);
  88. const template = urlParams.get('template');
  89. if (template && this.templates[template]) {
  90. this.loadTemplate(template);
  91. }
  92. this.loadPatchHotfix();
  93. },
  94. methods: {
  95. /**
  96. * 添加自定义字段
  97. */
  98. addCustomField() {
  99. if (!this.customField.name.trim()) {
  100. alert('请输入字段名称');
  101. return;
  102. }
  103. // 检查字段名是否已存在
  104. const exists = this.customFields.some(field => field.name === this.customField.name);
  105. if (exists) {
  106. alert('字段名已存在');
  107. return;
  108. }
  109. this.customFields.push({
  110. name: this.customField.name,
  111. type: this.customField.type,
  112. rule: this.customField.rule
  113. });
  114. // 重置输入框
  115. this.customField = {
  116. name: '',
  117. type: 'string',
  118. rule: ''
  119. };
  120. },
  121. /**
  122. * 删除自定义字段
  123. */
  124. removeCustomField(index) {
  125. this.customFields.splice(index, 1);
  126. },
  127. /**
  128. * 全选当前标签页的字段
  129. */
  130. selectAll() {
  131. const fieldGroups = {
  132. personal: ['name', 'email', 'phone', 'idCard', 'gender', 'age', 'birthday', 'address'],
  133. business: ['company', 'department', 'position', 'salary', 'bankCard', 'creditCard', 'price', 'currency'],
  134. technical: ['uuid', 'ip', 'mac', 'userAgent', 'url', 'domain', 'password', 'token', 'color', 'timestamp', 'filename', 'mimeType']
  135. };
  136. const fields = fieldGroups[this.activeTab] || [];
  137. fields.forEach(field => {
  138. this.selectedFields[field] = true;
  139. });
  140. },
  141. /**
  142. * 清空所有选择
  143. */
  144. clearAll() {
  145. Object.keys(this.selectedFields).forEach(key => {
  146. this.selectedFields[key] = false;
  147. });
  148. this.customFields = [];
  149. this.generatedData = '';
  150. this.dataSize = '0 B';
  151. },
  152. /**
  153. * 生成假数据
  154. */
  155. generateData() {
  156. const selectedFieldKeys = Object.keys(this.selectedFields).filter(key => this.selectedFields[key]);
  157. // 检查是否选择了字段
  158. if (selectedFieldKeys.length === 0 && this.customFields.length === 0) {
  159. alert('请至少选择一个字段');
  160. return;
  161. }
  162. // 生成数据
  163. const data = [];
  164. for (let i = 0; i < this.generateCount; i++) {
  165. const item = {};
  166. // 生成预定义字段
  167. selectedFieldKeys.forEach(field => {
  168. item[field] = this.generator.generateByType(field);
  169. });
  170. // 生成自定义字段
  171. this.customFields.forEach(field => {
  172. item[field.name] = this.generateCustomFieldData(field);
  173. });
  174. data.push(item);
  175. }
  176. // 格式化输出
  177. this.formatOutput(data);
  178. },
  179. /**
  180. * 生成自定义字段数据
  181. */
  182. generateCustomFieldData(field) {
  183. switch (field.type) {
  184. case 'string':
  185. return this.generator.randomString(this.generator.randomInt(5, 20));
  186. case 'number':
  187. return this.generator.randomInt(1, 1000);
  188. case 'boolean':
  189. return this.generator.generateBoolean();
  190. case 'date':
  191. return this.generator.generateDate();
  192. case 'array':
  193. const arrayLength = this.generator.randomInt(1, 5);
  194. const array = [];
  195. for (let i = 0; i < arrayLength; i++) {
  196. array.push(this.generator.randomString(5));
  197. }
  198. return array;
  199. default:
  200. return this.generator.randomString(10);
  201. }
  202. },
  203. /**
  204. * 格式化输出数据
  205. */
  206. formatOutput(data) {
  207. switch (this.outputFormat) {
  208. case 'json':
  209. this.generatedData = JSON.stringify(data, null, 2);
  210. break;
  211. case 'csv':
  212. this.generatedData = this.convertToCSV(data);
  213. break;
  214. case 'sql':
  215. this.generatedData = this.convertToSQL(data);
  216. break;
  217. case 'xml':
  218. this.generatedData = this.convertToXML(data);
  219. break;
  220. default:
  221. this.generatedData = JSON.stringify(data, null, 2);
  222. }
  223. // 计算数据大小
  224. this.dataSize = this.calculateSize(this.generatedData);
  225. },
  226. /**
  227. * 转换为CSV格式
  228. */
  229. convertToCSV(data) {
  230. if (data.length === 0) return '';
  231. const headers = Object.keys(data[0]);
  232. const csvContent = [
  233. headers.join(','),
  234. ...data.map(row =>
  235. headers.map(header => {
  236. const value = row[header];
  237. // 处理包含逗号或引号的值
  238. if (typeof value === 'string' && (value.includes(',') || value.includes('"'))) {
  239. return `"${value.replace(/"/g, '""')}"`;
  240. }
  241. return value;
  242. }).join(',')
  243. )
  244. ].join('\n');
  245. return csvContent;
  246. },
  247. /**
  248. * 转换为SQL INSERT语句
  249. */
  250. convertToSQL(data) {
  251. if (data.length === 0) return '';
  252. const tableName = 'fake_data';
  253. const headers = Object.keys(data[0]);
  254. let sql = `-- 表结构\nCREATE TABLE ${tableName} (\n`;
  255. sql += headers.map(header => ` ${header} VARCHAR(255)`).join(',\n');
  256. sql += '\n);\n\n-- 数据插入\n';
  257. data.forEach(row => {
  258. const values = headers.map(header => {
  259. const value = row[header];
  260. if (typeof value === 'string') {
  261. return `'${value.replace(/'/g, "''")}'`;
  262. }
  263. return value;
  264. }).join(', ');
  265. sql += `INSERT INTO ${tableName} (${headers.join(', ')}) VALUES (${values});\n`;
  266. });
  267. return sql;
  268. },
  269. /**
  270. * 转换为XML格式
  271. */
  272. convertToXML(data) {
  273. if (data.length === 0) return '';
  274. let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<data>\n';
  275. data.forEach((item, index) => {
  276. xml += ` <item id="${index + 1}">\n`;
  277. Object.keys(item).forEach(key => {
  278. const value = item[key];
  279. xml += ` <${key}>${this.escapeXML(value)}</${key}>\n`;
  280. });
  281. xml += ' </item>\n';
  282. });
  283. xml += '</data>';
  284. return xml;
  285. },
  286. /**
  287. * XML字符转义
  288. */
  289. escapeXML(value) {
  290. if (typeof value !== 'string') {
  291. value = String(value);
  292. }
  293. return value
  294. .replace(/&/g, '&amp;')
  295. .replace(/</g, '&lt;')
  296. .replace(/>/g, '&gt;')
  297. .replace(/"/g, '&quot;')
  298. .replace(/'/g, '&apos;');
  299. },
  300. /**
  301. * 计算数据大小
  302. */
  303. calculateSize(data) {
  304. const bytes = new Blob([data]).size;
  305. const sizes = ['B', 'KB', 'MB', 'GB'];
  306. if (bytes === 0) return '0 B';
  307. const i = Math.floor(Math.log(bytes) / Math.log(1024));
  308. const size = (bytes / Math.pow(1024, i)).toFixed(2);
  309. return `${size} ${sizes[i]}`;
  310. },
  311. /**
  312. * 复制结果到剪贴板
  313. */
  314. async copyResult() {
  315. if (!this.generatedData) {
  316. alert('没有可复制的数据');
  317. return;
  318. }
  319. try {
  320. await navigator.clipboard.writeText(this.generatedData);
  321. this.showMessage('数据已复制到剪贴板', 'success');
  322. } catch (err) {
  323. console.error('复制失败:', err);
  324. // 备用方法
  325. this.fallbackCopyText(this.generatedData);
  326. }
  327. },
  328. /**
  329. * 备用复制方法
  330. */
  331. fallbackCopyText(text) {
  332. const textArea = document.createElement('textarea');
  333. textArea.value = text;
  334. textArea.style.position = 'fixed';
  335. textArea.style.left = '-999999px';
  336. textArea.style.top = '-999999px';
  337. document.body.appendChild(textArea);
  338. textArea.focus();
  339. textArea.select();
  340. try {
  341. document.execCommand('copy');
  342. this.showMessage('数据已复制到剪贴板', 'success');
  343. } catch (err) {
  344. this.showMessage('复制失败,请手动选择复制', 'error');
  345. }
  346. document.body.removeChild(textArea);
  347. },
  348. /**
  349. * 下载数据文件
  350. */
  351. downloadData() {
  352. if (!this.generatedData) {
  353. alert('没有可下载的数据');
  354. return;
  355. }
  356. const extensions = {
  357. json: 'json',
  358. csv: 'csv',
  359. sql: 'sql',
  360. xml: 'xml'
  361. };
  362. const mimeTypes = {
  363. json: 'application/json',
  364. csv: 'text/csv',
  365. sql: 'application/sql',
  366. xml: 'application/xml'
  367. };
  368. const extension = extensions[this.outputFormat] || 'txt';
  369. const mimeType = mimeTypes[this.outputFormat] || 'text/plain';
  370. const blob = new Blob([this.generatedData], { type: mimeType });
  371. const url = URL.createObjectURL(blob);
  372. const link = document.createElement('a');
  373. link.href = url;
  374. link.download = `fake-data-${Date.now()}.${extension}`;
  375. document.body.appendChild(link);
  376. link.click();
  377. document.body.removeChild(link);
  378. URL.revokeObjectURL(url);
  379. this.showMessage('文件下载已开始', 'success');
  380. },
  381. /**
  382. * 加载预设模板
  383. */
  384. loadTemplate(templateKey) {
  385. const template = this.templates[templateKey];
  386. if (!template) return;
  387. // 清空当前选择
  388. this.clearAll();
  389. // 选择模板字段
  390. template.fields.forEach(field => {
  391. if (this.selectedFields.hasOwnProperty(field)) {
  392. this.selectedFields[field] = true;
  393. }
  394. });
  395. // 切换到相应的标签页
  396. if (template.fields.some(field => ['name', 'email', 'phone', 'idCard', 'gender', 'age', 'birthday', 'address'].includes(field))) {
  397. this.activeTab = 'personal';
  398. } else if (template.fields.some(field => ['company', 'department', 'position', 'salary', 'bankCard', 'creditCard', 'price', 'currency'].includes(field))) {
  399. this.activeTab = 'business';
  400. } else {
  401. this.activeTab = 'technical';
  402. }
  403. // 自动生成数据
  404. this.$nextTick(() => {
  405. this.generateData();
  406. });
  407. this.showMessage(`已加载 ${template.name} 并生成数据`, 'success');
  408. },
  409. // 打开工具市场页面
  410. openOptionsPage: function(event){
  411. event.preventDefault();
  412. event.stopPropagation();
  413. chrome.runtime.openOptionsPage();
  414. },
  415. openDonateModal: function(event){
  416. event.preventDefault();
  417. event.stopPropagation();
  418. chrome.runtime.sendMessage({
  419. type: 'fh-dynamic-any-thing',
  420. thing: 'open-donate-modal',
  421. params: { toolName: 'mock-data' }
  422. });
  423. },
  424. /**
  425. * 显示消息提示
  426. */
  427. showMessage(message, type = 'info') {
  428. // 创建消息元素
  429. const messageEl = document.createElement('div');
  430. messageEl.className = `${type}-message`;
  431. messageEl.textContent = message;
  432. messageEl.style.position = 'fixed';
  433. messageEl.style.top = '20px';
  434. messageEl.style.right = '20px';
  435. messageEl.style.zIndex = '9999';
  436. messageEl.style.padding = '10px 15px';
  437. messageEl.style.borderRadius = '4px';
  438. messageEl.style.fontSize = '14px';
  439. messageEl.style.fontWeight = '500';
  440. messageEl.style.boxShadow = '0 2px 12px rgba(0, 0, 0, 0.15)';
  441. // 设置样式
  442. if (type === 'success') {
  443. messageEl.style.background = '#d4edda';
  444. messageEl.style.color = '#155724';
  445. messageEl.style.border = '1px solid #c3e6cb';
  446. } else if (type === 'error') {
  447. messageEl.style.background = '#f8d7da';
  448. messageEl.style.color = '#721c24';
  449. messageEl.style.border = '1px solid #f5c6cb';
  450. } else {
  451. messageEl.style.background = '#d1ecf1';
  452. messageEl.style.color = '#0c5460';
  453. messageEl.style.border = '1px solid #bee5eb';
  454. }
  455. document.body.appendChild(messageEl);
  456. // 3秒后自动移除
  457. setTimeout(() => {
  458. if (messageEl.parentNode) {
  459. messageEl.parentNode.removeChild(messageEl);
  460. }
  461. }, 3000);
  462. },
  463. loadPatchHotfix() {
  464. // 页面加载时自动获取并注入页面的补丁
  465. chrome.runtime.sendMessage({
  466. type: 'fh-dynamic-any-thing',
  467. thing: 'fh-get-tool-patch',
  468. toolName: 'mock-data'
  469. }, patch => {
  470. if (patch) {
  471. if (patch.css) {
  472. const style = document.createElement('style');
  473. style.textContent = patch.css;
  474. document.head.appendChild(style);
  475. }
  476. if (patch.js) {
  477. try {
  478. if (window.evalCore && window.evalCore.getEvalInstance) {
  479. window.evalCore.getEvalInstance(window)(patch.js);
  480. }
  481. } catch (e) {
  482. console.error('mock-data补丁JS执行失败', e);
  483. }
  484. }
  485. }
  486. });
  487. },
  488. },
  489. watch: {
  490. // 监听生成数量变化,限制范围
  491. generateCount(newVal) {
  492. if (newVal < 1) {
  493. this.generateCount = 1;
  494. } else if (newVal > 1000) {
  495. this.generateCount = 1000;
  496. }
  497. }
  498. }
  499. });