首页首页
前端
非前端
辅助
Github
前端
非前端
辅助
Github
  • 前端基础

    • HTML基础
    • CSS基础
    • JS基础
    • ES6基础
    • HTTP基础
    • 前端缓存
    • 页面性能
    • 数据结构基础
    • 我的文章
  • 前端进阶

    • Vue2基础
    • Vue2进阶
    • Vue3基础
    • Vue3进阶
    • React基础
    • React进阶
    • React18新特性
    • Vue和React对比
    • RN基础
    • RN环境搭建和打包发布
    • 打包工具
    • TS基础
    • Nuxt基础
    • 小程序基础
    • 微前端基础
    • uni-app基础
    • 业务相关
    • 低代码相关
  • 前端代码练习

    • CSS代码练习
    • JS代码练习
    • 算法代码练习
  • 前端代码技巧

    • 工具库
    • 工具函数
    • CSS动画库
    • CSS代码技巧
    • JS代码技巧
    • 项目技巧

1、JS浮点数兼容处理

参考

example:加法

function plus(num1, num2) {
  const num1Digits = (num1.toString().split('.')[1] || '').length
  const num2Digits = (num2.toString().split('.')[1] || '').length
  const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits))
   return (times(num1, baseNum) + times(num2, baseNum)) / baseNum
}

example:乘法

function times(num1, num2) {
  const num1String = num1.toString()
  const num2String = num2.toString()
  const num1Digits = (num1String.split('.')[1] || '').length
  const num2Digits = (num2String.split('.')[1] || '').length
  const baseNum =  Math.pow(10, num1Digits + num2Digits)
  return Number(num1String.replace('.', '')) * Number(num2String.replace('.', '')) / baseNum
}

2、基于Vue读取txt文件

2-1、beforeCreate 生命周期定义方法

beforeCreate () {
    /**
     * 读取文件(自定义函数)
     * @param pms {@link Object}:参数:
     *  @param pms.encode {@link String}:编码格式:
     * @return {@link String}:文本内容;
     */
    FileReader.prototype.reading = function ({ encode } = pms) {
      let bytes = new Uint8Array(this.result);    //无符号整型数组
      let text = new TextDecoder(encode || 'UTF-8').decode(bytes);
      return text;
    };
    /* 重写readAsBinaryString函数 */
    FileReader.prototype.readAsBinaryString = function (f) {
      if (!this.onload)       //如果this未重写onload函数,则创建一个公共处理方式
        this.onload = e => {  //在this.onload函数中,完成公共处理
          let rs = this.reading();
          console.log(rs);
        };
      this.readAsArrayBuffer(f);  //内部会回调this.onload方法
    };
  }

2、read函数

 read (fileRaw) {
    let rd = new FileReader();
    rd.onload = e => {  //this.readAsArrayBuffer函数内,会回调this.onload函数。在这里处理结果
      let content = rd.reading({ encode: 'UTF-8' })
    };
    rd.readAsBinaryString(f);
}

3、从一个标签页发送消息到另一个标签页

github地址

3-1、Add lsbridge.min.js to your page:

<script src="js/lsbridge.min.js"></script>

3-2、Send messages:

lsbridge.send('my-namespace', { message: 'Hello world!' });

3-3、Listen for messages:

lsbridge.subscribe('my-namespace', function(data) {
  console.log(data); // prints: { message: 'Hello world!'}
});

3-4、Cancel listeners for specific namespace:

lsbridge.unsubscribe('my-namespace');

