sh-tools 1.2.2 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/packages/api/index.js +85 -0
- package/packages/api/smSecret/base64.js +168 -0
- package/packages/api/smSecret/hex.js +92 -0
- package/packages/api/smSecret/index.js +13 -0
- package/packages/api/smSecret/sm3.js +276 -0
- package/packages/api/smSecret/util.js +92 -0
- package/packages/index.js +5 -0
- package/packages/utils/boolean.js +10 -0
- package/packages/utils/color.js +395 -0
- package/packages/utils/dom.js +111 -0
- package/packages/utils/index.js +25 -0
- package/packages/utils/number.js +24 -0
- package/packages/utils/object.js +1 -0
- package/packages/utils/other.js +311 -0
- package/packages/utils/pattern.js +43 -0
- package/packages/utils/string.js +227 -0
- package/packages/utils/time.js +1 -0
- package/packages/utils/validate.js +139 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import XEUtils from 'xe-utils'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
toDateDiffText(date) {
|
|
5
|
+
let currDate = 1544407800000 // '2018-12-10 10:10:00'
|
|
6
|
+
let dateDiff = XEUtils.getDateDiff(date, currDate)
|
|
7
|
+
if (dateDiff.done) {
|
|
8
|
+
if (dateDiff.time < 31536000000) {
|
|
9
|
+
if (dateDiff.time < 2592000000) {
|
|
10
|
+
if (dateDiff.time < 86400000) {
|
|
11
|
+
if (dateDiff.time < 360000) {
|
|
12
|
+
if (dateDiff.time < 60000) {
|
|
13
|
+
if (dateDiff.time < 10000) {
|
|
14
|
+
return '刚刚'
|
|
15
|
+
}
|
|
16
|
+
return `${dateDiff.ss}秒之前`
|
|
17
|
+
}
|
|
18
|
+
return `${dateDiff.mm}分钟之前`
|
|
19
|
+
}
|
|
20
|
+
return `${dateDiff.HH}小时之前`
|
|
21
|
+
}
|
|
22
|
+
return `${dateDiff.dd}天之前`
|
|
23
|
+
}
|
|
24
|
+
return `${dateDiff.MM}个月之前`
|
|
25
|
+
}
|
|
26
|
+
return `${dateDiff.yyyy}年之前`
|
|
27
|
+
}
|
|
28
|
+
return '错误类型'
|
|
29
|
+
},
|
|
30
|
+
ioToFile(io, fileName = 'download', type = 'blob') {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
try {
|
|
33
|
+
if ('download' in document.createElement('a')) {
|
|
34
|
+
// 非IE下载
|
|
35
|
+
const elink = document.createElement('a')
|
|
36
|
+
const url = window.URL || window.webkitURL || window.moxURL
|
|
37
|
+
elink.style.display = 'none'
|
|
38
|
+
elink.href = url.createObjectURL(io)
|
|
39
|
+
elink.download = fileName
|
|
40
|
+
elink.click()
|
|
41
|
+
url.revokeObjectURL(elink.href)
|
|
42
|
+
} else {
|
|
43
|
+
// IE10+下载
|
|
44
|
+
navigator.msSaveBlob(io, fileName)
|
|
45
|
+
}
|
|
46
|
+
resolve()
|
|
47
|
+
} catch (e) {
|
|
48
|
+
reject(e)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
},
|
|
52
|
+
// 数字金额转大写
|
|
53
|
+
toMoneyChies(val, option = {}) {
|
|
54
|
+
let { upcase = true } = option
|
|
55
|
+
//汉字的数字
|
|
56
|
+
var cnNums = upcase ? ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'] : ['零', '一', '二', '三', '四', '伍', '六', '七', '八', '九']
|
|
57
|
+
//基本单位
|
|
58
|
+
var cnIntRadice = ['', '拾', '佰', '仟']
|
|
59
|
+
//对应整数部分扩展单位
|
|
60
|
+
var cnIntUnits = ['', '万', '亿', '兆']
|
|
61
|
+
//对应小数部分单位
|
|
62
|
+
var cnDecUnits = ['角', '分', '毫', '厘']
|
|
63
|
+
//整数金额时后面跟的字符
|
|
64
|
+
var cnInteger = '整'
|
|
65
|
+
//整型完以后的单位
|
|
66
|
+
var cnIntLast = '元'
|
|
67
|
+
//最大处理的数字
|
|
68
|
+
var maxNum = 999999999999999.9999
|
|
69
|
+
//金额整数部分
|
|
70
|
+
var integerNum
|
|
71
|
+
//金额小数部分
|
|
72
|
+
var decimalNum
|
|
73
|
+
//输出的中文金额字符串
|
|
74
|
+
var chineseStr = ''
|
|
75
|
+
//分离金额后用的数组,预定义
|
|
76
|
+
var parts
|
|
77
|
+
if (val === '') return val
|
|
78
|
+
val = parseFloat(val)
|
|
79
|
+
if (val >= maxNum) {
|
|
80
|
+
//超出最大处理数字
|
|
81
|
+
return ''
|
|
82
|
+
}
|
|
83
|
+
if (val === 0) {
|
|
84
|
+
chineseStr = cnNums[0] + cnIntLast + cnInteger
|
|
85
|
+
return chineseStr
|
|
86
|
+
}
|
|
87
|
+
//转换为字符串
|
|
88
|
+
val = val.toString()
|
|
89
|
+
if (val.indexOf('.') === -1) {
|
|
90
|
+
integerNum = val
|
|
91
|
+
decimalNum = ''
|
|
92
|
+
} else {
|
|
93
|
+
parts = val.split('.')
|
|
94
|
+
integerNum = parts[0]
|
|
95
|
+
decimalNum = parts[1].substr(0, 4)
|
|
96
|
+
}
|
|
97
|
+
//获取整型部分转换
|
|
98
|
+
if (parseInt(integerNum, 10) > 0) {
|
|
99
|
+
var zeroCount = 0
|
|
100
|
+
var IntLen = integerNum.length
|
|
101
|
+
for (let i = 0; i < IntLen; i++) {
|
|
102
|
+
let n = integerNum.substr(i, 1)
|
|
103
|
+
var p = IntLen - i - 1
|
|
104
|
+
var q = p / 4
|
|
105
|
+
var m = p % 4
|
|
106
|
+
if (n === '0') {
|
|
107
|
+
zeroCount++
|
|
108
|
+
} else {
|
|
109
|
+
if (zeroCount > 0) {
|
|
110
|
+
chineseStr += cnNums[0]
|
|
111
|
+
}
|
|
112
|
+
//归零
|
|
113
|
+
zeroCount = 0
|
|
114
|
+
chineseStr += cnNums[parseInt(n)] + cnIntRadice[m]
|
|
115
|
+
}
|
|
116
|
+
if (m === 0 && zeroCount < 4) {
|
|
117
|
+
chineseStr += cnIntUnits[q]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
chineseStr += cnIntLast
|
|
121
|
+
}
|
|
122
|
+
//小数部分
|
|
123
|
+
if (decimalNum !== '') {
|
|
124
|
+
var decLen = decimalNum.length
|
|
125
|
+
for (var i = 0; i < decLen; i++) {
|
|
126
|
+
var n = decimalNum.substr(i, 1)
|
|
127
|
+
if (n !== '0') {
|
|
128
|
+
chineseStr += cnNums[Number(n)] + cnDecUnits[i]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (chineseStr === '') {
|
|
133
|
+
chineseStr += cnNums[0] + cnIntLast + cnInteger
|
|
134
|
+
} else if (decimalNum === '') {
|
|
135
|
+
chineseStr += cnInteger
|
|
136
|
+
}
|
|
137
|
+
return chineseStr
|
|
138
|
+
},
|
|
139
|
+
// 补全金额位数,返回字符串
|
|
140
|
+
formatMoneyPad(value) {
|
|
141
|
+
//基本单位
|
|
142
|
+
let cnIntRadice = ['亿', '千', '百', '十', '万', '千', '百', '十', '元']
|
|
143
|
+
// 小数单位
|
|
144
|
+
let cnDecUnits = ['角', '分', '毫', '厘']
|
|
145
|
+
let intValue = String(value).split('.')[0] || ''
|
|
146
|
+
let decValue = String(value).split('.')[1] || ''
|
|
147
|
+
let intFixed = intValue.padStart(cnIntRadice.length, ' ')
|
|
148
|
+
let decFixed = decValue.padEnd(cnDecUnits.length, '0')
|
|
149
|
+
return intFixed + '.' + decFixed
|
|
150
|
+
},
|
|
151
|
+
// 封装 系统内对渲染器格式化方法
|
|
152
|
+
formatRender(value, key, rowData, renderName, renderProps, renderContext, editable) {
|
|
153
|
+
// editable: 是否对返回值进行修正 默认 否
|
|
154
|
+
let rvalue, rtext, rname, iscross
|
|
155
|
+
let { $vUtils, $vxePluginNames, rform } = renderContext
|
|
156
|
+
let { min, max, formula, digits, type, multiple, split, options, format, nodeKey, moneyUnit, commafy, openValue, openLabel, closeValue, closeLabel, bill } = renderProps
|
|
157
|
+
let defaultDateFormat = {
|
|
158
|
+
date: 'yyyy-MM-dd',
|
|
159
|
+
time: 'HH:mm:ss',
|
|
160
|
+
datetime: 'yyyy-MM-dd HH:mm:ss',
|
|
161
|
+
month: 'yyyy-MM',
|
|
162
|
+
week: 'yyyy-MM-dd',
|
|
163
|
+
year: 'yyyy'
|
|
164
|
+
}
|
|
165
|
+
if (renderName === '$vRowCell') {
|
|
166
|
+
let field = rowData[key + 'field'] || key
|
|
167
|
+
rname = rowData[field + 'RenderName'] || '$vInput'
|
|
168
|
+
} else {
|
|
169
|
+
rname = renderName
|
|
170
|
+
}
|
|
171
|
+
switch (rname) {
|
|
172
|
+
case '$vInput':
|
|
173
|
+
if (formula) {
|
|
174
|
+
value = $vUtils.calculate(formula, rowData)
|
|
175
|
+
}
|
|
176
|
+
if (editable && ['integer', 'number', 'float'].includes(type) && !$vUtils.isNone(value)) {
|
|
177
|
+
if ($vUtils.isNumber(+max) && +value > +max) {
|
|
178
|
+
value = +max
|
|
179
|
+
iscross = true
|
|
180
|
+
} else if ($vUtils.isNumber(+min) && +value < +min) {
|
|
181
|
+
value = +min
|
|
182
|
+
iscross = true
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
switch (type) {
|
|
186
|
+
case 'integer':
|
|
187
|
+
rvalue = value || value === 0 ? $vUtils.toInteger(value) : ''
|
|
188
|
+
break
|
|
189
|
+
case 'number':
|
|
190
|
+
case 'float':
|
|
191
|
+
rvalue = value || value === 0 ? $vUtils.truncate(value, digits) : ''
|
|
192
|
+
break
|
|
193
|
+
default:
|
|
194
|
+
rvalue = value
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
rvalue = $vUtils.replaceNutrim(rvalue)
|
|
198
|
+
rtext = rvalue
|
|
199
|
+
break
|
|
200
|
+
case '$vTime':
|
|
201
|
+
format = format ? format.replace('YYYY', 'yyyy').replace('DD', 'dd').replace('hh', 'HH') : defaultDateFormat[type]
|
|
202
|
+
if ($vUtils.isNumber(value)) value = String(value)
|
|
203
|
+
rvalue = format ? (value ? $vUtils.toDateString(value, format) : '') : value
|
|
204
|
+
rvalue = $vUtils.replaceNutrim(rvalue)
|
|
205
|
+
if ($vUtils.isDate(value)) $vUtils.set(rowData, key, rvalue)
|
|
206
|
+
rtext = rvalue
|
|
207
|
+
break
|
|
208
|
+
case '$vTextArea':
|
|
209
|
+
case '$vCheckbox':
|
|
210
|
+
case '$vRadio':
|
|
211
|
+
case '$vHref':
|
|
212
|
+
rvalue = $vUtils.replaceNutrim(value)
|
|
213
|
+
rtext = rvalue
|
|
214
|
+
break
|
|
215
|
+
case '$vSelect':
|
|
216
|
+
if (multiple) {
|
|
217
|
+
let oriArray = Array.isArray(value) ? value : value ? value.split(split).filter(_ => _ && _ !== 'undefined') : []
|
|
218
|
+
let rns = oriArray.map(orv => {
|
|
219
|
+
return options.find(opt => String(opt.value) === String(orv)).label || ''
|
|
220
|
+
})
|
|
221
|
+
rtext = rns.join(split)
|
|
222
|
+
rvalue = oriArray
|
|
223
|
+
} else {
|
|
224
|
+
let optionObj = options.find(_ => String(_.value) === String(value))
|
|
225
|
+
rtext = optionObj ? optionObj.label : ''
|
|
226
|
+
rvalue = value
|
|
227
|
+
}
|
|
228
|
+
break
|
|
229
|
+
case '$vTree':
|
|
230
|
+
let prefixKey = key.endsWith('Id') ? String(key).replace('Id', '') : key
|
|
231
|
+
if (multiple) {
|
|
232
|
+
let oriArray = Array.isArray(value) ? value : value ? value.split(split).filter(_ => _ && _ !== 'undefined') : []
|
|
233
|
+
if (options && options.length > 0) {
|
|
234
|
+
let rns = oriArray.map(orv => options.find(opt => String(opt[nodeKey]) === String(orv)).label || '')
|
|
235
|
+
rtext = rns.join(split)
|
|
236
|
+
}
|
|
237
|
+
rvalue = oriArray
|
|
238
|
+
} else {
|
|
239
|
+
if (options && options.length > 0) {
|
|
240
|
+
let optionObj = options.find(opt => String(opt[nodeKey]) === String(value))
|
|
241
|
+
rtext = optionObj ? optionObj.label : ''
|
|
242
|
+
}
|
|
243
|
+
rvalue = value ? [value] : []
|
|
244
|
+
}
|
|
245
|
+
if (format) {
|
|
246
|
+
let dataformat = format.replace(/@/gi, prefixKey)
|
|
247
|
+
rtext = $vUtils.format(dataformat, rowData)
|
|
248
|
+
}
|
|
249
|
+
break
|
|
250
|
+
case '$vProgress':
|
|
251
|
+
rvalue = value || value === 0 ? $vUtils.truncate(value, digits) : ''
|
|
252
|
+
rtext = rvalue
|
|
253
|
+
break
|
|
254
|
+
case '$vSwitch':
|
|
255
|
+
rvalue = $vUtils.replaceNutrim(value)
|
|
256
|
+
if (String(rvalue) === openValue) {
|
|
257
|
+
rtext = openLabel
|
|
258
|
+
} else if (String(rvalue) === closeValue) {
|
|
259
|
+
rtext = closeLabel
|
|
260
|
+
}
|
|
261
|
+
break
|
|
262
|
+
case '$vMoney':
|
|
263
|
+
if (formula) {
|
|
264
|
+
value = $vUtils.calculate(formula, rowData)
|
|
265
|
+
}
|
|
266
|
+
if (editable && !$vUtils.isNone(value)) {
|
|
267
|
+
if ($vUtils.isNumber(+max) && +value > +max) {
|
|
268
|
+
value = +max
|
|
269
|
+
iscross = true
|
|
270
|
+
} else if ($vUtils.isNumber(+min) && +value < +min) {
|
|
271
|
+
value = +min
|
|
272
|
+
iscross = true
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
rvalue = value || value === 0 ? (type !== 'integer' ? $vUtils.truncate(value, digits) : $vUtils.toInteger(value)) : ''
|
|
276
|
+
rvalue = $vUtils.replaceNutrim(rvalue)
|
|
277
|
+
rtext = rvalue !== '' ? $vUtils.truncate($vUtils.divide(rvalue, moneyUnit), digits) : ''
|
|
278
|
+
if (commafy && !bill && bill !== '0') rtext = $vUtils.commafy(rtext, { digits })
|
|
279
|
+
break
|
|
280
|
+
case '$vCheckgroup':
|
|
281
|
+
let oriArray = Array.isArray(value) ? value : value ? value.split(split).filter(_ => _ && _ !== 'undefined') : []
|
|
282
|
+
let rns = oriArray.map(orv => {
|
|
283
|
+
return options.find(opt => String(opt.value) === String(orv)).label || ''
|
|
284
|
+
})
|
|
285
|
+
rtext = rns.join(split)
|
|
286
|
+
rvalue = oriArray
|
|
287
|
+
break
|
|
288
|
+
case '$vRadiogroup':
|
|
289
|
+
let optionObj = options.find(_ => String(_.value) === String(value))
|
|
290
|
+
rtext = optionObj ? optionObj.label : ''
|
|
291
|
+
rvalue = value
|
|
292
|
+
break
|
|
293
|
+
case '$vUpload':
|
|
294
|
+
rvalue = Array.isArray(value) ? value : value.split(split).filter(_ => _ && _ !== 'undefined')
|
|
295
|
+
break
|
|
296
|
+
case '$vCode':
|
|
297
|
+
rvalue = value
|
|
298
|
+
rtext = value
|
|
299
|
+
break
|
|
300
|
+
default:
|
|
301
|
+
break
|
|
302
|
+
}
|
|
303
|
+
if (formula) {
|
|
304
|
+
$vUtils.set(rowData, key, rvalue)
|
|
305
|
+
}
|
|
306
|
+
// if (!rform && $vxePluginNames.expendRenders && $vxePluginNames.expendRenders.includes(renderName)) {
|
|
307
|
+
// $vUtils.set(rowData, `${key}${$vxePluginNames.expendName}`, rtext)
|
|
308
|
+
// }
|
|
309
|
+
return { rvalue, rtext }
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
numberReg() {
|
|
3
|
+
return {
|
|
4
|
+
number: /^[0-9]*$/, // 数字
|
|
5
|
+
numberX: /^\d{n}$/, // n位的数字
|
|
6
|
+
numberMinx: /^\d{n,}$/, // 至少n位的数字
|
|
7
|
+
numberMn: /^\d{m,n}$/, // m-n位的数字
|
|
8
|
+
numberFloat: /^(-)?\d+(\.\d{1,2})$/, // 带1-2位小数的正数或负数
|
|
9
|
+
numberC: /^(-|\+)?\d+(\.\d+)?$/, // 正数、负数、和小数
|
|
10
|
+
numberFloatZ: /^[0-9]+(\.[0-9]{2})?$/, // 有两位小数的正实数
|
|
11
|
+
numberZhengZ: /^\+?[1-9][0-9]*$/, // 非零的正整数
|
|
12
|
+
numberZhengF: /^-[1-9]\d*$/, // 非零的负整数
|
|
13
|
+
numberFix: /^(-?\d+)(\.\d+)?$/, // 浮点数
|
|
14
|
+
numberFixZ: /^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$/, // 正浮点数
|
|
15
|
+
numberFixF: /^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$/ // 负浮点数
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
stringReg() {
|
|
19
|
+
return {
|
|
20
|
+
stringChinese: /^[\u4e00-\u9fa5]{0,}$/i, // 汉字
|
|
21
|
+
stringEngNum: /^[A-Za-z0-9]+$/i, // 汉字
|
|
22
|
+
stringMn: /^.{3,20}$/i, // 长度为m-n的所有字符
|
|
23
|
+
stringEng: /^[A-Za-z]+$/i, // 字母
|
|
24
|
+
stringEngUp: /^[A-Z]+$/i, // 大写字母
|
|
25
|
+
stringEngLow: /^[a-z]+$/i // 小写字母
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
mainReg() {
|
|
29
|
+
return {
|
|
30
|
+
email: /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, // 邮箱
|
|
31
|
+
www: /^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/, // 域名
|
|
32
|
+
url: /^http[s]{0,1}:\/\/.+$/, // url
|
|
33
|
+
phone: /^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$/, // 手机号码
|
|
34
|
+
tel: /^([0-9]{3,4}-)?[0-9]{7,8}$/, // 固定电话
|
|
35
|
+
telphone: /^(1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8})|(([0-9]{3,4}-)?[0-9]{7,8})$/, // 手机号码与固定电话
|
|
36
|
+
idcard: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, // 身份证号
|
|
37
|
+
postcode: /^[1-9]\d{5}(?!\d)$/, // 邮编
|
|
38
|
+
qq: /^(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)$/, // qq
|
|
39
|
+
space: /(^\s*)|(\s*$)/g, // 前后空格
|
|
40
|
+
spaceAll: /^\s+|\s+$/g // 含有空格
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import XEUtils from 'xe-utils'
|
|
2
|
+
|
|
3
|
+
// 数据格式化获取keys
|
|
4
|
+
function getFormatKeys(format) {
|
|
5
|
+
let regR = new RegExp('({[a-zA-Z0-9_.]*})', 'ig')
|
|
6
|
+
let formatStr = String(format)
|
|
7
|
+
return (formatStr.match(regR) || []).map((key, keyIndex) => {
|
|
8
|
+
return key.replace(/{|}/gi, '')
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 格式化数据结构
|
|
13
|
+
function format(format, data) {
|
|
14
|
+
let keys = getFormatKeys(format)
|
|
15
|
+
let formatStr = String(format)
|
|
16
|
+
keys.map((key, indexkey) => {
|
|
17
|
+
let value = XEUtils.get(data, key) || ''
|
|
18
|
+
formatStr = formatStr.replace(new RegExp('{' + key + '}', 'ig'), value)
|
|
19
|
+
})
|
|
20
|
+
return formatStr
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 生成随机字符串
|
|
24
|
+
function randomStr(len = 32) {
|
|
25
|
+
const $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
|
|
26
|
+
const maxPos = $chars.length
|
|
27
|
+
let str = ''
|
|
28
|
+
for (let i = 0; i < len; i++) {
|
|
29
|
+
str += $chars.charAt(Math.floor(Math.random() * maxPos))
|
|
30
|
+
}
|
|
31
|
+
return str
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
randomStr,
|
|
36
|
+
getFormatKeys,
|
|
37
|
+
format,
|
|
38
|
+
replaceNutrim(str) {
|
|
39
|
+
return String(str).replace(/null|undefined|(^\s*)|(\s*$)/gi, '')
|
|
40
|
+
},
|
|
41
|
+
replaceAll(str, from, to) {
|
|
42
|
+
// 替换全部 将str中的所有from替换为to
|
|
43
|
+
return str.replace(new RegExp(from, 'gm'), to)
|
|
44
|
+
},
|
|
45
|
+
filterTag(str) {
|
|
46
|
+
/* 过滤html代码(把<>转换字符串) */
|
|
47
|
+
let me = this
|
|
48
|
+
str = me.replaceAll(str, /&/gi, '&')
|
|
49
|
+
str = me.replaceAll(str, /</gi, '<')
|
|
50
|
+
str = me.replaceAll(str, />/gi, '>')
|
|
51
|
+
str = me.replaceAll(str, ' ', ' ')
|
|
52
|
+
return str
|
|
53
|
+
},
|
|
54
|
+
setWaterMark(text, settings) {
|
|
55
|
+
// 生成水印 推荐 canvas
|
|
56
|
+
class WaterMark {
|
|
57
|
+
constructor(text, settings) {
|
|
58
|
+
this.settings = Object.assign(
|
|
59
|
+
{
|
|
60
|
+
container: document.body,
|
|
61
|
+
id: 'waterMark',
|
|
62
|
+
showType: 'canvas', // div 还是 canvas
|
|
63
|
+
x: 0, // 水印起始位置x轴坐标
|
|
64
|
+
y: 100, // 水印起始位置Y轴坐标
|
|
65
|
+
xSpace: 150, // 水印x轴间隔
|
|
66
|
+
ySpace: 100, // 水印y轴间隔
|
|
67
|
+
color: '#cfcccc', // 水印字体颜色
|
|
68
|
+
alpha: 0.5, // 水印透明度
|
|
69
|
+
fontsize: '15px', // 水印字体大小
|
|
70
|
+
angle: 20, // 水印倾斜度数
|
|
71
|
+
curTime: false // 是否自动拼接当前时间
|
|
72
|
+
},
|
|
73
|
+
settings || {}
|
|
74
|
+
)
|
|
75
|
+
this.globalWidth = this.settings.container.clientWidth
|
|
76
|
+
this.globalHeight = this.settings.container.clientHeight
|
|
77
|
+
this.initCanvas()
|
|
78
|
+
this.draw(text)
|
|
79
|
+
this.insertWaterMark()
|
|
80
|
+
}
|
|
81
|
+
initCanvas() {
|
|
82
|
+
this.renderCanvas = document.createElement('canvas')
|
|
83
|
+
this.renderContext = this.renderCanvas.getContext('2d')
|
|
84
|
+
this.renderCanvas.width = this.globalWidth
|
|
85
|
+
this.renderCanvas.height = this.globalHeight
|
|
86
|
+
this.div = document.createElement('div')
|
|
87
|
+
this.oldData = this.renderContext.getImageData(0, 0, this.globalWidth, this.globalHeight)
|
|
88
|
+
}
|
|
89
|
+
insertWaterMark() {
|
|
90
|
+
const { type, id, alpha, container } = this.settings
|
|
91
|
+
this.WaterMarkEl = this.renderCanvas
|
|
92
|
+
if (type === 'div') {
|
|
93
|
+
this.WaterMarkEl = this.div
|
|
94
|
+
this.WaterMarkEl.style.backgroundImage = 'url(' + this.renderCanvas.toDataURL('image/png') + ')'
|
|
95
|
+
}
|
|
96
|
+
const style = this.WaterMarkEl.style
|
|
97
|
+
style.position = 'absolute'
|
|
98
|
+
style.opacity = alpha
|
|
99
|
+
style.top = style.left = 0
|
|
100
|
+
style.zIndex = '9999'
|
|
101
|
+
style.width = this.globalWidth + 'px'
|
|
102
|
+
style.height = this.globalHeight + 'px'
|
|
103
|
+
style.pointerEvents = 'none'
|
|
104
|
+
this.WaterMarkEl.id = id
|
|
105
|
+
container.append(this.WaterMarkEl)
|
|
106
|
+
}
|
|
107
|
+
draw(waterMarkText, settings) {
|
|
108
|
+
this.settings = Object.assign(this.settings, settings || {})
|
|
109
|
+
let { x, y, xSpace, ySpace, color, fontsize, angle, curTime } = this.settings
|
|
110
|
+
let { globalWidth, globalHeight, renderContext } = this
|
|
111
|
+
let timeStr = ''
|
|
112
|
+
if (curTime) {
|
|
113
|
+
let today = new Date()
|
|
114
|
+
timeStr =
|
|
115
|
+
today.getFullYear() +
|
|
116
|
+
'-' +
|
|
117
|
+
String(today.getMonth() + 1).padStart(2, 0) +
|
|
118
|
+
'-' +
|
|
119
|
+
String(today.getDate()).padStart(2, 0) +
|
|
120
|
+
' ' +
|
|
121
|
+
String(today.getHours()).padStart(2, 0) +
|
|
122
|
+
':' +
|
|
123
|
+
String(today.getMinutes()).padStart(2, 0) +
|
|
124
|
+
':' +
|
|
125
|
+
String(today.getSeconds()).padStart(2, 0)
|
|
126
|
+
}
|
|
127
|
+
waterMarkText = waterMarkText ? waterMarkText + ' ' + timeStr : timeStr
|
|
128
|
+
renderContext.clearRect(0, 0, globalWidth, globalHeight)
|
|
129
|
+
renderContext.font = fontsize + ' microsoft yahei'
|
|
130
|
+
renderContext.fillStyle = color
|
|
131
|
+
renderContext.textAlign = 'left'
|
|
132
|
+
renderContext.textBaseline = 'Middle'
|
|
133
|
+
let measureText = renderContext.measureText(waterMarkText)
|
|
134
|
+
let width = measureText.width
|
|
135
|
+
let height = Math.ceil(width / Math.tan(angle) + 50)
|
|
136
|
+
for (let xx = x; xx < globalWidth; xx += xSpace + width) {
|
|
137
|
+
for (let yy = y; yy < globalHeight; yy += ySpace + height) {
|
|
138
|
+
// 填充文字,x 间距, y 间距
|
|
139
|
+
renderContext.save()
|
|
140
|
+
renderContext.translate(xx, yy)
|
|
141
|
+
renderContext.rotate(0 - (angle * Math.PI) / 180)
|
|
142
|
+
renderContext.fillText(waterMarkText, 0, 0)
|
|
143
|
+
renderContext.restore()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// 图片的像素信息里存储着 RGB 的色值,R、G、B 分别为该像素的红、绿、蓝通道,每个通道的分量值范围在 0~255,16 进制则是 00~FF。在 CSS 中经常使用其 16 进制形式,比如指定博客头部背景色为 #A9D5F4。其中 R(红色)的 16 进制值为 A9,换算成十进制为 169。这时候,对 R 分量的值 + 1,即为 170,整个像素 RGB 值为 #AAD5F4,别说你看不出差别,就连火眼金金的“ 像素眼” 设计师都察觉不出来呢。于此同时,修改 G、B 的分量值,也是我们无法察觉的。因此可以得出重要结论:RGB 分量值的小量变动,是肉眼无法分辨的,不影响对图片的识别。
|
|
148
|
+
// 有了这个结论,那就给我们了利用空间,常用手段的就是对二进制最低位进行操作,下面就用 canvas 来演示一下。
|
|
149
|
+
inVisible(ctx, newData, originalData, color = 'R') {
|
|
150
|
+
var oData = originalData.data
|
|
151
|
+
var bit, offset // offset的作用是找到alpha通道值,这里需要大家自己动动脑筋
|
|
152
|
+
switch (color) {
|
|
153
|
+
case 'R':
|
|
154
|
+
bit = 0
|
|
155
|
+
offset = 3
|
|
156
|
+
break
|
|
157
|
+
case 'G':
|
|
158
|
+
bit = 1
|
|
159
|
+
offset = 2
|
|
160
|
+
break
|
|
161
|
+
case 'B':
|
|
162
|
+
bit = 2
|
|
163
|
+
offset = 1
|
|
164
|
+
break
|
|
165
|
+
}
|
|
166
|
+
for (var i = 0; i < oData.length; i++) {
|
|
167
|
+
if (i % 4 === bit) {
|
|
168
|
+
// 只处理目标通道
|
|
169
|
+
if (newData[i + offset] === 0 && oData[i] % 2 === 1) {
|
|
170
|
+
// 没有信息的像素,该通道最低位置0,但不要越界
|
|
171
|
+
if (oData[i] === 255) {
|
|
172
|
+
oData[i]--
|
|
173
|
+
} else {
|
|
174
|
+
oData[i]++
|
|
175
|
+
}
|
|
176
|
+
} else if (newData[i + offset] !== 0 && oData[i] % 2 === 0) {
|
|
177
|
+
// // 有信息的像素,该通道最低位置1,可以想想上面的斑点效果是怎么实现的
|
|
178
|
+
oData[i]++
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
ctx.putImageData(originalData, 0, 0)
|
|
183
|
+
}
|
|
184
|
+
visible(ctx, originalData, color = 'R') {
|
|
185
|
+
var bit, offset // offset的作用是找到alpha通道值,这里需要大家自己动动脑筋
|
|
186
|
+
switch (color) {
|
|
187
|
+
case 'R':
|
|
188
|
+
bit = 0
|
|
189
|
+
offset = 3
|
|
190
|
+
break
|
|
191
|
+
case 'G':
|
|
192
|
+
bit = 1
|
|
193
|
+
offset = 2
|
|
194
|
+
break
|
|
195
|
+
case 'B':
|
|
196
|
+
bit = 2
|
|
197
|
+
offset = 1
|
|
198
|
+
break
|
|
199
|
+
}
|
|
200
|
+
var data = originalData.data
|
|
201
|
+
for (var i = 0; i < data.length; i++) {
|
|
202
|
+
if (i % 4 === bit) {
|
|
203
|
+
// 只处理目标通道
|
|
204
|
+
if (data[i] % 2 === 0) {
|
|
205
|
+
// 没有信息的像素,该通道最低位置0,但不要越界
|
|
206
|
+
if (data[i] % 2 === 0) {
|
|
207
|
+
data[i] = 0
|
|
208
|
+
} else if (i % 4 === 3) {
|
|
209
|
+
// alpha通道不做处理
|
|
210
|
+
continue
|
|
211
|
+
} else {
|
|
212
|
+
// 关闭其他分量,不关闭也不影响答案
|
|
213
|
+
data[i] = 0
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
ctx.putImageData(originalData, 0, 0)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return new WaterMark(text, settings)
|
|
222
|
+
},
|
|
223
|
+
removeWaterMark(container, id) {
|
|
224
|
+
const body = container || document.body
|
|
225
|
+
body.removeChild(document.getElementById(id || 'waterMark'))
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default {}
|