index.js 17 KB

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