
用于记录在工作与学习的过程中,发现的各种开发技巧与解决方案,便于日后再遇到同类问题时能快速查阅到解决方案。
-
解决create-react-app 脚手架打包时,运行内存溢出问题的命令
set NODE_OPTIONS=--max_old_space_size=4096&&react-scripts build -
缺少python2.7支持,可快速使用以下语句完成安装
npm install --global --production windows-build-tools -
针对页面指定区域生成pdf文件的方法
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'
import Canvg from 'canvg'
/*
利用Canvg导出页面指定区域为PDF格式
*/
export const getPdfByCanvg = (title, container) => {
return new Promise(async(resolve, reject)=>{
try {
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
canvas.setAttribute('width', '8000px')
canvas.setAttribute('height', '1500px')
canvas.setAttribute('position', 'fixed')
canvas.setAttribute('top', '99999999px')
document.body.appendChild(canvas)
/*
由于canvg库不支持filter属性,如果加上,矩形和圆形的描边或者填充都失效,所以在转换之前先将filter去掉,使用stroke代替
*/
let rectArr = Array.from(container.getElementsByTagName('rect'))
rectArr.forEach(item=>{
item.setAttribute('stroke', '#ccc')
item.removeAttribute('filter')
})
let rootCircle = container.getElementsByTagName('circle')[0]
rootCircle.setAttribute('stroke', '#ccc')
rootCircle.removeAttribute('filter')
let v = await Canvg.from(ctx, `<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><rect fill="white" stroke="none" width="${container.clientWidth}" height="${container.clientHeight}" pointer-events="all"></rect>${container.innerHTML.trim()}</svg>`, {
offsetX: 3000
})
await v.render()
let a = document.createElement('a')
a.style.display = 'none'
let contentWidth = 8000
let contentHeight = 1500
let pageHeight = contentWidth / 592.28 * 841.89
let leftHeight = contentHeight
let position = 0
let imgWidth = 595.28
let imgHeight = 592.28 / contentWidth * contentHeight
let pageData = canvas.toDataURL('image/png', 1.0)
let PDF = new JsPDF('', 'pt', 'a4')
if (leftHeight < pageHeight) {
PDF.addImage(canvas, 'PNG', 0, 0, imgWidth, imgHeight)
} else {
while (leftHeight > 0) {
PDF.addImage(canvas, 'PNG', 0, position, imgWidth, imgHeight)
leftHeight -= pageHeight
position -= 841.89
if (leftHeight > 0) {
PDF.addPage()
}
}
}
PDF.save(title + '.pdf')
document.body.removeChild(canvas)
resolve()
} catch (err){
reject(err)
} finally {
/*
还原矩形与圆形的样式
*/
let rectArr = Array.from(container.getElementsByTagName('rect'))
let rootCircle = container.getElementsByTagName('circle')[0]
rectArr.forEach(item=>{
item.setAttribute('stroke', 'none')
item.setAttribute('filter', 'drop-shadow(0px 5px 5px rgba(0,0,0,0.1))')
})
rootCircle.setAttribute('stroke', 'none')
rootCircle.setAttribute('filter', 'drop-shadow(0px 5px 5px rgba(0,0,0,0.1))')
}
})
}
/*
利用html2canvas导出页面指定区域为PDF格式
*/
export const getPdfByHtml2canvas = (title, container) => {
return html2Canvas(container, {
allowTaint: true,
width: 8000,
height: 1500,
x: 3000
}).then(canvas=>{
let contentWidth = canvas.width
let contentHeight = canvas.height
let pageHeight = contentWidth / 592.28 * 841.89
let leftHeight = contentHeight
let position = 0
let imgWidth = 595.28
let imgHeight = 592.28 / contentWidth * contentHeight
let pageData = canvas.toDataURL('image/jpeg', 1.0)
let PDF = new JsPDF('', 'pt', 'a4')
if (leftHeight < pageHeight) {
PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
} else {
while (leftHeight > 0) {
PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
leftHeight -= pageHeight
position -= 841.89
if (leftHeight > 0) {
PDF.addPage()
}
}
}
PDF.save(title + '.pdf')
})
}
-
Mac下最简单翻墙的方法,自动代理配置url:raw.githubusercontent.com/bannedbook/…,
-
linux 开启端口
/sbin/iptables -I INPUT -p http --dport 3000 -j ACCEPT,查看linux端口状态netstat -anp -
生成ssh秘钥:ssh-keygen -t rsa -C "any comment can be written here"
-
jenkins构建失败后自动重新进行构建的插件:Naginator
-
解决git拉取代码时报错
gnutls_handshake() failed: The TLS connection was non-properly terminated.: 将代码仓库地址的协议更改为http -
node.js连接mysql出现错误:
ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
解决方案:
ALTER USER '用户名' IDENTIFIED WITH mysql_native_password BY '密码';FLUSH PRIVILEGES;
- 生成rsa公钥与私钥
- 打开命令行工具,输入openssl,打开openssl
- 生成私钥:
genrsa -out rsa_private_key.pem 2048 - 生成公钥:
rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
-
JSON.parse解析换行符\n会报错,需要对目标字符串中\n进行替换:
'目标字符串\n'.replace(/\n/g, '\\n') -
cursor: url 注意事项
- 图片大小不能大于32*32
- 图片格式最好是.cur和.ico
-
用于RSA加密的npm包: jsencrypt
-
webpack构建输出bundle详细信息文件stats.json: 增加
--profile --json参数 -
可以调整 creat-react-app 脚手架webpack配置项的npm包:@rescripts/cli
-
在设置webpack运行时publicPath时,报错
'__webpack_public_path__' is not defined no-undef,原因是 ESLint规则警告有关使用未声明的变量,需要告诉ESLint __webpack_public_path__是全局变量。,解决方案有三种:
- 在使用 webpack_public_path 的地方加上注释:
/* global __webpack_public_path__:writable */ - 直接在使用 webpack_public_path 的地方加上注释: //eslint-disable-next-line 或 //eslint-disable-line
- 配置eslint
{
"globals": {
"__webpack_public_path__": "writable"
}
}
17. 客户端连接mysql服务的两种方式:
- mysql --host=localhost --user=myname --password=password mydb
- mysql -h localhost -u myname -ppassword mydb
需要注意如果明确指定了-p或者--password的值,那么-p或者--password和密码之间是不能有空格的,如果你使用了-p或者--password选项但是没有给出password值,客户端会提示输入密码
-
webpack报错
Uncaught TypeError: Cannot read property 'bind' of undefined, 直接原因是webpack运行时代码里的webpackJsonp的push方法是undefined导致的,根本原因是多个bundle使用了相同的output.jsonpFunction的值,所以只需将其赋值为不一样的值就可以解决了,详见issue -
开发node的CLI工具的项目中,所有依赖项必须是dependencies。如果是devDependencies,那么在全局安装CLI工具时不会自动安装对应依赖
-
fatal: refusing to merge unrelated histories解决方案:git merge master --allow-unrelated-histories -
软件许可证图例

