最近一个需求是将 excel 里的某列的文本和某列的多图片组合成 html,然后后端批量将这些 html 插入到数据库中,从而在前端展示。
excel 里的大致数据如下:
需要将这些数据组合成 html,如下:
<p>文本</p>
<p><img src="xx1" /><img src="xx2" /></p>
这边 js 拿到 excel 里的图片数据且与行号挂钩,有点麻烦,后端用行号命名文件夹,然后存放相应行的图片,所以我这边直接拿到图片文件夹。
1. 将 excel 里数据存储为 json数组
将 excel 里的数据存储为数组,这里使用了 xlsx 库。
npm install xlsx
拿到 excel 文件路径,然后将 excel 里的数据转换成数组。
/**
* @description: 将excel里的数据转换成json
* @param {string} excelFilePath excel文件路径
* @param {number} sheetIndex 工作表的索引,默认为0
* @return {array} 返回json数据数组
* @example
* transSheetToJSON('D:/示例.xlsx', 0) => [{ '列1': '文本1', '列2': '1' }, { '列1': '文本2', '列2': '2' }]
* 注意图片列不能识别,所有图片列的数据都是空的。
*/
function transSheetToJSON(excelFilePath, sheetIndex = 0) {
const XLSX = require('xlsx');
// 读取Excel文件
const workbook = XLSX.readFile(excelFilePath);
// 获取工作表(sheet)的名字
const sheetName = workbook.SheetNames[sheetIndex];
// 读取工作表的数据
const worksheet = workbook.Sheets[sheetName];
// 将工作表转换为JSON数据
const jsonData = XLSX.utils.sheet_to_json(worksheet);
// 输出JSON数据
return jsonData;
}
这样图中所示的 excel 数据就转换成了 数组数据
[
{ '评价文本': `1. 部分学习框没名\r\n 2. 讲解一般而是时间段\r\n3. 讲解完动词过told`, },
{ '评价文本': `1. 没有针对性命名\r\n2. 时刻而是时间段\r\n3. 目都考查的是动词的不规则变化,需要换题,比如第一题的see变成saw;第二题的tell变成told`, },
];
2. 上传所有图片,得到对象 { 行号: url 数组 }
- 写函数-上传单张图片到 cos,返回 url
- 写函数-上传文件夹里的所有图片到 cos,返回 url 数组
- 写函数-上传多个文件夹里的所有图片到 cos,返回对象数组 { 文件夹名: url 数组 }
因为这里的文件夹名是行号,所以这两是等同的。
这样图中所示的 excel 数据就转换成了 对象数据:
{
'2': [
'https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos1.png'
],
'3': [
'https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos2.png'
]
}
实现代码如下:
提前安装 cos-nodejs-sdk-v5
npm install cos-nodejs-sdk-v5
/**
* @description: 上传文件到cos
* @param {string} localFilepath 本地文件路径
* @param {function} getConfig 获取cos配置的函数
* @return {Promise} 返回上传成功后的url
* @example
* uploadFileToCos('D:/示例.jpg', getConfig) => 'https://.....示例_232.jpg'
*/
function uploadFileToCos(localFilepath, getConfig) {
const COS = require('cos-nodejs-sdk-v5');
const fs = require('fs');
const path = require('path');
return new Promise(async (resolve, reject) => {
/** 防止请求太快,所以加了延迟 */
await sleep(100);
// 获取cos配置 这里是一个异步函数 { bucketName, region, path, SecretId, SecretKey }
const cosConfig = await getConfig();
const cos = new COS(cosConfig);
const file = await fs.readFileSync(path.resolve(localFilepath));
const params = {
Bucket: cosConfig.bucketName,
Region: cosConfig.region,
// Key 可以理解是文件的路径,这里我使用了一个带有uuid的文件名
Key: `${cosConfig.path}/${addUuidToName(localFilepath)}`,
Body: file,
onProgress: function (progressData) {
const { loaded, total } = progressData;
console.log('上传进度', { loaded, total }); // 临时
},
};
cos.putObject(params, function (err, data) {
if (err) {
console.error('上传失败', err);
reject(err);
return;
}
console.log('上传成功', data.Location);
const url = `https://${data.Location}`;
resolve(url);
return Promise.resolve(url);
});
});
}
/**
*
* @param {*} dirPath 文件夹路径,里面是多个文件
* @param {*} getConfig
* @returns 字符串数组,每个元素是一个文件的url
* @example
* 示例文件夹里有 示例1.jpg, 示例2.jpg
* uploadDirFilesToCos('D:/示例文件夹', getConfig) => ['https://.....示例1_232.jpg', 'https://.....示例2_233.jpg']
*/
async function uploadDirFilesToCos(dirPath, getConfig) {
const fs = require('fs');
const path = require('path');
const files = fs.readdirSync(dirPath);
const urls = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
await sleep(100);
try {
const url = await uploadFileToCos(path.resolve(dirPath, file), getConfig);
urls[i] = url;
} catch (error) {
urls[i] = '';
}
}
return urls;
}
/**
*
* @param {*} baseDirPath 有多个文件夹的文件夹路径
* @param {*} getConfig
* @returns 对象数组,每个对象的key是文件夹名,value是一个字符串数组,每个元素是一个文件的url
*/
async function uploadBaseDir(baseDirPath, getConfig) {
const fs = require('fs');
const path = require('path');
const dirList = fs.readdirSync(baseDirPath);
const res = {};
for (let i = 0; i < dirList.length; i++) {
const dir = dirList[i];
const dirPath = path.resolve(baseDirPath, dir);
try {
const urlList = await uploadDirFilesToCos(dirPath, getConfig);
res[dir] = urlList;
} catch (error) {
res[dir] = [];
}
}
console.log(`${baseDirPath}上传完成`, res);
return res;
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
*
* @param {*} filenameOrPath
* @returns string
* @example
* addUuidToName('test.png') // test_6e1fa314-96ef-d686-104c-4b6a67649483.png
*/
function addUuidToName(filenameOrPath) {
const uuid = require('react-uuid');
const path = require('path');
const filename = path.basename(filenameOrPath);
const dotIndex = filename.lastIndexOf('.');
if (dotIndex === -1) {
return `${filename}_${uuid()}`;
}
const base = filename.slice(0, dotIndex);
const suffix = filename.slice(dotIndex);
return `${base}_${uuid()}${suffix}`;
}
3. 将文本和图片组合成 html
获取表格里的文本数据,然后将图片的 url 一起组合成 html。
首先看下索引和行号的对应关系,第二行其实是对应数组索引 0,第三行对应数组索引 1。
const jsonArr = [
{ '评价文本': `1. 部分学习框没名\r\n 2. 讲解一般而是时间段\r\n3. 讲解完动词过told`, },
{ '评价文本': `1. 没有针对性命名\r\n2. 时刻而是时间段\r\n3. 目都考查的是动词的不规则变化,需要换题,比如第一题的see变成saw;第二题的tell变成told`, },
];
const imgObj = {
2: [
'https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos1.png',
],
3: [
'https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos2.png',
],
};
然后将文本和图片组合成 html。
function mergeTextAndImgToHtml(jsonArr, textKey, imgObj) {
const htmlArr = [];
for (let i = 0; i < jsonArr.length; i++) {
const item = jsonArr[i];
const imgArr = imgObj[i + 2] || [];
const imgHtml = imgArr.map((img) => `<img src="${img}" />`).join('');
// 将\r\n替换成</p><p>
const textHtml = `<p>${item[textKey].replaceAll('\r\n', '</p><p>')}</p>`;
const html = `${textHtml}<p>${imgHtml}</p>`;
htmlArr.push(html);
}
return htmlArr;
}
这样就得到了 html 数组。
[
`<p>1. 部分学习名</p>
<p>2. 讲解一 1950不是时刻而是时间段</p>
<p>3. 讲解完动词题的t成told</p>
<p><img src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos1.png" />
</p>`,
`<p>1. 没有针对性命名</p>
<p>2. 时刻而是时间段</p>
<p>3. 目都考查的是动词的不规则变化,需要换题,比如第一题的see变成saw;第二题的tell变成told</p>
<p><img src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos2.png" />
</p>`,
];
执行的代码如下:
安装 axios
npm install axios
其他代码如下:
const path = require('path');
const uploadConfig = async () => {
const axios = require('axios');
const res = await axios.post('https://xx/config');
if (!res.data.success) {
console.error('请求配置失败');
return;
}
console.log('成功请求到配置');
const cosConfig = res.data.data;
const result = {
bucketName: cosConfig.bucketName,
region: cosConfig.region,
path: cosConfig.path || '/blog/code',
/** 验证信息 */
SecretId: cosConfig.tmpSecretId,
SecretKey: cosConfig.tmpSecretKey,
SecurityToken: cosConfig.sessionToken,
};
return result;
};
async function getRes() {
const jsonArr = transSheetToJSON(path.resolve(__dirname, './info.xlsx'));
const imgObj = await uploadBaseDir(
path.resolve(__dirname, './images'),
uploadConfig
);
return mergeTextAndImgToHtml(jsonArr, '评价文本', imgObj);
}
getRes()