林安随笔
type: channel
link: https://www.yuaq.cn/
description: 在心里种花，人生才不会荒芜
items: spacedesk 将你的手机，pad变为外接显示器
items: echarts 多图表鼠标移入联动（tooltip、高亮）
items: echarts 实现数据分段不同范围样式
items: 使用node+react+sse搭建AI聊天工具
items: highCharts 柱状图/折线图鼠标悬浮高亮
items: highCharts组成与基础配置
items: Lodash库常用方法
items: ES6常用方法（数组）
items: js中Excel文件导入与导出
items: openlayers绘制图形

spacedesk 将你的手机，pad变为外接显示器
type: item
date: 2025-12-05T08:56:00+08:00
description: spacedesk 是一款免费的多屏扩展工具，采用主从架构实现屏幕共享与扩展，能够将安卓手机、平板、iOS 设备甚至网页端设备，转变为 Windows 电脑的副屏，实现多屏协同办公或娱乐。其核心优势如下：零硬件成本：无需额外购置显示器，可盘活闲置的手机、平板设备；灵活连接方式：支持无线局域网（WiFi）、USB 有线连接，满足不同场景的延迟需求；多模式适配：可实现扩展显示（副屏作为独立工作区）、复制显示（副屏同步主屏内容），还支持屏幕方向自定义（横屏 / 竖屏），适配编程、文档阅读等不同使用场景；触控支持：安卓端可直接通过触屏或手写笔操控电脑内容，替代鼠标操作。目前该工具仅支持 Windows 系统作为主设备（主机端），客户端可覆盖安卓、iOS 及网页端，暂不支持 Mac 系统作为主机。二、Windows 主机端安装安装前提系统要求：Windows 10（Build 14393 及以上）、Windows 8.1/8/7（Windows 7/8 版本为非官方正式支持，需自行承担风险）；硬件要求：需配备 Nvidia/AMD/Intel 等主流图形适配器，支持 WDDM 1.1 及以上驱动，DirectX 9 及以上版本；网络要求：若使用无线连接，需保证电脑与客户端设备处于同一局域网。安装步骤下载安装包：访问 spacedesk 官方下载页面，获取对应 Windows 版本的驱动程序（安装包为 spacedesk Driver Software）；运行安装向导：双击安装包，按照提示点击 “下一步” 完成安装，过程中若弹出 Windows 防火墙权限请求，务必选择 “允许访问”，否则会导致后续设备无法连接；确认安装成功：安装完成后，电脑右下角任务栏会出现 spacedesk 绿色图标，图标显示绿色对勾即代表服务启动成功，右键点击图标可打开「spacedesk driver Console」进行后续设置。汉化方式，以下为汉化后的页面，汉化版本较低，一些功能未完整展示，可满足使用局域网连接三、安卓端客户端安装安装前提系统要求：安卓 4.1 及以上版本；权限要求：若通过非官方渠道安装，需开启 “未知来源应用安装” 权限（路径：设置→安全→允许未知来源）。安装渠道与步骤渠道 1：官方渠道（推荐）访问 spacedesk 安卓端专属下载页：https://www.spacedesk.net/android，点击「Download spacedesk Android client」获取官方 APK 安装包（无需依赖 Google Play）；将 APK 传输至安卓设备，双击安装即可。渠道 2：Google Play应用商店搜索spacedesk VIEWER for Android，下载安装即可四、使用方法前期准备确保 Windows 主机端服务已启动（任务栏图标为绿色对勾），且安卓设备与电脑处于同一 WiFi 网络；若使用 USB 连接，需提前为安卓设备开启 “开发者选项” 和 “USB 调试” 功能。连接步骤方式 1：无线连接（便捷首选）打开安卓端 spacedesk 应用，APP 会自动扫描局域网内的 Windows 主机；在扫描列表中点击对应电脑的 IP 地址或主机名，首次连接需在电脑端确认授权，授权后即可完成连接；若未扫描到主机，可点击 APP 右上角 “+” 按钮，手动输入电脑的 IP 地址（可在电脑端「spacedesk driver Console」中查询）进行连接。方式 2：USB 有线连接（低延迟首选）用数据线将安卓设备与电脑连接，确保安卓端已开启 “USB 调试”（如果识别不到可以关闭USB调试，再重新打开，spacedesk会识别到）；在电脑端「spacedesk driver Console」中启用 “USB (Android)” 选项；打开安卓端 spacedesk 应用，APP 会自动识别 USB 连接，点击连接即可建立低延迟通信。多屏模式设置连接成功后，Windows 会将安卓设备识别为第二显示器，可通过以下步骤调整显示模式：在电脑桌面右键，选择「显示设置」；在 “多显示器” 选项中选择模式：扩展这些显示器：副屏作为独立工作区，可将窗口拖拽至安卓设备屏幕，实现多任务并行（如主屏写文档、副屏查资料）；复制这些显示器：副屏同步主屏内容，适合会议演示场景；可拖拽显示器示意图调整物理位置，还能在 “显示方向” 中设置副屏为横屏或竖屏，适配不同使用需求。画质与性能优化在安卓端 spacedesk 应用的「setting」-「quality/performance」中，可根据网络情况调整参数：追求清晰度：提高画质分辨率，降低压缩率；追求流畅度：降低分辨率，提升压缩率，同时关闭安卓设备动态壁纸、后台冗余应用，减少系统负担。五、…

