# 1、JS浮点数兼容处理

参考 (opens new window)

# 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
}
1
2
3
4
5
6

# 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
}
1
2
3
4
5
6
7
8

# 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方法
    };
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 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);
}
1
2
3
4
5
6
7

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

github地址 (opens new window)

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

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

# 3-2、Send messages:

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

# 3-3、Listen for messages:

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

# 3-4、Cancel listeners for specific namespace:

lsbridge.unsubscribe('my-namespace');
1

# 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;
}));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

# 4、基于Vue纯前端导出Excel

参考 (opens new window)

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

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

# 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') // 添加一个别名
    }
  }
1
2
3
4
5
6
7
8

# 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
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/* 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')
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158

# 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)
            }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 5.2 在main.js文件中引入

import './log'
1

# 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)
            }
        })
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 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))
        }
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

中国大陆个人身份证号验证 github参考地址 (opens new window)

# 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>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

# 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
lastUpdate: 5/5/2025, 8:38:10 AM