lsbridge.min.js

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory();
    } else {
        root.lsbridge = factory();
    }
}(this, function () {

    /*
      - Storing messages in localStorage.
      - Clients subscribe to the changes and
        they get notified if a new message arrives.
    */

    var api = {};

    api.isLSAvailable = (function () {
        var mod = '_';
        try {
            localStorage.setItem(mod, mod);
            localStorage.removeItem(mod);
            return true;
        } catch (e) {
            return false;
        }
    })();

    if (api.isLSAvailable) {

        var interval = 100
            , intervalForRemoval = 200
            , ls = localStorage
            , listeners = {}
            , isLoopStarted = false
            , buffer = {};

        var loop = function () {
            for (var namespace in listeners) {
                var data = ls.getItem(namespace);
                if (data && buffer[namespace] && buffer[namespace].indexOf(data) === -1) {
                    buffer[namespace].push(data);
                    try {
                        var parsed = JSON.parse(data);
                        if (parsed) data = parsed;
                    } catch (e) { }
                    for (var i = 0; i < listeners[namespace].length; i++) {
                        listeners[namespace][i](data);
                    }
                    if (!ls.getItem(namespace + '-removeit')) {
                        ls.setItem(namespace + '-removeit', '1');
                        (function (n) {
                            setTimeout(function () {
                                ls.removeItem(n);
                                ls.removeItem(n + '-removeit');
                                buffer[namespace] = [];
                            }, intervalForRemoval);
                        })(namespace);
                    }
                } else if (!data) {
                    buffer[namespace] = [];
                }
            }
            setTimeout(loop, interval);
            return true;
        };

        api.send = function (namespace, data) {
            var raw = '';
            if (typeof data === 'function') { data = data(); }
            if (typeof data === 'object') {
                raw = JSON.stringify(data);
            } else {
                raw = data;
            }
            ls.setItem(namespace, raw);
        };


        api.subscribe = function (namespace, cb) {
            if (!listeners[namespace]) {
                listeners[namespace] = [];
                buffer[namespace] = [];
            }
            listeners[namespace].push(cb);
            if (!isLoopStarted) {
                isLoopStarted = loop();
            }
        };

        api.unsubscribe = function (namespace) {
            if (listeners[namespace]) {
                listeners[namespace] = [];
            }
            if (buffer[namespace]) {
                buffer[namespace] = [];
            }
        };

        api.getBuffer = function () {
            return buffer;
        }

    } else {
        api.send = api.subscribe = function () {
            throw new Error('localStorage not supported.');
        }
    }

    return api;
}));

4、基于Vue纯前端导出Excel

参考

4-1、安装依赖(三个)

npm install file-saver xlsx -S
npm install script-loader -D  // 加载script 需要

4-2、src目录下引入所需文件

/src 目录下新建 vendor文件夹,用于存放 Blob.js 和 Export2Excel.js 文件  后附这两个文件 注意:如果不叫 vendor 名字,则需要修改 Export2Excel.js 中的代码。

4-2-1 配置webpack,如果用的是cli-2搭建的项目

在 /build/webpack.base.config.js 的resolve 模块中添加一个别名

resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      'vendor': path.resolve(__dirname, 'src/vendor') // 添加一个别名
    }
  }

4-2-2 配置webpack,如果用的是cli-3搭建的项目

在 vue.config.js 的resolve 模块中添加一个别名

const path = require('path')

function resolve(dir) {
  return path.join(__dirname, dir)
}

module.exports = {
  outputDir: 'fxop',
  runtimeCompiler: true, // 包含编译器
  publicPath: process.env.BASE_URL || './',
  productionSourceMap: false,
  transpileDependencies: ['element-ui-verify/dist', 'vue-echarts', 'resize-detector'],
  chainWebpack: config => {
      
    // 设置一些别名
    config.resolve.alias.set('vendor', path.resolve(__dirname, 'src/vendor')).set('@', resolve('src'))
  },
  devServer: {
    port: 8081,
    proxy: {
      '/': {
        target: 'http://test.portal.ads.toponegames.mobi' /* 'http://172.16.2.174' 'http://192.168.64.1:8082' */,
        ws: false,
        changeOrigin: true
      }
    }
  }
}

4-3、在vue中使用

<template>
    <button @click="exportExcel">导出表格</button>
</template>

<script>
export default {
    name: 'export',
    data() {
        return {
            loading: false
        };
    },
    methods: {
        exportExcel() {
            let sourceOriginAmount = [
                {
                    goodsName: '苹果',
                    sourceCode: '123'
                },
                {
                    goodsName: '香蕉',
                    sourceCode: '234'
                }
            ]; // 需要导出的数据,可以动态获取
            this.loading = true; // 设置一个loading,生成Excel需要时间
            import('@/vendor/Export2Excel.js').then(excel => { // 导入js模块
                const tHeader = ['编号', '商品名称', '溯源码']; // 导出excel 的标题
                const filterVal = ['index', 'goodsName', 'sourceCode']; // 每个标题对应的字段

                const list = (sourceOriginAmount || []).map((item, key) => { // 通过 map 方法遍历,组装数据成上面的格式
                    return {
                        index: key + 1,
                        goodsName: item.goodsName,
                        sourceCode: item.key
                    };
                });

                if (list) {
                    const data = this.formatJson(filterVal, list); // 生成json数据
                    excel.export_json_to_excel({ // 调用excel方法生成表格
                        header: tHeader,
                        data,
                        filename: this.goodsName
                    });
                } else {
                    alert('暂无无数据');
                }
                this.loading = false;
            })
        },
        formatJson (filterVal, jsonData) {
            return jsonData.map(v => filterVal.map(j => v[j]));
        }
    }
};
</script>