echarts 多图表鼠标移入联动（tooltip、高亮）
type: item
date: 2025-06-24T15:44:00+08:00
description: 效果图：代码实现：import React, { Component } from 'react'; import * as echarts from 'echarts'; import moment from 'moment' const { registerReactDom, logoutReactDom } = scriptUtil; let style = document.createElement('style'); style.innerHTML = ` .echarts-container { width: 100%; height: calc(100% - 80px); display: flex; justify-content: flex-start; flex-wrap: wrap; overflow: auto; // background:rgb(80, 202, 117); } .echarts-container li { list-style: none; margin-right: 20px; margin-bottom: 20px; border: 1px solid #000; } .echarts-container li:nth-child(3n){ margin-right: 0; } .echarts-container li:last-child{ margin-bottom: 0; } .no-data{ width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; font-size: 20px; color: #000; } `; document.head.appendChild(style); class CustomComp extends Component { state = { data: {}, xData: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], chartInstances: {}, currentHighlightIndex: null, chartType: 'bar' // 图表类型状态 } componentDidMount = () => { registerReactDom(this, this.props); } componentWillUnmount() { logoutReactDom(this, this.props); } componentDidUpdate(prevProps, prevState) { if (prevState.currentHighlightIndex !== this.state.currentHighlightIndex) { this.highlightCharts(this.state.currentHighlightIndex); } if (prevState.chartType !== this.state.chartType) { this.updateAllCharts(); } } // 数据初始化 init = (data) => { let newData = _.groupBy(data, 'pn'); // 按pn分组 /* 示例数据： {"B80005210603181143": [{dataDate:"2025-01", valSUM: 124829},...], "B80005210603195057": [{dataDate:"2025-01", valSUM: 124829},...]} */ // console.log(newData, 'newData'); this.setState({ data: newData }) Object.keys(newData).forEach((key) => { let obj = newData[key]; let yData = []; obj.forEach((item) => { yData.push(item.valSUM); }) this.chartInit('analysis' + key, yData, obj[0].deviceName, key); }) } // 图表初始化 chartInit = (id, yData, name, key) => { let { xData, chartType } = this.state; let myChart = echarts.init(document.getElementById(id)); va…

echarts 实现数据分段不同范围样式
type: item
date: 2025-06-24T15:38:00+08:00
description: 效果图：visualMap：min 和 max：设置数据映射的最小值和最大值。inRange：定义一个映射的范围，可以设置颜色、大小等属性。outOfRange：定义超出范围的映射属性。orient：设置组件的方向，可以是 ‘horizontal’ 或 ‘vertical’。left、right、top、bottom：设置组件的位置。splitNumber：设置分段型 visualMap 的分段数。pieces：设置分段型 visualMap 的各段范围和样式。fetColumnData = (dataX, dataY, unit) => { const chart = echarts.init(this.chartRef.current); const color = ['rgb(39, 42, 233)', 'rgb(15, 216, 206)'] const color1 = [ 'rgba(83, 246, 211, 0.57)', 'rgba(255, 165, 0, 0.57)', 'rgba(92, 222, 109, 0.57)', 'rgba(0, 255, 0, 0.57)', 'rgba(255, 0, 0, 0.57)', 'rgba(66, 168, 218, 0.57)', 'rgba(240, 106, 106, 0.57)', 'rgba(230, 122, 240, 0.57)', 'rgba(255, 255, 0, 0.57)', 'rgba(0, 0, 255, 0.57)' ] let pieces = []; for (let i = 1; i < dataY.length; i++) { let startColor = color1[0]; if (dataY[i] > dataY[i - 1]) { startColor = color1[1]; } pieces.push({ gte: i - 1, lte: i, color: startColor }); } var hasDataOption = { backgroundColor: 'rgba(20, 58, 110,0)', color: ['#3cefff'], grid: { top: '20%', bottom: '3%', left: '3%', right: '5%', containLabel: true, }, tooltip: { trigger: 'axis', formatter: function (params) { return ` ${params[0]["name"]} 月费用：${params[0]["value"]} `; }, extraCssText: 'background: #fff; border-radius: 0;box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);color: #333;', }, xAxis: [ { name: '月', nameTextStyle: { color: '#fff' }, type: 'category', data: dataX, axisTick: { alignWithLabel: true, }, nameTextStyle: { color: '#82b0ec', }, axisLine: { lineStyle: { color: '#82b0ec', }, }, axisLabel: { formatter: '{value}', textStyle: { color: '99b3d6', fontSize: '16' }, }, }, ], yAxis: [ { name: unit, nameTextStyle: { color: '#fff' }, type: 'value', axisLabel: { textStyle: { fontSize: '13', color: '#99b3d6', }, formatter: '{value}', }, splitLine: { show: true, lineStyle: { type: 'dashed', color: 'RGBA(3, 75, 97, 0.8)', }, }, axisLine: { show: false, }, }, ], visualMap: { type: 'piecewise', show: false, dimension: 0, seriesIndex: 0, pieces: pieces }, series: [ { data: dataY, name: '', // smooth: false, ty…

使用node+react+sse搭建AI聊天工具
type: item
date: 2025-02-15T14:07:27+08:00
description: 思路：使用nodejs搭建服务端，接收到客户端发送的问题后返回加载响应，然后调用ai接口，等待ai接口响应后将响应结果通过sse推送给客户端；使用react搭建页面，主要由问题输入框、发送按钮和问答信息展示模块步骤：创建node项目，安装相关依赖项npm init -y npm install express openai body-parser cors创建server.js文件，配置服务const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const OpenAI = require("openai"); const app = express(); const port = 3001; app.use(cors()); app.use(bodyParser.json()); // deepseek 配置 // const openai = new OpenAI({ // baseURL: 'https://api.deepseek.com/v1', // apiKey: '你的key' // }); // 存储 SSE 客户端连接 const clients = new Map(); // SSE 端点 app.get('/stream', (req, res) => { const clientId = Date.now().toString(); res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); // 存储连接并发送客户端 ID clients.set(clientId, res); res.write(`event: clientId\ndata: ${clientId}\n\n`); // 心跳保持连接 const heartbeat = setInterval(() => res.write(': heartbeat\n\n'), 30000); req.on('close', () => { clearInterval(heartbeat); clients.delete(clientId); }); }); // 消息处理端点 app.post('/chat', async (req, res) => { const { clientId, message } = req.body; // 立即返回等待提示 res.status(200).send('AI 正在思考中...'); // 模拟 AI 接口调用 setTimeout(async () => { try { const aiResponse = `AI回复：${Math.random() * 100}`; // 替换为真实 AI 响应 const clientRes = clients.get(clientId); if (clientRes) { clientRes.write(`event: response\n`); clientRes.write(`data: ${JSON.stringify({ text: aiResponse })}\n\n`); } } catch (error) { const clientRes = clients.get(clientId); clientRes?.write(`event: error\ndata: ${error.message}\n\n`); } }, 2000); }); app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); }); 搭建前端项目npx create-react-app ai-chat cd ai-chat npm install axios在src文件下，创建Chat.js文件，搭建简单聊天页面import React, { useState, useEffect } from 'react'; import axios from 'axios'; function Chat() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [clientId, setClientI…

highCharts 柱状图/折线图鼠标悬浮高亮
type: item
date: 2024-11-21T15:09:17+08:00
description: // 矩形透明蒙版和准星线只能同时存在一种显示矩形透明蒙版xAxis: { ... crosshair: true }, plotOptions: { column: { pointWidth: 30, pointPadding: 0.3, borderWidth: 0, states: { hover: { enabled: true, opacity: 0.7 // 设置悬停时的透明度 } } } }, series: [{ name: '销量', data: data, states: { hover: { enabled: true, color: 'rgba(255, 165, 0, 0.5)' // 悬停时的颜色，使用 rgba 实现透明效果 } } }],显示十字准星线xAxis: { ..., crosshair: { width: 2, color: 'red', // 交叉线颜色 dashStyle: 'ShortDash', // 交叉线样式 zIndex: 999 } }, yAxis: { ..., crosshair: { width: 2, color: 'red', // 交叉线颜色 dashStyle: 'ShortDash', // 交叉线样式 zIndex: 999 } },配置自动轮播let cur = 0 setInterval(() => { // 每次清除鼠标移入效果，防止用户鼠标移入导致样式错乱 if (cur > 0) { hChart.series[0].data[cur - 1].onMouseOut(); } hChart.series[0].data[cur].onMouseOver(); cur++ if(cur==data.length)cur=0 }, 2000);

highCharts组成与基础配置
type: item
date: 2024-11-07T10:00:28+08:00
description: 组成标题（Title）：图表标题，包含标题和副标题（subTitle）坐标轴（Axis）：包含x轴（xAxis）和y轴（yAxis）图例（Legend）：用不同形状、颜色、文字等 标示不同数据列，通过点击标示可以显示或隐藏该数据列版权信息（Credits）：包含链接的文字，通过指定credits.enabled=false即可不显示该信息数据提示框（Tooltip）：鼠标悬停在某点上时，以框的形式提示该点的数据工具菜单（Exporting）：通过引入exporting.js即可增加图表导出为常见文件功能数据列（Series）：一个或多个数据系列配置基础配置：chartchart: { type: 'line', // 图表类型，如 'line', 'column', 'bar', 'pie', 'scatter' 等 backgroundColor: null, // 背景色，可以设置为颜色（如 '#FFFFFF'）或 'none' borderColor: '#A0A0A0', // 图表边框颜色 borderWidth: 2, // 图表边框宽度 borderRadius: 0, // 边框圆角 height: 400, // 图表高度（像素） width: 600, // 图表宽度（像素） margin: [50, 50, 50, 50], // 图表的内边距 [上, 右, 下, 左] marginTop: null, // 上部边距 marginRight: null, // 右部边距 marginBottom: null, // 下部边距 marginLeft: null, // 左部边距 spacing: [10, 10, 15, 10], // 图表的外边距 [上, 右, 下, 左] spacingTop: null, // 顶部外边距 spacingRight: null, // 右侧外边距 spacingBottom: null, // 底部外边距 spacingLeft: null, // 左侧外边距 plotBackgroundColor: null, // 绘图区的背景颜色 plotBorderColor: '#A0A0A0', // 绘图区的边框颜色 plotBorderWidth: 0, // 绘图区的边框宽度 plotShadow: false, // 是否为绘图区添加阴影 panning: false, // 是否允许平移图表 pinchType: 'x', // 手势缩放类型，'x', 'y', 'xy', 'none' selectionMarkerFill: 'rgba(69,114,167,0.25)', // 选择区域的填充颜色 events: { // 事件处理 load: function() {}, // 图表加载后触发的事件 click: function() {} // 图表点击时触发的事件 }, options3d: { // 配置3D效果 enabled: true, // 是否启用 3D，值为 true 或 false alpha: 0, // 视角的倾斜角度 beta: 15, // 视角的旋转角度 depth: 30, // 深度值，表示 3D 视觉效果的强度 frame: { back: { size: 1, color: 'rgba(0,0,0,0.05)' }, // 后面，薄且透明的黑色 front: { size: 1, color: 'rgba(0,0,0,0.1)' }, // 前面，稍厚的黑色 right: { size: 1, color: 'rgba(0,0,0,0.1)' }, // 右侧，稍厚的黑色 left: { size: 1, color: 'rgba(0,0,0,0.1)' }, // 左侧，稍厚的黑色 bottom: { size: 1, color: 'rgba(0,0,0,0.1)' }, // 底面，稍厚的黑色 } } }标题：Title/subtitletitle: { // title，subtitle text: '默认标题', // 图表的主标题文本 align: 'center', // 标题的对齐方式，可以是 'left'、'center' 或 'right' verticalAlign: 'top', // 标题的垂直对齐方式，可以是 'top'、'middle' 或 'bottom' x: 0, // 标题的水平偏移，单位为像素 y: 0, // 标题的垂直偏移，单位为像素 style: { // 标题的CSS样式 color: '#333333', // 文字颜色 fontSize: '18px', // 字…

Lodash库常用方法
type: item
date: 2024-10-17T15:39:04+08:00
description: 集合_.reduce()：对数组或集合进行归约操作。它从数组的第一个元素开始，依次对每个元素执行一个用户提供的函数，并将结果累积到一个累加器上，最终返回累加器的值_.reduce(collection, iteratee, [accumulator], [initAccum]) /** * collection: 要遍历的集合，可以是数组、对象等。 * iteratee: 用于每个元素的回调函数，可以接收四个参数：accumulator（累加器），value（当前元素），index（当前索引），collection（集合本身）。 * accumulator: （可选）初始值，如果未提供，则将集合的第一个元素作为初始值。 * initAccum: （可选）布尔值，指示是否将第一个元素作为初始值，默认为 false。 */ let sum = _.reduce([1, 2, 3, 4], (accumulator, value)=> { return accumulator + value; }, 0); console.log(sum); // 输出: 10_.groupBy()：将集合（数组或对象）的元素按特定条件分组。它会创建一个对象，其中每个键代表分组的条件，值为满足该条件的元素数组_.groupBy(collection, iteratee) /** * collection: 要进行分组的集合，可以是数组或对象。 * iteratee: 分组的条件，可以是属性名、函数或者字符串，如果是字符串，表示根据该属性的值进行分组。 */ const users = [ { 'user': 'Alice', 'age': 25, 'active': true }, { 'user': 'Bob', 'age': 30, 'active': false }, { 'user': 'Charlie', 'age': 25, 'active': true }, { 'user': 'David', 'age': 30, 'active': true } ]; // 按年龄分组 const groupedByAge = _.groupBy(users, 'age'); console.log(groupedByAge); /* 输出: { '25': [ { 'user': 'Alice', 'age': 25, 'active': true }, { 'user': 'Charlie', 'age': 25, 'active': true } ], '30': [ { 'user': 'Bob', 'age': 30, 'active': false }, { 'user': 'David', 'age': 30, 'active': true } ] } */_.countBy()：对集合中的元素进行计数。它会创建一个对象，其中每个键表示某个特定条件的结果，值为该条件对应的元素数量_.countBy(collection, iteratee) /** * collection: 要进行计数的集合，可以是数组或对象。 * iteratee: 计数的条件，可以是属性名、函数或者字符串。如果是字符串，表示根据该属性的值进行计数。 / const users = [ { 'user': 'Alice', 'age': 25, 'active': true }, { 'user': 'Bob', 'age': 30, 'active': false }, { 'user': 'Charlie', 'age': 25, 'active': true }, { 'user': 'David', 'age': 30, 'active': true } ]; // 按年龄计数 const countByAge = _.countBy(users, 'age'); console.log(countByAge); /* 输出: { '25': 2, '30': 2 } */_.filter() ：用于从集合（数组或对象）中筛选出符合指定条件的元素。它返回一个新数组，包含所有满足条件的元素_.filter(collection, predicate) /** * collection: 要进行过滤的集合，可以是数组或对象。 * predicate: 筛选条件，可以是函数、属性名或其他条件。如果是函数，它会对每个元素进行测试，返回 true 或 false 来决定该元素是否包含在结果中 / const users = [ { 'user': 'Alice', 'age': 25, 'active': true }, { 'user': 'Bob', 'age…

ES6常用方法（数组）
type: item
date: 2024-10-14T19:15:27+08:00
description: 改变原数组的方法push：向数组末尾添加一个或多个元素。pop：从数组末尾删除一个元素。shift：从数组开头删除一个元素。unshift：向数组开头添加一个或多个元素。splice：可以在任意位置添加或删除元素。reverse：反转数组中元素的顺序。sort：对数组的元素进行排序。fill：用指定的值填充数组的某部分。不改变原数组的方法slice：返回数组的某个部分，生成一个新数组。concat：合并两个或多个数组，返回一个新数组。join：将数组的所有元素连接成一个字符串。map：对数组的每个元素执行给定的函数，返回一个新数组。forEach：对数组的每个元素执行给定的函数，没有返回值。reduce：对数组的每个元素执行给定的函数，最终计算出一个值。filter：筛选数组中的元素，返回满足条件的元素的新数组。find：返回数组中第一个满足条件的元素。includes：判断数组是否包含某个元素。some：判断数组中至少有一个元素满足条件。every：判断数组中的所有元素是否都满足条件。push()：向数组末尾添加一个或多个元素，返回新数组的长度const arr = [1, 2, 3]; arr.push(4); console.log(arr); // [1, 2, 3, 4]pop()：移除数组末尾的元素，并返回该元素const arr = [1, 2, 3]; const lastElement = arr.pop(); console.log(lastElement); // 3 console.log(arr); // [1, 2]shift()：移除数组开头的元素，并返回该元素const arr = [1, 2, 3]; const firstElement = arr.shift(); console.log(firstElement); // 1 console.log(arr); // [2, 3]unshift()：向数组开头添加一个或多个元素，返回新数组的长度const arr = [2, 3]; arr.unshift(1); console.log(arr); // [1, 2, 3]splice()：从数组中添加或删除元素，返回被删除的元素。可以添加多个元素到指定位置const arr = [1, 2, 3, 4]; const removed = arr.splice(1, 2, 5, 6); // 从索引1开始删除2个元素，并插入5和6 console.log(arr); // [1, 5, 6, 4] console.log(removed); // [2, 3]slice()：返回数组的一个片段，复制数组的一部分，并返回新数组const arr = [1, 2, 3, 4]; const sliced = arr.slice(1, 3); // 从索引1到索引3（不包括3） console.log(sliced); // [2, 3]concat()：用于合并两个或多个数组，返回一个新数组const arr1 = [1, 2]; const arr2 = [3, 4]; const combined = arr1.concat(arr2); console.log(combined); // [1, 2, 3, 4]join()：将数组的所有元素连接成一个字符串，返回连接后的字符串const arr = ['Hello', 'World']; const str = arr.join(' '); // 用空格连接 console.log(str); // "Hello World"reverse()：反转数组中的元素顺序const arr = [1, 2, 3]; arr.reverse(); console.log(arr); // [3, 2, 1]sort()：对数组元素进行排序const arr = [3, 1, 4, 2]; arr.sort(); // 默认按字符排序 console.log(arr); // [1, 2, 3, 4] arr.sort((a,b)=>b-a); // 逆序排序 console.log(arr); // [4, 3, 2, 1]map()：用于对数组中的每个元素执行一个函数，并返回一个新数组const numbers = [1, 2, 3]; const doubled = numbers.map(num => num * 2); console.log(doubled); // [2, 4, 6]forEach()：遍历数组中的每个元素，并对每个元素执行一个指定的函数，不返回新数组const colors = ['red', 'green', …

js中Excel文件导入与导出
type: item
date: 2024-09-14T17:12:00+08:00
description: 文件导入（读取）// 组件 <Upload fileList={this.state.fileList} beforeUpload={this.fileBeforeUpload} accept=".xlsx">导入文件</Upload> // 上传组件中添加 beforeUpload 使用xlsx读取上传文件数据，写入数据库 fileBeforeUpload = file => { // xlsx 读取文件内容 const reader = new FileReader(); reader.onload = e => { try { const data = new Uint8Array(e.target.result); const workbook = XLSX.read(data, { type: 'array' }); const firstSheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[firstSheetName]; const rows = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); let newData = [] for (let i = 1; i < rows.length; i++) { newData.push({ id: this.state.tabData.length + 1, equipmentName: rows[i][0], manufacturer: rows[i][1], model: rows[i][2], mn: rows[i][3], status: rows[i][4], productionDate: rows[i][5], useYear: rows[i][6], }) } this.setState({ tabData: [...this.state.tabData, ...newData], fileList: [] }, () => { scriptUtil.showMessage("数据写入成功", 'success'); }); } catch (error) { scriptUtil.showMessage("数据写入失败，请检查文件格式或内容", 'error'); } }; reader.onerror = () => { console.error("文件读取失败"); scriptUtil.showMessage("文件读取失败，请检查文件是否正确", 'error'); }; reader.readAsArrayBuffer(file); return false; }文件导出一、二维数组格式数据导出：aoa_to_sheet[['姓名', '年龄', '城市'],['张三', 28, '北京'],['李四', 34, '上海'],['王五', 22, '广州']]const data = [ ['姓名', '年龄', '城市'], ['张三', 28, '北京'], ['李四', 32, '上海'], ['王五', 25, '广州'] ]; const workbook = XLSX.utils.book_new(); const worksheet = XLSX.utils.aoa_to_sheet(data); XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); XLSX.writeFile(workbook, 'output.xlsx');二、对象数组格式数据导出：json_to_sheet[{ 姓名: '张三', 年龄: 28, 城市: '北京' },{ 姓名: '李四', 年龄: 34, 城市: '上海' },{ 姓名: '王五', 年龄: 22, 城市: '广州' }]const data = [ { 姓名: '张三', 年龄: 28, 城市: '北京' }, { 姓名: '李四', 年龄: 32, 城市: '上海' }, { 姓名: '王五', 年龄: 25, 城市: '广州' } ]; const workbook = XLSX.utils.book_new(); const worksheet = XLSX.utils.json_to_sheet(data); XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); XLSX.writeFile(workbook, 'output.xlsx');三、table表格直接导出：table_to_sheet页…

openlayers绘制图形
type: item
date: 2024-08-20T08:29:10+08:00
description: 使用技术：vue3.2+openlayers8.2.0实现功能：​ 点击绘制图形按钮，从缓存中获取图形绘制到地图上（实际项目中可替换成请求接口获取图形数据绘制到地图）​ 点击多边形、圆形、三角形...后可在地图上绘制对应图形（实际项目中可替换成下拉框+数字input框），绘制完的图形默认打开编辑绘制​ 点击清除绘制按钮，会将地图矢量绘制层矢量数据源全部清除​ 点击关闭绘制按钮，如果当前处于编辑状态，则关闭编辑状态​ 点击编辑绘制按钮，可以选择当前地图上显示的图形，然后编辑图形对象 <el-button class="btn-search" @click="drawSketch" type="success">绘制图形</el-button> <el-button class="btn-search" @click="handleAddInteraction('Polygon')" type="primary">多边形</el-button> <el-button class="btn-search" @click="handleAddInteraction('Circle')" type="primary">圆形</el-button> <el-button class="btn-search" @click="handleAddInteraction('LineString')" type="primary">线条</el-button> <el-button class="btn-search" @click="handleAddInteraction('Circle', 3)" type="primary">三角形</el-button> <el-button class="btn-search" @click="handleAddInteraction('Circle', 6)" type="primary">六边形</el-button> <el-button class="btn-search" @click="clearDraw" type="danger">清除绘制</el-button> <el-button class="btn-search" @click="closeDraw" v-show="isOpenModify == 1" type="danger">关闭绘制</el-button> <el-button class="btn-search" @click="openDraw" v-show="isOpenModify == 0" type="danger">编辑绘制</el-button>