-
移动端视口配置
<meta name="viewport" content="width=device-width; initial-scale=1; maximum-scale=1; minimum-scale=1; user-scalable=no;">,user-scalable设置为no可以解决移动端点击事件延迟问题 -
md文档一键转换为微信公众号文章:md.openwrite.cn/?from=didi
-
eslint删除无用导入的插件:eslint-plugin-unused-imports
-
关键字高亮处理方法:
const highlightKeyWord = (text, keyword) => {
return keyword
? text.split(new RegExp(`(${keyword})`, 'gi')).map((c, i) =>
c === keyword ? (
<span key={i} style={{ color: '#009ba5' }}>
{c}
</span>
) : (
c
)
)
: text;
};
- js复制文本方法
//方法1
await navigator.clipboard.writeText('some text');
//方法2
const input = document.createElement('input');
input.setAttribute('value','some text');
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
- 中英文加数字混合排序
export function mixSort(_a, _b) {
const reg = /[a-zA-Z0-9]/
// 比对仅针对字符串,数字参与对比会导致对比的字符串转为number类型,变成NaN
const a = _a.toString()
const b = _b.toString()
// 比对0号位的原因是字符串中有可能出现中英文混合的情况,这种仅按首位排序即可
if (reg.test(a[0]) || reg.test(b[0])) {
if (a > b) {
return 1
} else if (a < b) {
return -1
} else {
return 0
}
} else {
return a.localeCompare(b)
}
}
- 给图片添加水印
(async ()=>{
const genWaterMark = async (imgSrc: string) => {
const canvas = document.createElement('canvas');
// ① 图片路径转成canvas
await imgSrc2Canvas(canvas, imgSrc);
// ② canvas添加水印
addWatermark(canvas);
// ③ canvas转成img
return canvas.toDataURL('image/png');
};
const addWatermark = async canvas => {
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgba(100,100,100,0.2)'; // 字体颜色
ctx.font = `24px serif`;
ctx.translate(0, 0);
ctx.rotate((5 * Math.PI) / 180); // 旋转角度
const repeatX = Math.floor((canvas.width / 24) * 4); // 100 为每个水印的基本宽度
const repeatY = Math.floor((canvas.height / 24) * 4);
for (let i = 0; i < repeatX; i++) {
for (let j = 1; j < repeatY; j++) {
ctx.fillText(`哈哈哈哈`, 24 * 4 * i + 20, 24 * j + 20); // 控制水印的疏密
}
}
};
const imgSrc2Canvas = (cav: HTMLCanvasElement, imgSrc: string) => {
return new Promise(async resolve => {
const image = new Image();
image.src = imgSrc;
// ① 为图片设置crossOrigin属性,防止Failed to execute 'toDataURL' on 'HTMLCanvasElement'
image.setAttribute('crossOrigin', 'anonymous');
// ② 解决渲染图片为透明图层
await new Promise(resolve => (image.onload = resolve));
cav.width = image.width;
cav.height = image.height;
const ctx = cav.getContext('2d');
if (ctx) {
ctx.drawImage(image, 0, 0);
}
resolve(cav);
});
};
const link = document.createElement('a');
link.download = `pic.png`;
const url = await genWaterMark(dataUrl);
link.href = url;
link.click();
})()
-
当给“图片元素”添加mousemove事件时,需要设置draggable=false,否则会出现mousemove事件不触发的bug
-
禁用移动端手势返回
window.addEventListener('popstate', () => {
history.pushState(null, '', document.URL)
})
- ai隐式标识打标
/** 工具函数 */
function blobToDataURL(blob: Blob): Promise<string> {
return new Promise(resolve => {
const reader = new FileReader()
reader.onload = () => resolve(reader.result as string)
reader.readAsDataURL(blob)
})
}
function dataURLToBlob(dataURL: string, type: string): Blob {
const byteString = atob(dataURL.split(',')[1])
const ab = new ArrayBuffer(byteString.length)
const ia = new Uint8Array(ab)
for (let i = 0; i < byteString.length; i++) ia[i] = byteString.charCodeAt(i)
return new Blob([ab], { type })
}
function stringToExifUnicode(str: string): Uint8Array {
const utf16 = unescape(encodeURIComponent(str))
const arr = new Uint8Array(utf16.length + 8)
const prefix = [85, 78, 73, 67, 79, 68, 69, 0] // "UNICODE\0"
arr.set(prefix, 0)
for (let i = 0; i < utf16.length; i++) arr[i + 8] = utf16.charCodeAt(i)
return arr
}
/** Blob / File / ArrayBuffer / Uint8Array 转 ArrayBuffer */
async function toArrayBuffer(input: Blob | ArrayBuffer | Uint8Array): Promise<ArrayBuffer> {
if (input instanceof Blob) return await input.arrayBuffer()
if (input instanceof ArrayBuffer) return input
if (input instanceof Uint8Array) {
// 确保返回的是 ArrayBuffer 而不是 SharedArrayBuffer
const buffer = input.buffer
if (buffer instanceof ArrayBuffer) {
return buffer.slice(input.byteOffset, input.byteOffset + input.byteLength)
}
// 如果是 SharedArrayBuffer,创建一个新的 ArrayBuffer
const newBuffer = new ArrayBuffer(input.byteLength)
new Uint8Array(newBuffer).set(input)
return newBuffer
}
throw new Error('Unsupported input type')
}
//PDF
//依赖pdf-lib库
async function setAIGCPropertyForPDF(fileOrBuffer: Blob | ArrayBuffer | Uint8Array, aigcData: any): Promise<Blob> {
if (fileOrBuffer instanceof Blob) {
fileOrBuffer = await fileOrBuffer.arrayBuffer();
}
const aigcJson = JSON.stringify(aigcData);
const pdfEscapedJson = aigcJson.replace(/([\\()])/g, '\\$1');
const pdfDoc = await PDFDocument.load(fileOrBuffer);
const allDicts: PDFDict[] = [];
const indirectObjects = pdfDoc.context.enumerateIndirectObjects();
let infoDict = null;
// 遍历查找所有字典对象
for (const [_, obj] of indirectObjects) {
if (obj instanceof PDFDict) {
allDicts.push(obj);
}
}
infoDict = allDicts.find((item) => {
for (const [key] of item.dict) {
if (key.encodedName === '/CreationDate') {
return true;
}
}
});
if (infoDict) {
infoDict.set(PDFName.of('AIGC'), PDFString.of(pdfEscapedJson));
}
const blob = new Blob([await pdfDoc.save()], { type: 'application/pdf' });
return blob;
}
//MD
/**
* 设置 Markdown 文件头部的 AIGC 元数据
* @param mdContent - 原始 Markdown 文本
* @param aigcData - AIGC 元数据对象
* @returns 新的 Markdown 文本
*/
export function setAIGCPropertyForMD(mdContent: string, aigcData: AIGCData): string {
// 构造AIGC的YAML块
const yamlEscape = (str: string) => `'${str.replace(/'/g, "''")}'`
const aigcYamlLines = [
'AIGC:',
...Object.entries(aigcData)
.filter(([, value]) => value !== undefined && value !== null)
.map(([key, value]) => ` ${key}: ${yamlEscape(String(value))}`)
]
const yamlBlock = `---\n${aigcYamlLines.join('\n')}\n---`
if (mdContent.startsWith('---')) {
// 替换或插入AIGC块
const parts = mdContent.split(/\n---\s*/)
let yamlLines = parts[0].split('\n')
const aigcStart = yamlLines.findIndex(line => line.trim() === 'AIGC:')
if (aigcStart !== -1) {
// 替换AIGC块
let endIdx = aigcStart + 1
while (endIdx < yamlLines.length && yamlLines[endIdx].startsWith(' ')) endIdx++
yamlLines.splice(aigcStart, endIdx - aigcStart, ...aigcYamlLines)
} else {
// 插入到第二行(保留YAML格式)
yamlLines.splice(1, 0, ...aigcYamlLines)
}
return `${yamlLines.join('\n')}\n---${parts.length > 1 ? '\n' + parts.slice(1).join('\n---') : ''}`
} else {
// 没有 front matter,直接插入
return `${yamlBlock}\n\n${mdContent}`
}
}
//XML
/**
* 基于 OOXML 自定义属性写入 AIGC 元数据
* 包含docx、pptx、xlsx、dotx、xltx、potx;
* 依赖:npm install jszip
* @returns 新的 XML blob
*
*/
export async function setAIGCPropertyForXML(
fileOrBlob: Blob | ArrayBuffer | Uint8Array,
aigcData: AIGCData
): Promise<Blob> {
const buffer = await toArrayBuffer(fileOrBlob)
const zip = new JSZip()
await zip.loadAsync(buffer)
const aigcJson = JSON.stringify(aigcData)
const customPath = 'docProps/custom.xml'
let customXml = await zip.file(customPath)?.async('string')
if (!customXml) {
customXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
</Properties>`
}
const parser = new DOMParser()
const xmlDoc = parser.parseFromString(customXml, 'application/xml')
// 删除已有 AIGC 节点
const oldProp = Array.from(xmlDoc.getElementsByTagName('property')).find(p => p.getAttribute('name') === 'AIGC')
if (oldProp && oldProp.parentNode) {
oldProp.parentNode.removeChild(oldProp)
}
// 创建新 property
const prop = xmlDoc.createElementNS(
'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties',
'property'
)
prop.setAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}')
prop.setAttribute('pid', Date.now().toString().slice(-6)) // 生成唯一 pid
prop.setAttribute('name', 'AIGC')
const vtNS = 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'
const lpwstr = xmlDoc.createElementNS(vtNS, 'vt:lpwstr')
lpwstr.textContent = aigcJson
prop.appendChild(lpwstr)
xmlDoc.documentElement.appendChild(prop)
// 写回 zip
const serializer = new XMLSerializer()
zip.file(customPath, serializer.serializeToString(xmlDoc))
return zip.generateAsync({ type: 'blob' })
}
//png,jpg
/** 统一方法:PNG / JPEG 兼容 */
export async function setAIGCPropertyForImage(blob: Blob, aigcData: AIGCData, useCustomChunk = false): Promise<Blob> {
if (blob.type === 'image/png') {
return setAIGCPropertyForPng(blob, aigcData, useCustomChunk)
} else if (blob.type === 'image/jpeg' || blob.type === 'image/jpg') {
return setAIGCPropertyForJpg(blob, aigcData)
} else {
throw new Error(`Unsupported image type: ${blob.type}`)
}
}
//png
/**
* 向 PNG Blob 写入 AIGC 元数据
* @param blob PNG 文件 Blob
* @param aigcData AIGC 元数据对象
* @param useCustomChunk 是否使用自定义块(否则用 tEXt)
* @returns 新的 PNG Blob
*/
async function setAIGCPropertyForPng(blob: Blob, aigcData: AIGCData, useCustomChunk = false): Promise<Blob> {
const buffer = new Uint8Array(await blob.arrayBuffer())
const chunks = extractChunks(buffer)
const jsonStr = JSON.stringify(aigcData)
// 删除已有 AIGC 块
const cleanedChunks = chunks.filter(chunk => {
if (chunk.name === 'tEXt') {
const text = new TextDecoder().decode(chunk.data)
return !text.startsWith('AIGC=')
}
return chunk.name !== 'AIGC'
})
// 构造新块
const newChunk = useCustomChunk
? {
name: 'AIGC',
data: new TextEncoder().encode(jsonStr)
}
: PNGTextChunk.encode('AIGC', jsonStr)
// 插入到第一个 IDAT 之前
const insertIndex = cleanedChunks.findIndex(chunk => chunk.name === 'IDAT')
if (insertIndex === -1) throw new Error('Invalid PNG: no IDAT chunk found.')
cleanedChunks.splice(insertIndex, 0, newChunk)
// 重编码并生成 Blob
const newBuffer = encodeChunks(cleanedChunks)
// 确保类型安全,处理 SharedArrayBuffer 兼容性
const uint8Array = newBuffer instanceof Uint8Array ? newBuffer : new Uint8Array(newBuffer)
return new Blob([uint8Array as Uint8Array], { type: 'image/png' })
}
//jpg
/**
* 基于 EXIF UserComment 写入 AIGC 元数据
* 支持 JPEG/JPG, PNG ≥1.5
* 依赖:npm install piexifjs
*/
async function setAIGCPropertyForJpg(blob: Blob, aigcData: AIGCData): Promise<Blob> {
const dataURL = await blobToDataURL(blob)
const jsonStr = JSON.stringify({ AIGC: aigcData })
let exifObj: any = {}
try {
exifObj = piexif.load(dataURL)
} catch {
exifObj = { '0th': {}, Exif: {}, GPS: {}, Interop: {}, '1st': {}, thumbnail: null }
}
exifObj['Exif'][piexif.ExifIFD.UserComment] = stringToExifUnicode(jsonStr)
const exifBytes = piexif.dump(exifObj)
const newDataURL = piexif.insert(exifBytes, dataURL)
return dataURLToBlob(newDataURL, blob.type)
}