4-4 文件附录:

/* eslint-disable */
/* Blob.js
 * A Blob implementation.
 * 2014-05-27
 *
 * By Eli Grey, http://eligrey.com
 * By Devin Samarin, https://github.com/eboyjr
 * License: X11/MIT
 *   See LICENSE.md
 */

/*global self, unescape */
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
 plusplus: true */

/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */

;(function(view) {
  'use strict'

  view.URL = view.URL || view.webkitURL

  if (view.Blob && view.URL) {
    try {
      new Blob()
      return
    } catch (e) {}
  }

  // Internally we use a BlobBuilder implementation to base Blob off of
  // in order to support older browsers that only have BlobBuilder
  var BlobBuilder =
    view.BlobBuilder ||
    view.WebKitBlobBuilder ||
    view.MozBlobBuilder ||
    (function(view) {
      var get_class = function(object) {
          return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]
        },
        FakeBlobBuilder = function BlobBuilder() {
          this.data = []
        },
        FakeBlob = function Blob(data, type, encoding) {
          this.data = data
          this.size = data.length
          this.type = type
          this.encoding = encoding
        },
        FBB_proto = FakeBlobBuilder.prototype,
        FB_proto = FakeBlob.prototype,
        FileReaderSync = view.FileReaderSync,
        FileException = function(type) {
          this.code = this[(this.name = type)]
        },
        file_ex_codes = (
          'NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR ' +
          'NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR'
        ).split(' '),
        file_ex_code = file_ex_codes.length,
        real_URL = view.URL || view.webkitURL || view,
        real_create_object_URL = real_URL.createObjectURL,
        real_revoke_object_URL = real_URL.revokeObjectURL,
        URL = real_URL,
        btoa = view.btoa,
        atob = view.atob,
        ArrayBuffer = view.ArrayBuffer,
        Uint8Array = view.Uint8Array
      FakeBlob.fake = FB_proto.fake = true
      while (file_ex_code--) {
        FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1
      }
      if (!real_URL.createObjectURL) {
        URL = view.URL = {}
      }
      URL.createObjectURL = function(blob) {
        var type = blob.type,
          data_URI_header
        if (type === null) {
          type = 'application/octet-stream'
        }
        if (blob instanceof FakeBlob) {
          data_URI_header = 'data:' + type
          if (blob.encoding === 'base64') {
            return data_URI_header + ';base64,' + blob.data
          } else if (blob.encoding === 'URI') {
            return data_URI_header + ',' + decodeURIComponent(blob.data)
          }
          if (btoa) {
            return data_URI_header + ';base64,' + btoa(blob.data)
          } else {
            return data_URI_header + ',' + encodeURIComponent(blob.data)
          }
        } else if (real_create_object_URL) {
          return real_create_object_URL.call(real_URL, blob)
        }
      }
      URL.revokeObjectURL = function(object_URL) {
        if (object_URL.substring(0, 5) !== 'data:' && real_revoke_object_URL) {
          real_revoke_object_URL.call(real_URL, object_URL)
        }
      }
      FBB_proto.append = function(data /*, endings*/) {
        var bb = this.data
        // decode data to a binary string
        if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
          var str = '',
            buf = new Uint8Array(data),
            i = 0,
            buf_len = buf.length
          for (; i < buf_len; i++) {
            str += String.fromCharCode(buf[i])
          }
          bb.push(str)
        } else if (get_class(data) === 'Blob' || get_class(data) === 'File') {
          if (FileReaderSync) {
            var fr = new FileReaderSync()
            bb.push(fr.readAsBinaryString(data))
          } else {
            // async FileReader won't work as BlobBuilder is sync
            throw new FileException('NOT_READABLE_ERR')
          }
        } else if (data instanceof FakeBlob) {
          if (data.encoding === 'base64' && atob) {
            bb.push(atob(data.data))
          } else if (data.encoding === 'URI') {
            bb.push(decodeURIComponent(data.data))
          } else if (data.encoding === 'raw') {
            bb.push(data.data)
          }
        } else {
          if (typeof data !== 'string') {
            data += '' // convert unsupported types to strings
          }
          // decode UTF-16 to binary string
          bb.push(unescape(encodeURIComponent(data)))
        }
      }
      FBB_proto.getBlob = function(type) {
        if (!arguments.length) {
          type = null
        }
        return new FakeBlob(this.data.join(''), type, 'raw')
      }
      FBB_proto.toString = function() {
        return '[object BlobBuilder]'
      }
      FB_proto.slice = function(start, end, type) {
        var args = arguments.length
        if (args < 3) {
          type = null
        }
        return new FakeBlob(this.data.slice(start, args > 1 ? end : this.data.length), type, this.encoding)
      }
      FB_proto.toString = function() {
        return '[object Blob]'
      }
      FB_proto.close = function() {
        this.size = this.data.length = 0
      }
      return FakeBlobBuilder
    })(view)

  view.Blob = function Blob(blobParts, options) {
    var type = options ? options.type || '' : ''
    var builder = new BlobBuilder()
    if (blobParts) {
      for (var i = 0, len = blobParts.length; i < len; i++) {
        builder.append(blobParts[i])
      }
    }
    return builder.getBlob(type)
  }
})((typeof self !== 'undefined' && self) || (typeof window !== 'undefined' && window) || this.content || this)
/* eslint-disable */
require('script-loader!file-saver')
require('script-loader!@/vendor/Blob')
import XLSX from 'xlsx'

function generateArray(table) {
  var out = []
  var rows = table.querySelectorAll('tr')
  var ranges = []
  for (var R = 0; R < rows.length; ++R) {
    var outRow = []
    var row = rows[R]
    var columns = row.querySelectorAll('td')
    for (var C = 0; C < columns.length; ++C) {
      var cell = columns[C]
      var colspan = cell.getAttribute('colspan')
      var rowspan = cell.getAttribute('rowspan')
      var cellValue = cell.innerText
      if (cellValue !== '' && cellValue == +cellValue) cellValue = +cellValue

      //Skip ranges
      ranges.forEach(function(range) {
        if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
          for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null)
        }
      })

      //Handle Row Span
      if (rowspan || colspan) {
        rowspan = rowspan || 1
        colspan = colspan || 1
        ranges.push({ s: { r: R, c: outRow.length }, e: { r: R + rowspan - 1, c: outRow.length + colspan - 1 } })
      }
      //Handle Value
      outRow.push(cellValue !== '' ? cellValue : null)

      //Handle Colspan
      if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null)
    }
    out.push(outRow)
  }
  return [out, ranges]
}

function datenum(v, date1904) {
  if (date1904) v += 1462
  var epoch = Date.parse(v)
  return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000)
}

function sheet_from_array_of_arrays(data, opts) {
  var ws = {}
  var range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } }
  for (var R = 0; R != data.length; ++R) {
    for (var C = 0; C != data[R].length; ++C) {
      if (range.s.r > R) range.s.r = R
      if (range.s.c > C) range.s.c = C
      if (range.e.r < R) range.e.r = R
      if (range.e.c < C) range.e.c = C
      var cell = { v: data[R][C] }
      if (cell.v == null) continue
      var cell_ref = XLSX.utils.encode_cell({ c: C, r: R })

      if (typeof cell.v === 'number') cell.t = 'n'
      else if (typeof cell.v === 'boolean') cell.t = 'b'
      else if (cell.v instanceof Date) {
        cell.t = 'n'
        cell.z = XLSX.SSF._table[14]
        cell.v = datenum(cell.v)
      } else cell.t = 's'

      ws[cell_ref] = cell
    }
  }
  if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range)
  return ws
}

function Workbook() {
  if (!(this instanceof Workbook)) return new Workbook()
  this.SheetNames = []
  this.Sheets = {}
}

function s2ab(s) {
  var buf = new ArrayBuffer(s.length)
  var view = new Uint8Array(buf)
  for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
  return buf
}

export function export_table_to_excel(id) {
  var theTable = document.getElementById(id)
  var oo = generateArray(theTable)
  var ranges = oo[1]

  /* original data */
  var data = oo[0]
  var ws_name = 'SheetJS'

  var wb = new Workbook(),
    ws = sheet_from_array_of_arrays(data)

  /* add ranges to worksheet */
  // ws['!cols'] = ['apple', 'banan'];
  ws['!merges'] = ranges

  /* add worksheet to workbook */
  wb.SheetNames.push(ws_name)
  wb.Sheets[ws_name] = ws

  var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' })

  saveAs(new Blob([s2ab(wbout)], { type: 'application/octet-stream' }), 'test.xlsx')
}

export function export_json_to_excel({ header, data, filename = 'excel-list', autoWidth = true } = {}) {
  /* original data */
  data = [...data]
  data.unshift(header)
  var ws_name = 'SheetJS'
  var wb = new Workbook(),
    ws = sheet_from_array_of_arrays(data)

  if (autoWidth) {
    /*设置worksheet每列的最大宽度*/
    const colWidth = data.map(row =>
      row.map(val => {
        /*先判断是否为null/undefined*/
        if (val == null) {
          return { wch: 10 }
        } else if (val.toString().charCodeAt(0) > 255) {
        /*再判断是否为中文*/
          return { wch: val.toString().length * 2 }
        } else {
          return { wch: val.toString().length }
        }
      })
    )
    /*以第一行为初始值*/
    let result = colWidth[0]
    for (let i = 1; i < colWidth.length; i++) {
      for (let j = 0; j < colWidth[i].length; j++) {
        if (result[j]['wch'] < colWidth[i][j]['wch']) {
          result[j]['wch'] = colWidth[i][j]['wch']
        }
      }
    }
    ws['!cols'] = result
  }

  /* add worksheet to workbook */
  wb.SheetNames.push(ws_name)
  wb.Sheets[ws_name] = ws

  var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' })
  saveAs(new Blob([s2ab(wbout)], { type: 'application/octet-stream' }), filename + '.xlsx')
}

5、小程序log日志

可根据对应的小程序环境执行不同日志输出

develop

开发环境 可以看到log的行数

trial

体验版环境 可在小程序的开发调试,看到log

release

线上版环境 需要用户反馈日志,从后台拉取

5.1 log文件

let log = wx.getLogManager ? wx.getLogManager({ level: 1 }) : null

export default {
    debug() {
        if (!log) return
        log.debug.apply(log, arguments)
    },
    info() {
        if (!log) return
        log.info.apply(log, arguments)
    },
    warn() {
        if (!log) return
        log.warn.apply(log, arguments)
    }
}

console.log = (function (oriLogFunc) {
    return logCommon(oriLogFunc, 'info')
})(console.log)

console.warn = (function (oriLogFunc) {
    return logCommon(oriLogFunc, 'warn')
})(console.warn)

function logCommon(oriLogFunc, type) {
    const _envVersion = uni.getAccountInfoSync()?.miniProgram?.envVersion
    switch (_envVersion) {
        case 'develop':
            return oriLogFunc
            break
        case 'trial':
        case 'release':
        default:
            return function (...arg) {
                oriLogFunc.call(console, ...arg)
                log[type](...arg)
            }
    }
}

5.2 在main.js文件中引入

import './log'

6、uni-app 小程序 上传文件方法

/**
 * 小程序专用上传文件方法
 * @param {string} filePath 要上传文件资源的路径 (本地路径)
 * @param {Object} formData HTTP 请求中其他额外的 form data
 * @return {Promise}
 */
function uploadFile(filePath, formData) {
    return new Promise((resolve, reject) => {
        wx.uploadFile({
            url: `${domain}${commonPath}/upload-files/upload`,
            // header: {
            //     Authorization: 'Bearer ' + uni.getStorageSync('token')
            // },
            filePath,
            name: 'file',
            formData,
            success(res) {
                resolve(res)
            },
            fail(err) {
                reject(err)
            }
        })
    })
}

7、H5错误捕获上报

// 通过 VueErrorHandler 捕获的错误
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
Vue.config.errorHandler = (err, vm, info) => {
    // console.log('err, vm, info: ', err, vm, info);
    const msg = `发生在 ${info}; 具体信息 ${err};组件data:${JSON.stringify(
        vm.$data
    )}`
    uploadLog('VueErrorHandler', msg)
}

// Promise catch错误上报,需要在使用promise的地方显示调用.catch(),否则不会捕获错误
if (typeof Promise !== 'undefined') {
    const _promiseCatch = Promise.prototype.catch
    Promise.prototype.catch = function(foo) {
        return _promiseCatch.call(this, catCatch(foo))
    }
}

function catCatch(foo) {
    return function(args) {
        const msg = args.stack ? args.stack : args
        foo && foo.call(this, args)
        uploadLog('PromiseCatch', msg)
    }
}

// 监听页面的 JS Error 并上报
window.addEventListener('error', function(ex) {
    // 一般事件的参数中会包含pos信息。
    // logger && logger.error(ex.error, ex);
    const msg = `发生在 ${ex.filename}; 具体信息 ${ex.message};`
    uploadLog('JsError', msg)
})

// 通过 unhandledrejection 捕获的错误
// 监听promise未处理的reject错误, 跨域情况下监控不到
window.addEventListener('unhandledrejection', function(e) {
    // console.log('e: unhandledrejection', e);
    uploadLog('unhandledrejection', e.reason)
})

/**
 * 上报数据
 * @param {String} source 错误数据源头
 * @param {String} msg 错误信息
 */
export const uploadLog = (source, msg) => {
    // 如果msg是Object对象,需要转成字符串
    if (Object.prototype.toString.call(msg) === '[object Object]') {
        msg = JSON.stringify(msg)
    }
    const url = location.href // 页面URL
    const ua = navigator.userAgent // userAgent

    const content = `H5日志 *** | userID: ${userID} | 错误源:${source} |\r\n --- | 错误信息:${msg} |\r\n --- | 访问URL:${url} |\r\n --- | UserAgent:${ua} |`
    jsBridgeClient.printLog(content)
}

8、微信小程序反编译

使用步骤,资源在网盘

1、获取微信小程序加密包

电脑微信使用小程序,使用 everything 工具,输入.wxapkg 后缀名,然后点击修改时间倒叙排序(要提前删除小程序,重新进入登录,才能准确找到小程序加密包)

进入文件夹,打开 UnpackMiniApp.exe 文件,选择需要破解的微信小程序包(注意:需要在和 UnpackMiniApp.exe 同级目录下,新建立)

2、解析获取混淆源码

破解之后会有一个新的 wxapkg 文件(在 wxpack 目录里面),放入 wxappUnpacker 目录,执行命令 npm run pkg wx1b17fa6e767ff55f.wxapkg, 转换为混淆过后的源码

9、预解析SVGA文件

/**
 * SVGA文件预解析,并存在DB中
 */

import { Downloader, Parser } from 'svga.lite'
import DB from 'svga.lite/db'

/**
 * 解析SVGA文件
 * @param {Array[String]} fileList svga文件路径
 */
function parseFile(fileList) {
    let data = void 0
    let db = void 0

    try {
        db = new DB()
    } catch (error) {
        console.error(error)
    }

    const downloader = new Downloader()
    const parser = new Parser()

    fileList.forEach(async item => {
        let curData = void 0
        if (db) {
            curData = await db.find(item)
        }

        if (!curData) {
            const fileData = await downloader.get(item)

            data = await parser.do(fileData)
            // 插入数据
            db && (await db.insert(item, data))
        }
    })
}

10、常用正则库

/**
 * 校验手机号码格式
 * @param {String} phone 手机号码
 */
export const isPhone = (phone) => {
  return /^1(3|4|5|6|7|8|9)\d{9}$/.test(phone);
};

/**
 * 校验邮政编码格式
 * @param {String} postalCode 邮政编码
 */
export const isPostalCode = (postalCode) => {
  return /^[1-9][0-9]{5}$/.test(postalCode);
};

/**
 * 校验邮件地址格式
 * @param {String} email 邮件地址
 */
export const isEmail = (email) => {
  return /\w@\w*\.\w/.test(email);
};

/**
 * 检测字符串是否包含中文
 * @param {String} str 字符串
 */
export const isIncludeChinese = (str) => {
  return /.*[\u4e00-\u9fa5]+.*$/.test(str);
};

/**
 * 校验身份证格式
 * @param {String} str 身份证号码
 */
export const isIdCard = (str) => {
    return /(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/.test(str)
}

/**
 * 检测银行卡号格式
 * @param {String} str 银行卡号
 */
export const luhmCheck = (bankno) => {
  var lastNum = bankno.substr(bankno.length - 1, 1); //取出最后一位(与luhm进行比较)
  var first15Num = bankno.substr(0, bankno.length - 1); //前15或18位
  var newArr = new Array();
  for (var i = first15Num.length - 1; i > -1; i--) { //前15或18位倒序存进数组
      newArr.push(first15Num.substr(i, 1));
  }
  var arrJiShu = new Array(); //奇数位*2的积 <9
  var arrJiShu2 = new Array(); //奇数位*2的积 >9
  var arrOuShu = new Array(); //偶数位数组
  for (var j = 0; j < newArr.length; j++) {
      if ((j + 1) % 2 == 1) { //奇数位
          if (parseInt(newArr[j]) * 2 < 9) arrJiShu.push(parseInt(newArr[j]) * 2);
          else arrJiShu2.push(parseInt(newArr[j]) * 2);
      } else //偶数位
      arrOuShu.push(newArr[j]);
  }
  
  var jishu_child1 = new Array(); //奇数位*2 >9 的分割之后的数组个位数
  var jishu_child2 = new Array(); //奇数位*2 >9 的分割之后的数组十位数
  for (var h = 0; h < arrJiShu2.length; h++) {
      jishu_child1.push(parseInt(arrJiShu2[h]) % 10);
      jishu_child2.push(parseInt(arrJiShu2[h]) / 10);
  }
  
  var sumJiShu = 0; //奇数位*2 < 9 的数组之和
  var sumOuShu = 0; //偶数位数组之和
  var sumJiShuChild1 = 0; //奇数位*2 >9 的分割之后的数组个位数之和
  var sumJiShuChild2 = 0; //奇数位*2 >9 的分割之后的数组十位数之和
  var sumTotal = 0;
  for (var m = 0; m < arrJiShu.length; m++) {
      sumJiShu = sumJiShu + parseInt(arrJiShu[m]);
  }
  
  for (var n = 0; n < arrOuShu.length; n++) {
      sumOuShu = sumOuShu + parseInt(arrOuShu[n]);
  }
  
  for (var p = 0; p < jishu_child1.length; p++) {
      sumJiShuChild1 = sumJiShuChild1 + parseInt(jishu_child1[p]);
      sumJiShuChild2 = sumJiShuChild2 + parseInt(jishu_child2[p]);
  }
  //计算总和
  sumTotal = parseInt(sumJiShu) + parseInt(sumOuShu) + parseInt(sumJiShuChild1) + parseInt(sumJiShuChild2);
  
  //计算Luhm值
  var k = parseInt(sumTotal) % 10 == 0 ? 10 : parseInt(sumTotal) % 10;
  var luhm = 10 - k;
  var my = false;
  if (lastNum == luhm) { //Luhm验证通过
      my = true;
  } else { //银行卡号必须符合Luhm校验
      my = false;
  }
  return my;
}

中国大陆个人身份证号验证 github参考地址

11、解析mpkg文件

基于Vu3实现,借助 jszip 库

<script setup>
import { ref } from 'vue'
import JSZip from 'jszip'

const version = ref('')
const packagePath = ref('')
const fileName = ref('')
const isLoading = ref(false)
const error = ref('')

const handleFileUpload = async (event) => {
  const file = event.target.files[0]
  if (!file) return
  
  fileName.value = file.name
  isLoading.value = true
  error.value = ''
  version.value = ''
  packagePath.value = ''
  
  try {
    // 使用JSZip解析文件
    const zip = new JSZip()
    const zipContent = await zip.loadAsync(file)

    console.log('zipContent =>', zipContent)
    
    // 查找package.json和project.json文件
    let packageJsonFile = null
    let projectJsonFile = null
    
    // 递归查找文件
    const findFile = (obj, currentPath, targetFileName) => {
      for (const [relativePath, entry] of Object.entries(obj)) {
        const fullPath = currentPath ? `${currentPath}/${relativePath}` : relativePath
        
        if (!entry.dir) {
          if (relativePath === targetFileName || fullPath.endsWith(`/${targetFileName}`)) {
            return entry
          }
        } else if (Object.keys(entry).length > 0) {
          const found = findFile(entry, fullPath, targetFileName)
          if (found) return found
        }
      }
      return null
    }
    
    // 遍历zip文件内容查找package.json
    for (const [relativePath, entry] of Object.entries(zipContent.files)) {
      if (!entry.dir) {
        if (relativePath === 'package.json' || relativePath.endsWith('/package.json')) {
          packageJsonFile = entry
        } else if (relativePath === 'project.json' || relativePath.endsWith('/project.json')) {
          projectJsonFile = entry
        }
        
        // 如果两个文件都找到了,就可以停止遍历
        if (packageJsonFile && projectJsonFile) break;
      }
    }
    
    // 如果没有直接找到,尝试递归查找
    if (!packageJsonFile) {
      packageJsonFile = findFile(zipContent.files, '', 'package.json')
    }
    
    if (!projectJsonFile) {
      projectJsonFile = findFile(zipContent.files, '', 'project.json')
    }
    
    // 解析package.json获取version
    if (packageJsonFile) {
      const packageJsonContent = await packageJsonFile.async('text')
      const packageJson = JSON.parse(packageJsonContent)
      
      if (packageJson.version) {
        version.value = packageJson.version
      } else {
        console.warn('package.json中未找到version字段')
      }
    } else {
      console.warn('未找到package.json文件')
    }
    
    // 解析project.json获取package_path
    if (projectJsonFile) {
      const projectJsonContent = await projectJsonFile.async('text')
      const projectJson = JSON.parse(projectJsonContent)
      
      if (projectJson.package_path) {
        packagePath.value = projectJson.package_path
      } else {
        console.warn('project.json中未找到package_path字段')
      }
    } else {
      console.warn('未找到project.json文件')
    }
    
    // 如果两个文件都没找到,抛出错误
    if (!packageJsonFile && !projectJsonFile) {
      throw new Error('未找到package.json和project.json文件')
    }
    
    // 如果两个字段都没找到,抛出错误
    if (!version.value && !packagePath.value) {
      throw new Error('未找到version和package_path字段')
    }
  } catch (err) {
    error.value = `解析失败: ${err.message}`
    console.error(err)
  } finally {
    isLoading.value = false
  }
}
</script>

<template>
  <div class="container">
    <div class="upload-container">
      <h2>上传MPKG文件</h2>
      
      <div class="file-input">
        <input 
          type="file" 
          accept=".mpkg" 
          @change="handleFileUpload" 
          id="file-upload"
          :disabled="isLoading"
        />
        <label for="file-upload" class="file-label">
          {{ fileName || '选择文件' }}
        </label>
      </div>
      
      <div v-if="isLoading" class="loading">
        正在解析文件...
      </div>
      
      <div v-if="error" class="error">
        {{ error }}
      </div>
      
      <div v-if="version || packagePath" class="result">
        <h3>解析结果</h3>
        <p>文件名: {{ fileName }}</p>
        <p v-if="version">版本号: <strong>{{ version }}</strong></p>
        <p v-if="packagePath">包路径: <strong>{{ packagePath }}</strong></p>
      </div>
    </div>
  </div>
</template>

<style scoped>
.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

.upload-container {
  margin-top: 2rem;
  padding: 2rem 8rem;
  border-radius: 8px;
  background-color: #f9f9f9;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.file-input {
  margin: 1.5rem 0;
}

input[type="file"] {
  display: none;
}

.file-label {
  display: inline-block;
  padding: 10px 20px;
  background-color: #42b883;
  color: white;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s;
}

.file-label:hover {
  background-color: #369a6e;
}

.loading {
  margin: 1rem 0;
  color: #666;
}

.error {
  margin: 1rem 0;
  color: #e74c3c;
}

.result {
  margin-top: 1.5rem;
  padding: 1rem;
  background-color: #e8f5e9;
  border-radius: 4px;
}

.result h3 {
  margin-top: 0;
  color: #2c3e50;
}

.result p {
  margin: 0.5rem 0;
}
</style>

12、在线代码编辑+实时预览

实现原理

  1. 代码编辑器 页面上有一个代码编辑器(如 CodeMirror、Monaco Editor、CodeJar、Ace 等),用户可以在里面输入或修改 React 代码。

  2. 代码转译 用户写的 React 代码通常是 JSX 语法,浏览器不能直接运行。需要用 Babel(或 esbuild、SWC 等)在浏览器端把 JSX 转成普通 JavaScript。

  3. 沙盒/预览 把转译后的代码注入到一个 iframe 或 shadow DOM 里,作为“沙盒”环境运行,防止污染主页面。iframe 的内容会被动态更新,实时反映用户的代码修改。

<!-- 使用 codejar 等编辑器 + iframe -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<textarea id="editor" style="width: 400px; height: 100px">
function App() {
  return <h1>Hello, world!</h1>
}
</textarea>
<iframe id="preview" style="width: 400px; height: 200px"></iframe>

<script>
    const editor = document.getElementById("editor");
    const preview = document.getElementById("preview");

    function updatePreview() {
        const code = editor.value;
        // 转译 JSX
        const compiled = Babel.transform(code, { presets: ["react"] }).code;
        // 生成 iframe 内容
        const html = `
    <div id="root"></div>
    <script src="https://unpkg.com/react@18/umd/react.development.js"><\/script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"><\/script>
    <script>
      ${compiled}
      ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(App));
    <\/script>
  `;
        preview.srcdoc = html;
    }

    editor.addEventListener("input", updatePreview);
    updatePreview(); // 初始化
</script>
最后更新: 2025/5/5 08:38
Next
工具函数