<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>曦的博客</title><link>http://xiblogs.top:1111/</link><description>哈喽！哈喽！</description><item><title>前端使用xlsx模板导出表格</title><link>http://xiblogs.top:1111/?id=71</link><description>&lt;h1 id=&quot;h1-u524Du8A00&quot;&gt;&lt;a name=&quot;前言&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;前言&lt;/h1&gt;&lt;p&gt;前端导出表格有很多种方案，但是表格样式一旦复杂了，那么就得用代码写excel的样式，还是比较麻烦的。每次样式不一样，就得重新写，这时使用表格模板的优势就体现出来了，想导出不同样式的表格直接修改表格模板即可。&lt;/p&gt;
&lt;h1 id=&quot;h1-u65B9u6848&quot;&gt;&lt;a name=&quot;方案&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;方案&lt;/h1&gt;&lt;p&gt;我找了两种方案：&lt;br&gt;1、使用xlsx-template，利用模板语法在xlsx中占位填充后编辑导出。&lt;br&gt;2、使用exceljs，读取模板后，利用行列坐标定位编辑后导出。&lt;br&gt;两种我都尝试过，第一种方案类似我这篇文章(&lt;a href=&quot;http://xiblogs.top:1111/?id=27&quot;&gt;http://xiblogs.top:1111/?id=27&lt;/a&gt;) 中使用的docxtemplater，只不过是docx换成了xlsx，但xlsx-template在浏览器端的兼容不如docxtemplater那么好，你得处理fs、path之类的问题，当然也有老哥(&lt;a href=&quot;https://www.jianshu.com/p/85c844d96cfb&quot;&gt;https://www.jianshu.com/p/85c844d96cfb&lt;/a&gt;) 通过改项目配置的方式解决了。还是比较麻烦的而且老项目不一定适用，所以我使用了第二种方案。&lt;/p&gt;
&lt;h1 id=&quot;h1-u6B65u9AA4&quot;&gt;&lt;a name=&quot;步骤&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;步骤&lt;/h1&gt;&lt;p&gt;1、安装exceljs与file-saver&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm i exceljs
npm i file-saver&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;2、xlsx模板放在项目的public目录下。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2024/09/202409041651317950109.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2024/09/202409041652344272161.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;3、使用fetch的方式读取public下的xlsx模板。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let response = await fetch(&amp;#39;./static/xlsx/t1.xlsx&amp;#39;); //读取文件&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;4、将读取的数据转换为buffer再使用exceljs的workbook.xlsx.load加载数据。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let data = await response.arrayBuffer(); //转为二进制
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(data); //读取buffer
const worksheet = workbook.getWorksheet(1); //读取第一张表&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;5、利用exceljs的worksheet.getCell()给指定单元格赋值，getCell参数为行列，如修改第一行第一列数据为test。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;worksheet.getCell(&amp;#39;A1&amp;#39;).value = &amp;#39;test&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;6、使用exceljs的writeBuffer()读取表格为buffer后再使用file-saver的saveAs下载。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await workbook.xlsx.writeBuffer().then(async (buffer) =&amp;gt; {
    let blob = new Blob([buffer], { type: &amp;#39;application/octet-stream&amp;#39; });
    await saveAs(blob, &amp;#39;exportExcel.xlsx&amp;#39;);
    this.loading = false;
});&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;完整方法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//templateExportXlsx.js
import * as ExcelJS from &amp;#39;exceljs&amp;#39;;
import { saveAs } from &amp;#39;file-saver&amp;#39;;

//使用exceljs的getCell()根据行列坐标修改表格数据
//根据模板导出xlsx，tempPath为模板路径，dataList为数据，exportName为导出文件名
//templateExportXlsx(&amp;#39;./static/xlsx/t1.xlsx&amp;#39;, [{ G25: &amp;#39;ggg&amp;#39; }, { H25: &amp;#39;hhh&amp;#39; }], &amp;#39;1.xlsx&amp;#39;)
export async function templateExportXlsx(tempPath, dataList, exportName) {
  let response = await fetch(tempPath); //读取文件
  let data = await response.arrayBuffer(); //转为二进制
  let workbook = new ExcelJS.Workbook();
  await workbook.xlsx.load(data); //读取buffer
  let worksheet = workbook.getWorksheet(1); //读取第一张表
  for (let i = 0; i &amp;lt; dataList.length; i++) {
    let key = Object.keys(dataList[i])[0];
    let val = Object.values(dataList[i])[0];
    // console.log(key, val);
    worksheet.getCell(key).value = val; //坐标定位更新数据
  }
  // worksheet.eachRow({ includeEmpty: true }, (row, rowNumber) =&amp;gt; {
  //   console.log(rowNumber, row.values);
  // });
  //下载
  await workbook.xlsx.writeBuffer().then(async (buffer) =&amp;gt; {
    let blob = new Blob([buffer], { type: &amp;#39;application/octet-stream&amp;#39; });
    saveAs(blob, exportName);
  });
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最后下载导出的表格如下：&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2024/09/202409041702103662864.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;h1-u7ED3u8BED&quot;&gt;&lt;a name=&quot;结语&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;结语&lt;/h1&gt;&lt;p&gt;使用过程中需要注意读取数据时的异步处理。&lt;/p&gt;
</description><pubDate>Wed, 04 Sep 2024 16:26:04 +0800</pubDate></item><item><title>python将资源打包进exe</title><link>http://xiblogs.top:1111/?id=66</link><description>&lt;h1 id=&quot;h1-u524Du8A00&quot;&gt;&lt;a name=&quot;前言&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;前言&lt;/h1&gt;&lt;p&gt;之前py打包的exe一直是不涉及图片等资源的，直到我引入图片后打包，再双击exe发现直接提示未找到资源。&lt;/p&gt;
&lt;h1 id=&quot;h1-u5206u6790&quot;&gt;&lt;a name=&quot;分析&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;分析&lt;/h1&gt;&lt;p&gt;我py代码中的图片引入使用的是项目相对路径，打包时pyinstaller只会引入py模块，这种路径引入的资源其实是不会打包进exe的。有人想的是可以直接将资源放到exe旁边，但这不是最优雅的方法，你发布exe还得给人家一堆相关资源文件，其实想要解决此类无法打包资源的问题其实只需要做两件事：1、修改spec打包配置。2、动态获取资源路径。&lt;/p&gt;
&lt;h1 id=&quot;h1-u6B65u9AA4&quot;&gt;&lt;a name=&quot;步骤&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;步骤&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;这是我的项目结构&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/12/202312121520186525670.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;图片资源在src下的img目录中，我在main.py中需要使用其中的图片。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/12/202312121524454532695.jpg&quot; alt=&quot;&quot;&gt;&lt;/li&gt;&lt;li&gt;在main.py中定义动态获取资源路径的函数&lt;pre&gt;&lt;code&gt;#获取资源路径
def getPath(relative_path):
  # 判断是否为frozen状态(即为打包运行状态)
  if getattr(sys, &amp;#39;frozen&amp;#39;, False):
      base_path = sys._MEIPASS
  else:
      base_path = os.path.abspath(&amp;quot;.&amp;quot;)
  return os.path.join(base_path, relative_path)# 返回真实路径&lt;/code&gt;&lt;/pre&gt;我们需要先获取项目当前状态，判断是否为frozen状态(即为打包运行状态)，是的话先获取     MEIPASS路径(MEIPASS为双击exe时系统分配的临时工作目录)，不是的话直接获取绝对路径，最后返回真实路径。&lt;/li&gt;&lt;li&gt;在需要获取路径的地方直接使用getPath即可，比如我要修改exe窗体图标，这样即可：&lt;pre&gt;&lt;code&gt;root.iconbitmap(getPath(os.path.join(&amp;quot;src&amp;quot;,&amp;quot;img/logo.ico&amp;quot;)))&lt;/code&gt;&lt;/pre&gt;我要在tk界面使用Label添加图片，这样即可：&lt;pre&gt;&lt;code&gt;imgImg = ImageTk.PhotoImage(Image.open(getPath(os.path.join(&amp;quot;src&amp;quot;,&amp;quot;img/img.png&amp;quot;))))
Label(imgFrame,image=imgImg).grid(column = 0,row = 0)&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;最后修改spec打包配置(不懂spec的看这儿:&lt;a href=&quot;http://xiblogs.top:1111/?id=57&quot;&gt;http://xiblogs.top:1111/?id=57&lt;/a&gt; )中Analysis的datas：&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/12/202312121536574193426.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;datas中第一个src表示你需要打包的资源目录，第二个src表示打包成exe后双击exe时释放资源在MEIPASS目录下的具体位置，这是我打包运行时系统释放资源的位置，你可以看到src就在这里，exe中的图片也加载进界面了：&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/12/202312121540102982022.jpg&quot; alt=&quot;&quot;&gt;&lt;h1 id=&quot;h1-u7ED3u8BED&quot;&gt;&lt;a name=&quot;结语&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;结语&lt;/h1&gt;值得注意的是该目录打开exe即生成,关闭exe即删除，不可在此长久保存文件。类似于我篇文章(&lt;a href=&quot;http://xiblogs.top:1111/?id=65&quot;&gt;http://xiblogs.top:1111/?id=65&lt;/a&gt; )修改exe图标使用base64转换ico的逻辑，当然，你现在可以直接使用MEIPASS来处理了。&lt;/li&gt;&lt;/ul&gt;
</description><pubDate>Tue, 12 Dec 2023 14:54:54 +0800</pubDate></item><item><title>修改python打包后的窗体图标、任务栏图标、exe图标</title><link>http://xiblogs.top:1111/?id=65</link><description>&lt;h1 id=&quot;h1-u524Du8A00&quot;&gt;&lt;a name=&quot;前言&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;前言&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;我python开发的GUI界面(图形用户界面)一直是tkinter，打包exe一直是Pyinstaller。但是打包出来的exe图标、状态栏图标、窗体左上角图标一直是默认的羽毛，我想自定义。&lt;h1 id=&quot;h1-u6548u679C&quot;&gt;&lt;a name=&quot;效果&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;效果&lt;/h1&gt;&lt;/li&gt;&lt;li&gt;最后使用base64创建临时ico解决了该问题&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/12/202312051022333255031.png&quot; alt=&quot;&quot;&gt;&lt;h1 id=&quot;h1-u6B65u9AA4&quot;&gt;&lt;a name=&quot;步骤&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;步骤&lt;/h1&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;创建icoToBase64.py，内容如下：&lt;/li&gt;&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import base64

# ico转base64
open_icon = open(&amp;quot;./img/logo.ico&amp;quot;, &amp;quot;rb&amp;quot;)
b64str = base64.b64encode(open_icon.read())  # 转换为base64编码
open_icon.close()
write_data = &amp;quot;imgBase64 = %s&amp;quot; % b64str
f = open(&amp;quot;./img/logo.py&amp;quot;, &amp;quot;w+&amp;quot;)
f.write(write_data)# 写入文件
f.close()&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;创建img目录，将准备好的logo.ico放入img目录中。&lt;/li&gt;&lt;li&gt;运行icoToBase64.py文件，将ico转换为base64,写入py文件，得到logo.py。&lt;/li&gt;&lt;li&gt;引入logo.py，使用base64创建临时logo，自定义窗体图标与任务栏图标。&lt;/li&gt;&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;from img.logo import imgBase64

# 创建临时logo
def createTempLogo():  # 处理图片
    tmp = open(&amp;quot;temp.ico&amp;quot;, &amp;quot;wb+&amp;quot;)  # 创建temp.ico临时文件
    tmp.write(base64.b64decode(imgBase64))  # 写入img的base64
    tmp.close()  # 关闭文件
...
createTempLogo()
root.wm_iconbitmap(&amp;quot;temp.ico&amp;quot;)# 使用wm_iconbitmap引入创建的ico
if os.path.exists(&amp;quot;temp.ico&amp;quot;):
   os.remove(&amp;quot;temp.ico&amp;quot;)# 创建logo后需删除临时logo
...&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;执行命令：pyi-makespec -F -w main.py。创建main.spec配置文件，自定义exe图标。&lt;/li&gt;&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# -*- mode: python ; coding: utf-8 -*-
# 打包命令：Pyinstaller main.spec

block_cipher = None

a = Analysis(
    [&amp;#39;main.py&amp;#39;],# 需要打包的文件
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name=&amp;#39;批量修改照片拍摄时间_v0.0.1&amp;#39;,# 打包后的名字
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=False,# 双击exe是否显示cmd窗口
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon=&amp;#39;./img/logo.ico&amp;#39;# 打包后的exe图标
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;一般使用Pyinstaller打包，是使用命令的，各种参数就加在命令里面，但其实还可以使用spec这种配置文件的打包方式，只需要运行Pyinstaller main.spec这个命令即可，无需在命令中添加参数。Analysis中的第一个列表就是需要打包的py文件，我这里是打包main.py文件(支持添加多个)。EXE中也可修改，其中比较常用的就是name与icon，分别用于自定义打包后的exe名称与exe图标。&lt;/p&gt;
&lt;h1 id=&quot;h1-u7ED3u8BED&quot;&gt;&lt;a name=&quot;结语&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;结语&lt;/h1&gt;&lt;p&gt;最后不管是直接运行还是打包exe后运行，会发现窗体图标、任务栏图标、exe图标都变成了自定义的图标。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/12/202312051022333255031.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
</description><pubDate>Mon, 04 Dec 2023 14:39:33 +0800</pubDate></item><item><title>pinia的使用</title><link>http://xiblogs.top:1111/?id=64</link><description>&lt;h1 id=&quot;h1-u524Du8A00&quot;&gt;&lt;a name=&quot;前言&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;前言&lt;/h1&gt;&lt;p&gt;最近新开了个项目，以前老项目都是vue2+vuex开发的，都说用vue3+pinia爽得多，那新项目就vue3+pinia吧。这里记录一下pinia的使用。&lt;/p&gt;
&lt;h1 id=&quot;h1-u4F7Fu7528u65B9u6CD5&quot;&gt;&lt;a name=&quot;使用方法&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;使用方法&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;安装pinia:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm i pinia&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;main.js中引入pinia:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//main.js
import { createApp } from &amp;#39;vue&amp;#39;;
import { createPinia } from &amp;#39;pinia&amp;#39;;
import App from &amp;#39;./App.vue&amp;#39;;
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.mount(&amp;#39;#app&amp;#39;)&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;创建store仓库：&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;在src下创建store目录，目录下创建index.js文件:&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/11/202311091521115853311.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;在index.js中利用pinia的defineStore方法创建仓库，一般使用只会用到defineStore的前两个参数(第三个参数可做持久化配置)，下面是setup组合式写法(你也可以改为选项式写法)。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;第一个参数为一个字符串，作为仓库的唯一id；第二个参数为一个方法，内部可定义需要存储的数据，定义完成后返回即可，你可以在此处定义多个数据，也可在defineStore之外多次使用defineStore创建多个仓库。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;定义的时候有几个取巧的办法：可以使用ref给数据添加响应式特性。只需定义set方法，无需定义get方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// /store/index.js
import { defineStore } from &amp;quot;pinia&amp;quot;;
import { ref } from &amp;quot;vue&amp;quot;;
export const piniaStore = defineStore(
  &amp;quot;piniaStore&amp;quot;,
  () =&amp;gt; {
      const pageData = ref(1);//页面数据
      const setPageData = () =&amp;gt; {
          pageData.value = pageData.value * 2;
      };//设置页面数据

      return {
          pageData,
          setPageData
      };
  },
);&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;页面使用&lt;/li&gt;&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;使用仓库前必须先引入仓库创建store。&lt;/li&gt;&lt;li&gt;获取仓库数据可借助计算属性computed直接取出。&lt;/li&gt;&lt;li&gt;修改仓库数据可直接调用返回的对应set方法即可，此处方法没有传参直接在仓库内部修改的数据，你也可以传参使用外部数据修改。&lt;/li&gt;&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
        &amp;lt;h1&amp;gt;{{ pageData }}&amp;lt;/h1&amp;gt;
        &amp;lt;button @click=&amp;quot;setData&amp;quot;&amp;gt;设置数据&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script setup&amp;gt;
import { computed } from &amp;#39;vue&amp;#39;;
import { piniaStore } from &amp;#39;@/store/index.js&amp;#39;;
let store = piniaStore(),//获取仓库
      pageData = computed(() =&amp;gt; {
        return store.pageData;//获取仓库中的数据
      });
const setData = () =&amp;gt; {
    store.setPageData();//设置仓库中的数据
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;点击前页面显示：&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/11/202311091553064048084.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;点击后页面显示：&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/11/202311091553094187498.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;到此处其实你就已经学会使用pinia了，比vuex简单多了吧？但我当前项目需要对数据进行持久化处理，不处理页面刷新之后数据全部会重置，无法保存状态。&lt;/li&gt;&lt;/ul&gt;
&lt;h1 id=&quot;h1-u6301u4E45u5316&quot;&gt;&lt;a name=&quot;持久化&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;持久化&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;其实可以直接使用sessionStorage或localstorage实现前端数据持久化。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;但数据一旦多了，你会发现代码里面全是setItem、getItem，会有大量的重复代码。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;有人说直接封装不久得了？就像这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//storeTools.js
//写入仓库
export const setStore = (key, value) =&amp;gt; {
  sessionStorage.setItem(key, JSON.stringify(value));
}
//读取仓库
export const getStore = (key) =&amp;gt; {
  return JSON.parse(sessionStorage.getItem(key));
}
//清除所有仓库
export const clearStore = () =&amp;gt; {
  sessionStorage.clear();
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;当然你也可以这样做，但在有轮子的情况下，我还是倾向于直接用轮子的，况且轮子提供的功能远比自己封装的方法多。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;下面是pinia的持久化步骤：&lt;/p&gt;
&lt;/li&gt;&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;安装插件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm i pinia-plugin-persistedstate&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;main.js引入插件:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createApp } from &amp;#39;vue&amp;#39;;
import { createPinia } from &amp;#39;pinia&amp;#39;;
import piniaPluginPersistedstate from &amp;#39;pinia-plugin-persistedstate&amp;#39;;
import App from &amp;#39;./App.vue&amp;#39;;
const app = createApp(App);
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);//pinia持久化
app.use(pinia);
app.mount(&amp;#39;#app&amp;#39;)&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;pinia配置插件，其实就是利用pinia中defineStore方法的第三个参数，直接传入一个对象persist，对象中enabled表示启用持久化，storage表示持久化方式(可选sessionStorage、localstorage)：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineStore } from &amp;quot;pinia&amp;quot;;
import { ref } from &amp;quot;vue&amp;quot;;
export const piniaStore = defineStore(
 &amp;quot;piniaStore&amp;quot;,
 () =&amp;gt; {
     const pageData = ref(1);//页面数据
     const setPageData = () =&amp;gt; {
         pageData.value = pageData.value * 2;
     };//设置页面数据

     return {
         pageData,
         setPageData
     };
 },
 {
     persist: {
         enabled: true,
         storage: sessionStorage,
     }
 }
);&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;现在再操作页面后，数据会保存在sessionStorage中，刷新数据不会重置丢失。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/11/202311091613538609612.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;h1 id=&quot;h1-u7ED3u8BED&quot;&gt;&lt;a name=&quot;结语&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;结语&lt;/h1&gt;&lt;p&gt;其实vuex也有自己的持久化方法，但我还是倾向于pinia，毕竟更简洁方便了。&lt;/p&gt;
</description><pubDate>Thu, 09 Nov 2023 14:41:22 +0800</pubDate></item><item><title>纯前端实现图片验证码</title><link>http://xiblogs.top:1111/?id=63</link><description>&lt;h1 id=&quot;h1-u524Du8A00&quot;&gt;&lt;a name=&quot;前言&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;前言&lt;/h1&gt;&lt;p&gt;之前业务系统中验证码一直是由后端返回base64与一个验证码的字符串来实现的，想了下，前端其实可以直接canvas实现，减轻服务器压力。&lt;/p&gt;
&lt;h1 id=&quot;h1-u5B9Eu73B0&quot;&gt;&lt;a name=&quot;实现&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;实现&lt;/h1&gt;&lt;p&gt;子组件，允许自定义图片尺寸(默认尺寸为100 * 40)与验证码刷新时间(默认时间为60秒)。同时暴露绘制验证码方法drawPic(),允许父组件直接调用(需要利用ref实现)，点击验证码也可手动刷新。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//VerifyCodeImg.vue
&amp;lt;!--验证码生成--&amp;gt;
&amp;lt;template&amp;gt;
    &amp;lt;canvas id=&amp;quot;canvasDom&amp;quot; :width=&amp;quot;props.canvasWidth&amp;quot; :height=&amp;quot;props.canvasHeight&amp;quot; @click=&amp;quot;drawPic&amp;quot;&amp;gt;&amp;lt;/canvas&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup&amp;gt;
import { onMounted, onBeforeUnmount, computed } from &amp;#39;vue&amp;#39;;
let props = defineProps({
    canvasWidth: { // 容器宽度
        type: Number,
        default: 100
    },
    canvasHeight: { // 容器高度
        type: Number,
        default: 40
    },
    refreshTime: {//验证码刷新间隔时间
        type: Number,
        default: 60
    }
}),
    emits = defineEmits([&amp;#39;getVerifyCodeStr&amp;#39;]),
    verifyCodeTimeId = null,//定时器id
    randomStr = &amp;#39;0123456789abcdefghijklmnopqrstuvwxyz&amp;#39;,// 所有随机字符串
    trueRefreshTime = computed(() =&amp;gt; {
        return props.refreshTime * 1000;
    }),
    verifyCodeStr = &amp;#39;&amp;#39;;//验证码字符串

onMounted(() =&amp;gt; {
    initVerifyCodeImg();
});

// 初始化
const initVerifyCodeImg = () =&amp;gt; {
    drawPic();
    verifyCodeTimeId = setInterval(() =&amp;gt; {
        drawPic();
    }, trueRefreshTime.value);
};

// 绘制验证码图片
const drawPic = () =&amp;gt; {
    randomCode();
    let canvas = document.getElementById(&amp;#39;canvasDom&amp;#39;)
    let ctx = canvas.getContext(&amp;#39;2d&amp;#39;)
    ctx.textBaseline = &amp;#39;bottom&amp;#39;
    // 绘制背景
    ctx.fillStyle = &amp;#39;#e6ecfd&amp;#39;
    ctx.fillRect(0, 0, props.canvasWidth, props.canvasHeight)
    // 绘制文字
    for (let i = 0; i &amp;lt; verifyCodeStr.length; i++) {
        drawText(ctx, verifyCodeStr[i], i)
    }
    drawLine(ctx)
    drawDot(ctx)
};

//4个随机字符
const randomCode = () =&amp;gt; {
    verifyCodeStr = &amp;#39;&amp;#39;
    for (let i = 0; i &amp;lt; 4; i++) {
        let txt = randomStr[randomNum(0, randomStr.length)];
        verifyCodeStr += txt;
    }
    emits(&amp;#39;getVerifyCodeStr&amp;#39;, verifyCodeStr);
};

// 随机数
const randomNum = (min, max) =&amp;gt; {
    return Math.floor(Math.random() * (max - min) + min)
};

// 随机色
const randomColor = (min, max) =&amp;gt; {
    let r = randomNum(min, max)
    let g = randomNum(min, max)
    let b = randomNum(min, max)
    return &amp;#39;rgb(&amp;#39; + r + &amp;#39;,&amp;#39; + g + &amp;#39;,&amp;#39; + b + &amp;#39;)&amp;#39;
};

// 绘制文字
const drawText = (ctx, txt, i) =&amp;gt; {
    let fontSizeMin = 30,// 字体最小值
        fontSizeMax = 40;// 字体最大值
    ctx.fillStyle = randomColor(50, 160) // 随机生成字体颜色
    ctx.font = randomNum(fontSizeMin, fontSizeMax) + &amp;#39;px SimHei&amp;#39; // 随机生成字体大小
    let x = (i + 1) * (props.canvasWidth / (verifyCodeStr.length + 1))
    let y = randomNum(fontSizeMax, props.canvasHeight - 5)
    var deg = randomNum(-30, 30)
    // 修改坐标原点和旋转角度
    ctx.translate(x, y)
    ctx.rotate(deg * Math.PI / 180)
    ctx.fillText(txt, 0, 0)
    // 恢复坐标原点和旋转角度
    ctx.rotate(-deg * Math.PI / 180)
    ctx.translate(-x, -y)
};

// 绘制干扰线
const drawLine = (ctx) =&amp;gt; {
    for (let i = 0; i &amp;lt; 4; i++) {
        ctx.strokeStyle = randomColor(100, 200)
        ctx.beginPath()
        ctx.moveTo(randomNum(0, props.canvasWidth), randomNum(0, props.canvasHeight))
        ctx.lineTo(randomNum(0, props.canvasWidth), randomNum(0, props.canvasHeight))
        ctx.stroke()
    }
};

// 绘制干扰点
const drawDot = (ctx) =&amp;gt; {
    // 绘制干扰点
    for (let i = 0; i &amp;lt; 30; i++) {
        ctx.fillStyle = randomColor(0, 255)
        ctx.beginPath()
        ctx.arc(randomNum(0, props.canvasWidth), randomNum(0, props.canvasHeight), 1, 0, 2 * Math.PI)
        ctx.fill()
    }
};

//暴露绘制图片方法供父组件使用
defineExpose({
    drawPic
});

onBeforeUnmount(() =&amp;gt; {
    clearInterval(verifyCodeTimeId);//页面销毁前，需要清除定时器
});

&amp;lt;/script&amp;gt;

&amp;lt;style scoped lang=&amp;#39;scss&amp;#39;&amp;gt;
#canvasDom {
    cursor: pointer;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;父组件使用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;VerifyCodeImg :refreshTime=&amp;quot;1&amp;quot; ref=&amp;quot;verifyCodeImgRef&amp;quot; @getVerifyCodeStr=&amp;quot;getVerifyCodeStr&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;h1-u7ED3u8BED&quot;&gt;&lt;a name=&quot;结语&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;结语&lt;/h1&gt;&lt;p&gt;上面是vue3的实现，你也可以改写为vue2的。&lt;/p&gt;
</description><pubDate>Wed, 01 Nov 2023 22:03:39 +0800</pubDate></item><item><title>js玩儿爬虫</title><link>http://xiblogs.top:1111/?id=60</link><description>&lt;h1 id=&quot;h1-u524Du8A00&quot;&gt;&lt;a name=&quot;前言&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;前言&lt;/h1&gt;&lt;p&gt;提到爬虫可能大多都会想到python，其实爬虫的实现并不限制任何语言。&lt;br&gt;下面我们就使用js来实现，后端为express，前端为vue3。&lt;/p&gt;
&lt;h1 id=&quot;h1-u5B9Eu73B0u529Fu80FD&quot;&gt;&lt;a name=&quot;实现功能&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;实现功能&lt;/h1&gt;&lt;p&gt;话不多说，先看结果：&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308091729346144935.jpg&quot; alt=&quot;&quot;&gt;&lt;br&gt;这是项目链接：&lt;a href=&quot;https://gitee.com/xi1213/worm&quot;&gt;https://gitee.com/xi1213/worm&lt;/a&gt;&lt;br&gt;项目用到的库有：vue、axios、cheerio、cron、express、node-dev&lt;br&gt;计划功能有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;微博热榜爬取。&lt;/li&gt;&lt;li&gt;知乎热榜爬取。&lt;/li&gt;&lt;li&gt;B站排行榜爬取。&lt;/li&gt;&lt;li&gt;三个壁纸网站爬取。&lt;/li&gt;&lt;li&gt;随机生成人脸。&lt;/li&gt;&lt;li&gt;爬取指定页面所有图片。&lt;/li&gt;&lt;li&gt;删除爬取的数据。&lt;/li&gt;&lt;li&gt;定时任务(开发中)。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;使用形式为：&lt;br&gt;双击打包出的exe(最好右键管理员运行，以防权限不足)。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101125329443876.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;双击exe后会弹出node后端启动的黑框。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101128293096134.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;自动在浏览器中打开操作界面(用户界面)。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308091729346144935.jpg&quot; alt=&quot;&quot;&gt;&lt;br&gt;爬取出的数据在exe同级目录下的exportData中。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101124549905782.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;h1-u5177u4F53u5B9Eu73B0&quot;&gt;&lt;a name=&quot;具体实现&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;具体实现&lt;/h1&gt;&lt;h3 id=&quot;h3-u5FAEu535Au70EDu699C&quot;&gt;&lt;a name=&quot;微博热榜&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;微博热榜&lt;/h3&gt;&lt;p&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101435057371883.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;打开微博官网，f12分析后台请求，会发现它的热榜数据列表在请求接口：&lt;a href=&quot;https://weibo.com/ajax/side/hotSearch&quot;&gt;https://weibo.com/ajax/side/hotSearch&lt;/a&gt; 中，无参。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101346575481652.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;在接口列表realtime中根据页面信息，推测其字段含义：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;word为关键字，&lt;/li&gt;&lt;li&gt;category为类别，&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://s.weibo.com/weibo?q=%23&quot;&gt;https://s.weibo.com/weibo?q=%23&lt;/a&gt; + word为链接，&lt;/li&gt;&lt;li&gt;num为热度。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;既然数据是现成的，那我们直接使用axios即可。&lt;br&gt;获取到数据列表后将其遍历拼接成指定格式的字符串，写入txt，下面是具体方法：&lt;br&gt;weibo.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let axios = require(&amp;#39;axios&amp;#39;),
    writeTxt = require(&amp;quot;../utils/writeTxt&amp;quot;),
    { addMsg } = require(&amp;quot;../store/index&amp;quot;);

//抓取weibo
async function weiboWorm(dir, time) {
    let com = &amp;#39;https://weibo.com&amp;#39;;
    addMsg(`${com} 爬取中...`)
    let res = await axios.get(`${com}/ajax/side/hotSearch`);
    //拼接数据
    let strData = `微博热榜\r\n爬取时间：${time}\r\n`
    await res.data.data.realtime.forEach((l, index) =&amp;gt; {
        strData = strData +
            &amp;#39;\r\n序号：&amp;#39; + (index + 1) + &amp;#39;\r\n&amp;#39; +
            &amp;#39;关键字：&amp;#39; + l.word + &amp;#39;\r\n&amp;#39; +
            &amp;#39;类别：&amp;#39; + l.category + &amp;#39;\r\n&amp;#39; +
            &amp;#39;链接：https://s.weibo.com/weibo?q=%23&amp;#39; + l.word.replace(/\s+/g, &amp;quot;&amp;quot;) + &amp;#39;\r\n&amp;#39; +
            &amp;#39;热度：&amp;#39; + l.num + &amp;#39;\r\n&amp;#39; +
            &amp;#39;\r\n\r\n==================================================================================================================&amp;#39;
    })
    writeTxt(`${dir}/weibo_${Date.now()}.txt`, strData);//写入txt
    addMsg(&amp;#39;$END&amp;#39;);
}

module.exports = weiboWorm;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;writeTxt.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let fs = require(&amp;#39;fs&amp;#39;);

//写入txt
function writeTxt(filePath, data) {
    fs.writeFile(filePath, data, (err) =&amp;gt; {
    })
}

module.exports = writeTxt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;需要注意的是在windows中换行使用的是\r\n，在链接中需要去掉空格。&lt;/p&gt;
&lt;h3 id=&quot;h3-u77E5u4E4Eu70EDu699C&quot;&gt;&lt;a name=&quot;知乎热榜&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;知乎热榜&lt;/h3&gt;&lt;p&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101433307396697.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;打开知乎官网，会发现它是需要登录的。&lt;br&gt;f12后点击左上角第二个按钮，在浏览器中切换为手机布局，刷新后即可不登录显示文章信息。&lt;br&gt;分析请求发现文章数据在请求接口：&lt;a href=&quot;https://www.zhihu.com/api/v3/explore/guest/feeds&quot;&gt;https://www.zhihu.com/api/v3/explore/guest/feeds&lt;/a&gt; 中，参数为limit，限制文章数。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101416295466126.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;根据页面信息推测接口字段含义：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;target.question.title为问题标题，&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/&quot;&gt;https://www.zhihu.com/question/&lt;/a&gt; + target.question.id为问题链接，&lt;/li&gt;&lt;li&gt;target.question.answer_count为问答数，&lt;/li&gt;&lt;li&gt;target.question.author.name为提问的用户名，&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/org/&quot;&gt;https://www.zhihu.com/org/&lt;/a&gt; + target.question.author.url_token为提问的用户链接，&lt;/li&gt;&lt;li&gt;target.content为高赞回答的内容。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;需要注意的是高赞回答的内容中有html的标签，需要自己str.replace(/xxx/g,’’)去除。&lt;br&gt;数据的具体获取方法同微博类似。&lt;/p&gt;
&lt;h3 id=&quot;h3-b-&quot;&gt;&lt;a name=&quot;B站排行榜&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;B站排行榜&lt;/h3&gt;&lt;p&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101440269900877.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;打开B站官网，找到排行榜，f12后发现数据在接口请求：&lt;a href=&quot;https://api.bilibili.com/x/web-interface/ranking/v2&quot;&gt;https://api.bilibili.com/x/web-interface/ranking/v2&lt;/a&gt; 中，无参。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101432378047448.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;推测接口字段含义：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;title为视频标题，&lt;/li&gt;&lt;li&gt;short_link_v2为视频短链，&lt;/li&gt;&lt;li&gt;stat.view为视频浏览量，&lt;/li&gt;&lt;li&gt;desc为视频描述，&lt;/li&gt;&lt;li&gt;pic为视频封面，&lt;/li&gt;&lt;li&gt;owner.name为视频作者，&lt;/li&gt;&lt;li&gt;pub_location为发布地址，&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://space.bilibili.com/&quot;&gt;https://space.bilibili.com/&lt;/a&gt; + owner.mid为作者链接。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;数据的具体获取方法同微博类似。&lt;/p&gt;
&lt;h3 id=&quot;h3-u58C1u7EB8u7F51u7AD9u722Cu53D6&quot;&gt;&lt;a name=&quot;壁纸网站爬取&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;壁纸网站爬取&lt;/h3&gt;&lt;p&gt;项目使用了下面三个网站作为例子：&lt;br&gt;&lt;a href=&quot;http://www.netbian.com/&quot;&gt;http://www.netbian.com/&lt;/a&gt;&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101454273505182.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;&lt;a href=&quot;https://www.logosc.cn/so/&quot;&gt;https://www.logosc.cn/so/&lt;/a&gt;&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101454441508834.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;&lt;a href=&quot;https://bing.ioliu.cn/&quot;&gt;https://bing.ioliu.cn/&lt;/a&gt;&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101454569064579.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;具体思路如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用axios请求页面。&lt;/li&gt;&lt;li&gt;将请求到的数据使用cheerio.load解析(cheerio为node中的jq,语法同jq)。&lt;/li&gt;&lt;li&gt;f12分析需要的数据在什么元素中，使用cheerio获取到该目标元素。&lt;/li&gt;&lt;li&gt;获取到元素中img的src内容。&lt;/li&gt;&lt;li&gt;axios请求src(需要encodeURI转码，防止中文报错)，记得设置responseType为stream。&lt;/li&gt;&lt;li&gt;有分页的需要考虑到动态改变url中的页码。&lt;/li&gt;&lt;li&gt;需要保证下载顺序，一张图片下载完成后才能下载另一张，否则下载量过大会有下载失败的可能，使用for配合async与await即可。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;具体实现代码如下：&lt;br&gt;bian.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let fs = require(&amp;#39;fs&amp;#39;),
    cheerio = require(&amp;#39;cheerio&amp;#39;),
    axios = require(&amp;#39;axios&amp;#39;),
    downloadImg = require(&amp;quot;../utils/downloadImg.js&amp;quot;),
    { addMsg } = require(&amp;quot;../store/index&amp;quot;);

//抓取彼岸图片
async function bianWorm(dir, pageNum) {
    let page = pageNum,//抓取页数
        pagUrlList = [],
        imgList = [],
        index = 0,
        com = &amp;#39;https://pic.netbian.com&amp;#39;;
    addMsg(`${com} 爬取中...`)
    for (let i = 1; i &amp;lt;= page; i++) {
        let url = i == 1 ? `${com}/index.html` : `${com}/index_${i}.html`;
        let res = await axios.get(url);
        let $ = cheerio.load(res.data);//解析页面
        let slistEl = $(&amp;#39;.slist&amp;#39;);//找到元素列表
        slistEl.find(&amp;#39;a&amp;#39;).each(async (j, e) =&amp;gt; {
            pagUrlList.push(`${com}${$(e).attr(&amp;#39;href&amp;#39;)}`);//获取到页面url列表
        })
    }
    pagUrlList.forEach(async (p, i) =&amp;gt; {
        let pRes = await axios.get(p);
        let p$ = cheerio.load(pRes.data);//解析页面
        let imgEl = p$(&amp;#39;.photo-pic&amp;#39;).find(&amp;#39;img&amp;#39;);//找到元素列表
        let imgUrl = `${com}${imgEl.attr(&amp;#39;src&amp;#39;)}`;//获取图片url
        imgList.push(imgUrl);
        index++;
        //循环的次数等于列表长度时获取图片
        if (index == pagUrlList.length) {
            let dirStr = `${dir}/bian_${Date.now()}`;
            fs.mkdir(dirStr, (err) =&amp;gt; { })
            downloadImg(imgList, dirStr);//下载图片
        }
    })
}

module.exports = bianWorm;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;downloadImg.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let fs = require(&amp;#39;fs&amp;#39;),
    axios = require(&amp;#39;axios&amp;#39;),
    { addMsg } = require(&amp;quot;../store/index&amp;quot;);

//下载图片
async function downloadImg(list, path) {
    if (list.length == 0) {
        addMsg(&amp;#39;$END&amp;#39;);
        return;
    }
    // console.log(list.length);
    for (let i = 0; i &amp;lt; list.length; i++) {
        let url = encodeURI(list[i]);//转码,防止url中文报错
        try {
            //计算下载的百分比
            let percent = ((i + 1) / list.length * 100).toFixed(2);
            let msgStr = `${percent}% 爬取中... ${url}`;
            addMsg(msgStr);
            if (i == list.length - 1) {
                msgStr = `图片爬取完成，共${list.length}项。`
                addMsg(msgStr);
                addMsg(&amp;#39;$END&amp;#39;);
            }
            let typeList = [&amp;#39;jpg&amp;#39;, &amp;#39;png&amp;#39;, &amp;#39;jpeg&amp;#39;, &amp;#39;gif&amp;#39;, &amp;#39;webp&amp;#39;, &amp;#39;svg&amp;#39;, &amp;#39;psd&amp;#39;, &amp;#39;bmp&amp;#39;, &amp;#39;tif&amp;#39;, &amp;#39;tiff&amp;#39;, &amp;#39;ico&amp;#39;];
            let type = typeList.find((item) =&amp;gt; {
                return url.includes(item);
            });//获取图片类型
            (type == undefined) &amp;amp;&amp;amp; (type = &amp;#39;jpg&amp;#39;);//判断type是否为undefined
            const imgPath = `${path}/${i + 1}.${type}`;//拼接本地路径
            const writer = fs.createWriteStream(imgPath);
            const response = await axios
                .get(url, { responseType: &amp;#39;stream&amp;#39;, timeout: 5000 }).catch(err =&amp;gt; { });
            response.data.pipe(writer);
            await new Promise((resolve, reject) =&amp;gt; {
                writer.on(&amp;#39;finish&amp;#39;, resolve);
                writer.on(&amp;#39;error&amp;#39;, reject);
            });

        } catch (error) { }
    }
}
module.exports = downloadImg;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;值得注意的是需要保证准确获取图片资源的不同后缀。&lt;/p&gt;
&lt;h3 id=&quot;h3-u968Fu673Au751Fu6210u4EBAu8138&quot;&gt;&lt;a name=&quot;随机生成人脸&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;随机生成人脸&lt;/h3&gt;&lt;p&gt;这里可没有人脸算法之类的，调用的是&lt;a href=&quot;https://thispersondoesnotexist.com/&quot;&gt;https://thispersondoesnotexist.com/&lt;/a&gt; 站点的接口，此接口每次刷新可生成不同人脸。&lt;br&gt;axios请求接口后，使用fs的createWriteStream创建可写流，将数据流写入文件中，下面是具体实现方法：&lt;br&gt;randomFace.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let fs = require(&amp;#39;fs&amp;#39;),
    axios = require(&amp;#39;axios&amp;#39;),
    { addMsg } = require(&amp;quot;../store/index&amp;quot;);

//生成随机人脸
async function randomFace(dir, faceNum) {
    let com = &amp;#39;https://thispersondoesnotexist.com&amp;#39;;
    addMsg(`人脸生成中...`);
    let dirStr = `${dir}/randomFace_${Date.now()}`;
    fs.mkdir(dirStr, (err) =&amp;gt; { })
    for (let i = 1; i &amp;lt;= faceNum; i++) {
        await axios.get(com, { responseType: &amp;#39;stream&amp;#39; })
            .then((resp) =&amp;gt; {
                const writer = fs.createWriteStream(`${dirStr}/${i}.jpg`);// 创建可写流
                resp.data.pipe(writer);// 将响应的数据流写入文件
                writer.on(&amp;#39;finish&amp;#39;, () =&amp;gt; {
                    //计算下载的百分比
                    let percent = ((i) / faceNum * 100).toFixed(2);
                    let msgStr = `${percent}% 人脸生成中... ${dirStr}/${i}.jpg`;
                    addMsg(msgStr);
                    if (i == faceNum) {
                        msgStr = `人脸生成完成，共${faceNum}张。`
                        addMsg(msgStr);
                        addMsg(&amp;#39;$END&amp;#39;);
                    }
                });
                writer.on(&amp;#39;error&amp;#39;, (err) =&amp;gt; { addMsg(&amp;#39;$END&amp;#39;); });
            })
    }
}

module.exports = randomFace;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;h3-u722Cu53D6u6307u5B9Au9875u9762u6240u6709u56FEu7247&quot;&gt;&lt;a name=&quot;爬取指定页面所有图片&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;爬取指定页面所有图片&lt;/h3&gt;&lt;p&gt;思路同上面获取壁纸类似，只不过这次是获取页面所有的img标签的src。&lt;br&gt;由于范围扩大到所有页面了，所以需要考虑的情况就会比较多。&lt;br&gt;有的src中是没有http或者https的，有的src使用的是相对路径，有的可能有中文字符，还有很多我没考虑到的情况。&lt;br&gt;所以并不能爬取任意页面的所有图片，比如页面加载过慢，或者用了懒加载、防盗链等技术。&lt;br&gt;下面是我实现的方法：&lt;br&gt;allWebImg.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let fs = require(&amp;#39;fs&amp;#39;),
    cheerio = require(&amp;#39;cheerio&amp;#39;),
    axios = require(&amp;#39;axios&amp;#39;),
    downloadImg = require(&amp;quot;../utils/downloadImg.js&amp;quot;),
    { addMsg } = require(&amp;quot;../store/index&amp;quot;);

//网站所有图片
async function allWebImgWorm(dir, com) {
    let imgList = [];
    addMsg(`${com} 爬取中...`);
    let res = await axios.get(com).catch(err =&amp;gt; { });
    if (!res) {
        addMsg(&amp;#39;$END&amp;#39;);
        return
    }
    let $ = cheerio.load(res.data);//解析页面
    //获取到页面所有图片标签组成的列表
    $(&amp;#39;img&amp;#39;).each(async (j, e) =&amp;gt; {
        let imgUrl = e.attribs.src;//获取图片链接
        if (imgUrl) {
            !imgUrl.includes(&amp;#39;https&amp;#39;) &amp;amp;&amp;amp; (imgUrl = `https:${imgUrl}`);//判断是否有https,没有则加上
            imgList.push(imgUrl);
        }
    })
    let dirStr = `${dir}/allWebImg_${Date.now()}`;
    fs.mkdir(dirStr, (err) =&amp;gt; { })
    downloadImg(imgList, dirStr);//下载图片
}

module.exports = allWebImgWorm;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;h3-u5220u9664u722Cu53D6u7684u6570u636E&quot;&gt;&lt;a name=&quot;删除爬取的数据&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;删除爬取的数据&lt;/h3&gt;&lt;p&gt;使用fs.unlinkSync删除文件，fs.rmdirSync删除目录。&lt;br&gt;需要提前判断文件夹是否存在。&lt;br&gt;需要遍历文件，判断是否为文件。为文件则删除，否则递归遍历。&lt;br&gt;下面是我的方法：&lt;br&gt;deleteFiles.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let fs = require(&amp;#39;fs&amp;#39;),
    path = require(&amp;#39;path&amp;#39;);

//删除文件夹及文件夹下所有文件
const deleteFiles = (directory) =&amp;gt; {
    if (fs.existsSync(directory)) {
        fs.readdirSync(directory).forEach((file) =&amp;gt; {
            const filePath = path.join(directory, file);
            const stat = fs.statSync(filePath);
            if (stat.isFile()) {
                fs.unlinkSync(filePath);
            } else if (stat.isDirectory()) {
                deleteFiles(filePath);
            }
        });
        if (fs.readdirSync(directory).length === 0) {
            fs.rmdirSync(directory);
        }
    }
    fs.mkdir(&amp;#39;./exportData&amp;#39;, (err) =&amp;gt; { })
};

module.exports = deleteFiles;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;h3-u5B9Au65F6u4EFBu52A1&quot;&gt;&lt;a name=&quot;定时任务&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;定时任务&lt;/h3&gt;&lt;p&gt;项目中该功能正在开发中，只放了一个按钮，但思路已有了。&lt;br&gt;在node中的定时操作可用cron实现。&lt;br&gt;下面是一个小例子，每隔10秒打印一次1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const cron = require(&amp;#39;cron&amp;#39;);

async function startTask() {
     let cronJob = new cron.CronJob(
    //秒、分、时、天、月、周
    //通配符：,（时间点）-（时间域）*（所有值）/（周期性,/之前的0与*等效）?（不确定）
     &amp;#39;0/10 * * * * *&amp;#39;,
     async () =&amp;gt; {
     console.log(1);
         },
         null,
         true,
         &amp;#39;Asia/Shanghai&amp;#39;//时区标识符
     );
};&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;h1-u6CE8u610Fu4E8Bu9879&quot;&gt;&lt;a name=&quot;注意事项&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;注意事项&lt;/h1&gt;&lt;h3 id=&quot;h3-server-sent-events-sse-&quot;&gt;&lt;a name=&quot;Server-Sent Events(SSE)&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;Server-Sent Events(SSE)&lt;/h3&gt;&lt;p&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308101638383457594.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;该项目中前后端数据交互接口大多使用的是get请求，但有一个除外，反显爬取进度的接口：/getTaskState。&lt;br&gt;该接口使用的是SSE，爬取的进度与链接是实时显示的。&lt;br&gt;最近火热的ChatGPT的流式输出(像人打字一样一个字一个字的显示)使用的便是这个。&lt;br&gt;SSE虽然与WebSocket一样都是长链接，但不同的是，WebSocket为双工通信(服务器与客户端双向通信)，SSE为单工通信(只能服务器向客户端单向通信)。&lt;br&gt;项目中node服务端发送数据是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 事件流获取任务状态
    app.get(&amp;#39;/getTaskState&amp;#39;, async (req, res, next) =&amp;gt; {
        res.writeHead(200, {
            &amp;#39;Content-Type&amp;#39;: &amp;#39;text/event-stream&amp;#39;,
            &amp;#39;Cache-Control&amp;#39;: &amp;#39;no-cache&amp;#39;,
            &amp;#39;Connection&amp;#39;: &amp;#39;keep-alive&amp;#39;,
        });
        let sendStr = &amp;#39;&amp;#39;//发送的消息
        let id = setInterval(() =&amp;gt; {
            msgList = getMsg();
            //消息列表不为空且最后一条消息不等于上一次发送的消息才能执行
            if (msgList.length != 0 &amp;amp;&amp;amp; msgList[msgList.length - 1] != sendStr) {
                sendStr = msgList[msgList.length - 1];
                console.log(&amp;#39;\x1B[32m%s\x1B[0m&amp;#39;, sendStr)
                res.write(`data: ${sendStr}\n\n`);//发送消息
            }
        }, 10);
        req.on(&amp;#39;close&amp;#39;, () =&amp;gt; {
            clearMsg();//清空消息
            res.end();//结束响应
            clearInterval(id);//清除定时器(否则内存泄漏)
        });
    });&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;需要在res.writeHead中Content-Type设置为text/event-stream即表示使用SSE发送数据。&lt;br&gt;res.write(‘data: test\n\n’)即表示发送消息:test，每次发送消息需要以data:开头，\n\n结尾。&lt;br&gt;使用setInterval控制消息发送频率。&lt;br&gt;需要在服务端监听何时关闭，使用req.on(‘close’,()=&amp;gt;{})。&lt;br&gt;监听到关闭时执行响应结束res.end()与清除定时器clearInterval(id)。&lt;br&gt;在vue客户端接收数据是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//事件流获取任务状态
const getTaskState = () =&amp;gt; {
  stateMsg.value = &amp;quot;&amp;quot;;
  isState.value = true;
  let eventSource = new EventSource(origin.value + &amp;#39;/getTaskState&amp;#39;);
  eventSource.onmessage = (event) =&amp;gt; {
    if (event.data != &amp;#39;$END&amp;#39;) {
      stateMsg.value = event.data;
    } else {
      eventSource.close();//关闭连接(防止浏览器3秒重连)
      stateMsg.value = &amp;#39;执行完成！是否打开数据文件夹？&amp;#39;;
      isState.value = false;
      setTimeout(() =&amp;gt; {
        confirm(stateMsg.value) &amp;amp;&amp;amp;
          axios.get(origin.value + &amp;#39;/openDir&amp;#39;).then(res =&amp;gt; { })//打开数据文件夹
      }, 100);
    }
  };
  //处理错误
  eventSource.onerror = (err) =&amp;gt; {
    eventSource.close();//关闭连接
    stateMsg.value = &amp;#39;&amp;#39;
    isState.value = false;
  };
};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;直接在方法中new一个EventSource(url)，这是H5中新提出的对象，可用于接收服务器发送的事件流数据。&lt;br&gt;使用EventSource接收数据，直接在onmessage中获取event.data即可。&lt;br&gt;关闭连接记得使用eventSource.close()方法，因为服务器单方面关闭连接会触发浏览器3秒重连。&lt;br&gt;处理错误使用eventSource.onerror方法。&lt;br&gt;关于关闭SSE连接的时机，这是由node服务端决定的。&lt;br&gt;我在后端有一个store专门用于存储消息数据：&lt;br&gt;store/index.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let msgList = [];//消息列表

function addMsg(msg) {
    msgList.push(msg);
}

function getMsg() {
    return msgList;
}

function clearMsg() {
    //清空msgList中元素
    msgList = [];
}

module.exports = {
    addMsg,
    getMsg,
    clearMsg
};&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;在爬取数据时，后端会计算爬取的进度，将生成的消息字符串push到msgList列表中，每隔10ms发送给前端msgList列表中的最后一个元素。&lt;/li&gt;&lt;li&gt;当后端数据爬取完成时会向msgList中push存入指定字符串：$END，表示获取完成。&lt;/li&gt;&lt;li&gt;当前端识别到获取的消息为$END时，关闭连接。&lt;/li&gt;&lt;li&gt;后端监听到前端连接被关闭，则后端也关闭连接。&lt;/li&gt;&lt;/ol&gt;
&lt;h3 id=&quot;h3-pkg-&quot;&gt;&lt;a name=&quot;pkg打包&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;pkg打包&lt;/h3&gt;&lt;p&gt;全局安装pkg时最好网络环境为可访问github的环境，否则你只能手动下载那个失败的包再扔到指定路径。&lt;br&gt;pkg安装完成后需要在package.json中配置一番(主要是配置assets，将public与需要的依赖包打包进exe中)。&lt;br&gt;这是我的package.json配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;worm&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;0.1.3&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;bin&amp;quot;: &amp;quot;./index.js&amp;quot;,
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;start&amp;quot;: &amp;quot;node-dev ./index.js&amp;quot;,
    &amp;quot;dist&amp;quot;: &amp;quot;node pkg-build.js&amp;quot;
  },
  &amp;quot;pkg&amp;quot;: {
    &amp;quot;icon&amp;quot;: &amp;quot;./public/img/icon.ico&amp;quot;,
    &amp;quot;assets&amp;quot;: [
      &amp;quot;public/**/*&amp;quot;,
      &amp;quot;node_modules/axios/**/*.*&amp;quot;,
      &amp;quot;node_modules/cheerio/**/*.*&amp;quot;,
      &amp;quot;node_modules/cron/**/*.*&amp;quot;,
      &amp;quot;node_modules/express/**/*.*&amp;quot;
    ]
  },
  &amp;quot;author&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;,
  &amp;quot;dependencies&amp;quot;: {
    &amp;quot;axios&amp;quot;: &amp;quot;^0.27.2&amp;quot;,
    &amp;quot;cheerio&amp;quot;: &amp;quot;^1.0.0-rc.12&amp;quot;,
    &amp;quot;cron&amp;quot;: &amp;quot;^2.3.1&amp;quot;,
    &amp;quot;express&amp;quot;: &amp;quot;^4.18.2&amp;quot;,
    &amp;quot;node-dev&amp;quot;: &amp;quot;^8.0.0&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我的打包命令是通过scripts中的dist在pkg-build.js中引入的，因为我需要将版本号输出在打包出的exe文件名中。&lt;br&gt;若打包命令直接写在package.json的scripts中会无法读取打包进程中项目的version。&lt;br&gt;这是我的pkg-build.js：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//只有通过node xxx.js方式执行的命令才能获取到package.json的version
const pkg = require(&amp;#39;./package.json&amp;#39;),
    { execSync } = require(&amp;#39;child_process&amp;#39;);

const outputName = `dist/worm_v${pkg.version}.exe`;//拼接文件路径
const pkgCommand = `pkg . --output=${outputName} --target=win --compress=GZip`;//打包命令
execSync(pkgCommand);//执行打包命令
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上面命令中的output表示输出路径(包含exe文件名)，target表示打包的平台，compress表示压缩格式。&lt;br&gt;需要注意的是使用pkg打包时，项目中axios的版本不能太高。&lt;br&gt;否则即使你将axios写在pkg的打包配置里也无济于事，我使用的axios版本为0.27.2。&lt;/p&gt;
&lt;h3 id=&quot;h3-u89E3u51B3u8DE8u57DF&quot;&gt;&lt;a name=&quot;解决跨域&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;解决跨域&lt;/h3&gt;&lt;p&gt;我node使用的是express，直接在header中配置Access-Control-Allow-Origin为* 即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.all(&amp;#39;*&amp;#39;, (req, res, next) =&amp;gt; {
        res.header(&amp;quot;Access-Control-Allow-Origin&amp;quot;, &amp;quot;*&amp;quot;);//允许所有来源访问(设置跨域)
        res.header(&amp;quot;Access-Control-Allow-Headers&amp;quot;, &amp;quot;X-Requested-With,Content-Type&amp;quot;);//允许访问的响应头
        res.header(&amp;quot;Access-Control-Allow-Methods&amp;quot;, &amp;quot;PUT,POST,GET,DELETE,OPTIONS&amp;quot;);//允许访问的方法
        res.header(&amp;quot;X-Powered-By&amp;quot;, &amp;#39; 3.2.1&amp;#39;);//响应头
        res.header(&amp;quot;Content-Type&amp;quot;, &amp;quot;application/json;charset=utf-8&amp;quot;);//响应类型
        next();
    });&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;h3-child_process-&quot;&gt;&lt;a name=&quot;child_process模块&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;child_process模块&lt;/h3&gt;&lt;p&gt;众所周知，node是单线程运行的，在主线程中执行大量计算任务时会产生无响应的问题。&lt;br&gt;但node内置的child_process模块却可以创建新的进程，在新进程中执行操作不会影响到主进程的运行。&lt;br&gt;在此项目中自动打开浏览器、打开指定文件夹、执行打包命令用的就是它。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 打开数据文件夹
app.get(&amp;#39;/openDir&amp;#39;, (req, res) =&amp;gt; {
    res.send(&amp;#39;ok&amp;#39;);
   //打开文件夹,exe环境下需要使用exe所在目录
    let filePath = isPkg ?
        `${path.dirname(process.execPath)}${dir.replace(&amp;#39;./&amp;#39;, &amp;#39;\\&amp;#39;)}` :
        path.resolve(__dirname, dir);
    exec(`start ${filePath}`);
});

//监听端口
app.listen(port, () =&amp;gt; {
    let url = `http://${ipStr}:${port}`;
    isPkg &amp;amp;&amp;amp; exec(`start ${url}`);//打包环境下自动打开浏览器
    //判断是否存在exportData文件夹，没有则创建
    fs.exists(dir, async (exists) =&amp;gt; {
        !exists &amp;amp;&amp;amp; fs.mkdir(dir, (err) =&amp;gt; { });
    })
    console.log(
        &amp;#39;\x1B[31m%s\x1B[0m&amp;#39;,
        `\n
${time} 爬虫服务开启!\n
运行过程中禁止点击此窗口!\n
如需关闭爬虫关闭此窗口即可！\n`
    );
});&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;h3-u8BBEu7F6Eu9759u6001u8D44u6E90&quot;&gt;&lt;a name=&quot;设置静态资源&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;设置静态资源&lt;/h3&gt;&lt;p&gt;前端使用vue开发时，需要将vue.config.js中的publicPath配置设置为./之后再打包。&lt;br&gt;将vue打包后dist内的文件拷贝到node项目的public目录下。&lt;br&gt;需要在express设置请求头之前使用static(path.join(__dirname, ‘./public’))设置静态资源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const app = express();
const isPkg = process.pkg;//判断是否为打包环境
const port = isPkg ? 2222 : 1111;//端口
const ipStr = getLocalIp();//获取本机ip
let time = getFormatTime();//获取格式化时间
let dir = &amp;#39;./exportData&amp;#39;;
app.use(express.json());//解析json格式
app.use(express.static(path.join(__dirname, &amp;#39;./public&amp;#39;)));//设置静态资源
app.all(&amp;#39;*&amp;#39;, (req, res, next) =&amp;gt; {
    res.header(&amp;quot;Access-Control-Allow-Origin&amp;quot;, &amp;quot;*&amp;quot;);//允许所有来源访问(设置跨域)
    res.header(&amp;quot;Access-Control-Allow-Headers&amp;quot;, &amp;quot;X-Requested-With,Content-Type&amp;quot;);//允许访问的响应头
    res.header(&amp;quot;Access-Control-Allow-Methods&amp;quot;, &amp;quot;PUT,POST,GET,DELETE,OPTIONS&amp;quot;);//允许访问的方法
    res.header(&amp;quot;X-Powered-By&amp;quot;, &amp;#39; 3.2.1&amp;#39;);//响应头
    res.header(&amp;quot;Content-Type&amp;quot;, &amp;quot;application/json;charset=utf-8&amp;quot;);//响应类型
    next();
});&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;h3-u5173u4E8Eu8FD0u884Cu65F6u7684u9ED1u6846&quot;&gt;&lt;a name=&quot;关于运行时的黑框&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;关于运行时的黑框&lt;/h3&gt;&lt;p&gt;双击exe时，不仅会弹出浏览器用户页面，还会弹出黑框，点击黑框内部还会暂停程序运行。&lt;br&gt;我有想过使用pm2守护进程干掉黑框，但想到关闭爬虫时只需关闭黑框即可，便留下了黑框。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/08/202308102115083723103.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;h3-u9650u5236u722Cu53D6u6B21u6570&quot;&gt;&lt;a name=&quot;限制爬取次数&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;限制爬取次数&lt;/h3&gt;&lt;p&gt;做人，特别是做开发，你得有道德，你把人家网站给玩儿崩了这好吗(′⌒`)？&lt;br&gt;没有任何东西是无限制的，我的限制是放在前端的(可能不太严谨)，以爬取壁纸为例，调用inputLimit(num)，入参为执行次数，方法是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//输入限制
const inputLimit = (pageNum) =&amp;gt; {
  let val = prompt(`输入执行次数(小于等于${pageNum})`, &amp;quot;&amp;quot;);
  if (val == null || isNaN(val) || parseInt(val) &amp;lt; 1 || parseInt(val) &amp;gt; pageNum) {
    return false;
  }
  return parseInt(val);
};

//彼岸壁纸
const bianWorm = () =&amp;gt; {
  let val = inputLimit(10);
  if (val) {
    axios.get(origin.value + &amp;#39;/bianWorm?pageNum=&amp;#39; + val).then(res =&amp;gt; { });
    getTaskState();
  }
};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;后端获取到pageNum参数后，以此作为执行爬虫逻辑的循环依据。&lt;/p&gt;
&lt;h1 id=&quot;h1-u7ED3u8BED&quot;&gt;&lt;a name=&quot;结语&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;结语&lt;/h1&gt;&lt;p&gt;这是我第一次用js玩儿爬虫，很多地方可能不太完善，还请大佬们指出，谢谢啦！&lt;br&gt;此项目仅供学习研究，勿作他用。&lt;/p&gt;
</description><pubDate>Thu, 10 Aug 2023 21:45:44 +0800</pubDate></item><item><title>python项目生成exe</title><link>http://xiblogs.top:1111/?id=57</link><description>&lt;h1 id=&quot;h1-u524Du8A00&quot;&gt;&lt;a name=&quot;前言&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;前言&lt;/h1&gt;&lt;p&gt;做了个python的小项目，需要打包为桌面端的exe使用，结果一打包，体积直接上百兆了，研究了下，使用虚拟环境打出的包会更干净小巧。&lt;/p&gt;
&lt;h1 id=&quot;h1--anaconda&quot;&gt;&lt;a name=&quot;安装anaconda&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;安装anaconda&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;anaconda用作python的虚拟环境管理工具，下载地址：&lt;a href=&quot;https://repo.anaconda.com/archive/Anaconda3-2023.03-Windows-x86_64.exe&quot;&gt;https://repo.anaconda.com/archive/Anaconda3-2023.03-Windows-x86_64.exe&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;配置anaconda环境变量&lt;br&gt;win+i 搜索“高级系统设置”，打开&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041218208084755.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;点击环境变量&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041218026583437.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;双击系统变量中的Path&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041218596731351.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;依次添加系统环境变量(需视anaconda安装目录而定)：&lt;br&gt;D:\anaconda&lt;br&gt;D:\anaconda\Library\mingw-w64\bin&lt;br&gt;D:\anaconda\Library\usr\bin&lt;br&gt;D:\anaconda\Library\bin&lt;br&gt;D:\anaconda\Scripts&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041221593817117.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;依次返回确定保存&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;h1 id=&quot;h1--anaconda-&quot;&gt;&lt;a name=&quot;创建anaconda虚拟环境&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;创建anaconda虚拟环境&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;打开anaconda prompt&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041234233617362.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;创建anaconda虚拟环境：conda create -n env_1 python==3.7.9(可自行选择版本)&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;验证是否创建成功：python -V&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304131644263424862.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;常用anaconda命令：&lt;br&gt;导出虚拟环境的列表:conda env list&lt;br&gt;导出当前环境的包:conda list&lt;br&gt;启动/切换至名为name的Python环境:conda activate name&lt;br&gt;退出虚拟环境:conda deactivate&lt;br&gt;删除虚拟环境:conda remove -n 环境名 –all&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;在虚拟环境中安装python打包工具pyinstaller，以及其他自己在项目中使用到的包：&lt;br&gt;pip install -i &lt;a href=&quot;https://pypi.tuna.tsinghua.edu.cn/simple&quot;&gt;https://pypi.tuna.tsinghua.edu.cn/simple&lt;/a&gt; pyinstaller(国内使用清华源较快)&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;h1 id=&quot;h1-vscode-python-&quot;&gt;&lt;a name=&quot;vscode选择python解释器&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;vscode选择python解释器&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;打开vscode,按下ctrl+shift+p&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;输入：Python: Select Interpreter，点击出现的选项&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041227591866016.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;点击：输入解释器路径-查找&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041249186762991.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;确认选中anaconda安装路径下刚生成的虚拟环境的python.exe文件&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041240321219262.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;h1 id=&quot;h1--vscode-cmd&quot;&gt;&lt;a name=&quot;设置vscode默认终端为cmd&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;设置vscode默认终端为cmd&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;点击选择默认配置文件&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041412433291128.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;点击选中Command Prompt&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041413351084630.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;关闭vscode，重新使用vscode打开项目，按下shift+ctrl+` ,查看是否使用cmd切换为了虚拟环境&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041417416200052.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;h1 id=&quot;h1--spec-&quot;&gt;&lt;a name=&quot;生成spec文件&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;生成spec文件&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;spec文件就是pyinstaller打包时的配置文件(语法为python语法)，控制台输入命令：pyi-makespec -F -w main.py(main为生成的spec文件的文件名)&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;编辑spec文件&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# -*- mode: python ; coding: utf-8 -*-
def getExeName(): # 获取名字与版本
 import config
 return config.name + &amp;#39;-&amp;#39; + config.version
block_cipher = None
a = Analysis(
 [&amp;#39;main.py&amp;#39;], # 需要打包的py文件
 pathex=[],
 binaries=[],
 datas=[],
 hiddenimports=[],
 hookspath=[],
 hooksconfig={},
 runtime_hooks=[],
 excludes=[],
 win_no_prefer_redirects=False,
 win_private_assemblies=False,
 cipher=block_cipher,
 noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
 pyz,
 a.scripts,
 a.binaries,
 a.zipfiles,
 a.datas,
 [],
 name=getExeName(), # 打包后的名字
 debug=False,
 bootloader_ignore_signals=False,
 strip=False,
 upx=True,
 upx_exclude=[],
 runtime_tmpdir=None,
 console=False, # 双击exe是否显示cmd窗口
 disable_windowed_traceback=False,
 argv_emulation=False,
 target_arch=None,
 codesign_identity=None,
 entitlements_file=None,
 icon=&amp;#39;./img/icon.ico&amp;#39; # 打包的exe图标
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Analysis第一个参数为列表，列表中元素为需要打包的py文件，可自行添加删除，EXE为打包的exe信息，可在此处修改exe的名字、图标等。图标必须为ico格式图片，这里提供一个图片格式转换网站:&lt;a href=&quot;https://onlineconvertfree.com/zh/convert/svg/&quot;&gt;https://onlineconvertfree.com/zh/convert/svg/&lt;/a&gt; 。&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;h1 id=&quot;h1--spec-&quot;&gt;&lt;a name=&quot;利用spec配置打包&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;利用spec配置打包&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;spec文件编辑完成后输入命令：Pyinstaller main.spec(main.spec为刚才生成的配置文件名)&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;出现下图即为打包成功，在项目的根目录下会生成dist目录，里面即为打包出的exe文件：&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304041451522103846.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
</description><pubDate>Tue, 04 Apr 2023 11:42:00 +0800</pubDate></item><item><title>express项目的创建</title><link>http://xiblogs.top:1111/?id=56</link><description>&lt;h1 id=&quot;h1-u524Du8A00&quot;&gt;&lt;a name=&quot;前言&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;前言&lt;/h1&gt;&lt;p&gt;前端开发者若要进行后端开发，大多都会选择node.js，在node生态下是有大量框架的，其中最受新手喜爱的便是老牌的express.js，接下来我们就从零创建一个express项目。&lt;/p&gt;
&lt;h1 id=&quot;h1--node&quot;&gt;&lt;a name=&quot;安装node&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;安装node&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;在这里：&lt;a href=&quot;https://nodejs.org/dist/v16.14.0/node-v16.14.0-x64.msi&quot;&gt;https://nodejs.org/dist/v16.14.0/node-v16.14.0-x64.msi&lt;/a&gt; ，下载后直接安装。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;安装完成后，验证是否安装成功，win+r输入cmd，输入命令:node -v&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304131741056384858.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;h1 id=&quot;h1--cnpm&quot;&gt;&lt;a name=&quot;安装淘宝镜像cnpm&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;安装淘宝镜像cnpm&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;国内直接使用npm下载会比较慢，在cmd中输入命令安装淘宝cnpm：&lt;br&gt;npm install -g cnpm –registry=&lt;a href=&quot;https://registry.npm.taobao.org&quot;&gt;https://registry.npm.taobao.org&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;验证cnpm是否安装成功：cnpm -v&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304131744545833463.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;h1 id=&quot;h1--express-generator&quot;&gt;&lt;a name=&quot;安装express-generator&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;安装express-generator&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;express跟大多框架一样，也是有脚手架工具的，便于项目的搭建，即express-generator。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;使用cnpm全局安装脚手架：cnpm install -g express-generator&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;验证是否安装成功：express –version&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304141049389403689.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;h1 id=&quot;h1-u521Bu5EFAu9879u76EE&quot;&gt;&lt;a name=&quot;创建项目&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;创建项目&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;输入命令：express test (test为项目名) 出现下图即为创建成功。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304141053126561036.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;项目创建后不会自动安装依赖。安装依赖命令：npm i(i为install简写) 出现下图即为安装成功。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304141055408029481.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;最终目录结构如下：&lt;br&gt;bin:项目启动脚本&lt;br&gt;node_modules：项目依赖&lt;br&gt;public：公共静态资源&lt;br&gt;routes：路由目录&lt;br&gt;views：视图目录(前后端分离开发可不用关注此)&lt;br&gt;app.js：项目入口&lt;br&gt;package-lock.json：依赖版本锁定信息&lt;br&gt;package.json：项目配置及依赖版本信息&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304141104007687619.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;h1 id=&quot;h1-u542Fu52A8u9879u76EE&quot;&gt;&lt;a name=&quot;启动项目&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;启动项目&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;输入命令：npm start 会出现下图&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304141101526195427.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;在浏览器地址栏输入：&lt;a href=&quot;http://localhost:3000/&quot;&gt;http://localhost:3000/&lt;/a&gt; 页面出现下图即为启动成功。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/04/202304141102446159592.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
&lt;h1 id=&quot;h1-u9879u76EEu70EDu66F4&quot;&gt;&lt;a name=&quot;项目热更&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;项目热更&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;在开发过程中，每次修改代码后需要npm start重启项目后，才会使得修改后的代码生效。&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;可以使用插件node-dev，实现项目热更。安装命令:npm i node-dev&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;在package.json的scripts中添加:”dev”: “node-dev ./bin/www”&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;热更启动项目的命令：npm run dev&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
</description><pubDate>Thu, 23 Mar 2023 22:57:11 +0800</pubDate></item><item><title>无法加载nodejs\vue.ps1</title><link>http://xiblogs.top:1111/?id=55</link><description>&lt;h1 id=&quot;h1-u53D1u73B0u95EEu9898&quot;&gt;&lt;a name=&quot;发现问题&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;发现问题&lt;/h1&gt;&lt;p&gt;刚换了电脑之后，安装了node.js、vue/cli，在vscode中使用vue ui命令新建vue项目时，发现报错如下：&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/03/202303051206497978247.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;h1-u5206u6790u95EEu9898&quot;&gt;&lt;a name=&quot;分析问题&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;分析问题&lt;/h1&gt;&lt;p&gt;多番查询后发现,一般此类问题大多出现在第一次运行脚本的电脑中，因为PowerShell的默认执行策略是禁止系统允许脚本命令的执行，如果需要允许类似命令需要允许脚本执行。&lt;/p&gt;
&lt;h1 id=&quot;h1-u89E3u51B3u529Eu6CD5&quot;&gt;&lt;a name=&quot;解决办法&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;解决办法&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;管理员身份运行powershell&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/03/202303051208511849124.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;li&gt;&lt;p&gt;输入命令：set-ExecutionPolicy RemoteSigned后按下回车&lt;br&gt;输入：y&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/03/202303051210462589157.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;&lt;/ol&gt;
</description><pubDate>Fri, 10 Mar 2023 12:02:37 +0800</pubDate></item><item><title>vue3实现一个抽奖小项目</title><link>http://xiblogs.top:1111/?id=53</link><description>&lt;h1 id=&quot;h1-u524Du8A00&quot;&gt;&lt;a name=&quot;前言&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;前言&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;在公司年会期间我做了个抽奖小项目，我把它分享出来，有用得着的可以看下。&lt;/li&gt;&lt;li&gt;浏览链接：&lt;a href=&quot;https://xiblogs.top/original/other/luckDraw/index.html&quot;&gt;https://xiblogs.top/original/other/luckDraw/index.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt;项目链接：&lt;a href=&quot;https://gitee.com/xi1213/luck-draw&quot;&gt;https://gitee.com/xi1213/luck-draw&lt;/a&gt; (欢迎star!)&lt;/li&gt;&lt;li&gt;项目截图：&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/01/202301201458081786833.jpg&quot; alt=&quot;&quot;&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;h1 id=&quot;h1-u5B9Eu73B0u76EEu6807&quot;&gt;&lt;a name=&quot;实现目标&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;实现目标&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;数据保存：无后端，纯前端实现，浏览器刷新或者关闭数据不能丢失。&lt;/li&gt;&lt;li&gt;姓名切换：点击中部开始按钮姓名快速切换。&lt;/li&gt;&lt;li&gt;奖项切换：奖项为操作人员手动切换设置。&lt;/li&gt;&lt;li&gt;历史记录：抽奖完成后需要有历史记录。&lt;/li&gt;&lt;li&gt;数据导入：允许参与人员的表格导入。&lt;/li&gt;&lt;/ul&gt;
&lt;h1 id=&quot;h1-u6570u636Eu4FDDu5B58&quot;&gt;&lt;a name=&quot;数据保存&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;数据保存&lt;/h1&gt;&lt;p&gt;无后台，纯前端实现而且需要刷新关闭浏览器数据不丢失，很容易便会想到使用localStorage，localStorage存入的数据具有持久性，不会因为刷新或关闭浏览器而变化(除非手动刻意的清除)，有别于sessionstorage，localStorage的生命周期是永久，sessionstorage是浏览器或者标签页关闭。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/01/202301201607474276988.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;因为存入的数据不是单纯的字符串，而是具有结构性的对象数组，所以需要配合JSON.stringify与JSON.parse来使用。这是存入数据的方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;localStorage.setItem(&amp;quot;luckDrawHis&amp;quot;, JSON.stringify(luckDrawHis));//JSON.stringify将json转换为字符串&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这是读取数据的方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;JSON.parse(localStorage.getItem(&amp;quot;luckDrawHis&amp;quot;))//JSON.parse将字符串转换为json&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;h1-u59D3u540Du5207u6362&quot;&gt;&lt;a name=&quot;姓名切换&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;姓名切换&lt;/h1&gt;&lt;p&gt;抽奖的方式是数据导入后，点击中间的圆形开始按钮，姓名便开始快速切换，再次点击按钮便停止姓名切换，弹出对话框显示当前姓名以及设置的奖项。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/01/202301201612268094437.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;切换姓名利用了vue的数据响应式原理。先获取到所有的参与人员数据，然后乱序处理，最后循环展示，我这里每个姓名展示的时间为50毫秒，你也可以自己设置。这里的数组乱序我使用了洗牌算法，其实就是利用Math.random获取数组的随机下标，然后与最后一个元素进行位置交换。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//洗牌算法(乱序数组)
function shuffle(arr) {
  let l = arr.length
  let index, temp
  while (l &amp;gt; 0) {
    index = Math.floor(Math.random() * l)
    temp = arr[l - 1]
    arr[l - 1] = arr[index]
    arr[index] = temp
    l--
  }
  return arr;
}

//循环列表
function forNameList(list) {
  list = shuffle(list);
  for (let i = 0; i &amp;lt; list.length; i++) {
    setTimeout(() =&amp;gt; {
      if (!isStop.value) {
        curName.value = list[i].name;
        (i == list.length - 1) &amp;amp;&amp;amp; (forNameList(nameList.value));//数组耗尽循环
      }
    }, 50 * i);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;h1-u5956u9879u5207u6362&quot;&gt;&lt;a name=&quot;奖项切换&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;奖项切换&lt;/h1&gt;&lt;p&gt;奖项切换直接使用elementPlus的单选框即可。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/01/202301201602521200257.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/01/202301201603142463419.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;h1-u5386u53F2u8BB0u5F55&quot;&gt;&lt;a name=&quot;历史记录&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;历史记录&lt;/h1&gt;&lt;p&gt;每次点击抽奖出现结果时，将之前的抽奖结果取出来，然后把当前的结果添加到末尾。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/01/202301201543032211178.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;点击抽奖历史按钮时再将所有历史数据取出来。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/01/202301201545149962597.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1 id=&quot;h1-u6570u636Eu5BFCu5165&quot;&gt;&lt;a name=&quot;数据导入&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;数据导入&lt;/h1&gt;&lt;p&gt;由于需要导入人员表格数据，这里我使用了xlsx插件与file-saver插件来实现。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/01/202301201515539131274.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;首先是下载模板。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/01/202301201545469977054.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;将事先准备好的表格模板放在项目的public目录下。&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/01/202301201515059536445.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;点击下载模板按钮时直接调用以下方法即可，其中的saveAs是file-saver插件中的方法，传入路径与文件名即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { saveAs } from &amp;#39;file-saver&amp;#39;;
//下载模板
function downTemp() {
    let fileName = &amp;quot;人员模板.xlsx&amp;quot;;//文件名
    let fileUrl = &amp;quot;./template/&amp;quot;;//文件路径(路径相对index.html)
    saveAs(fileUrl + fileName, fileName);
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;表格处理好，&lt;br&gt;&lt;img src=&quot;http://xiblogs.top:1111/zb_users/upload/2023/01/202301201521269502861.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;点击导入按钮读取表格数据时使用的是xlsx插件，下面是读取数据的方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import * as XLSX from &amp;quot;xlsx&amp;quot;;
//导入数据
function importData(e) {
    isLoading.value = true;
    let file = e.target.files[0]; //获取事件中的file对象
    let fileReader = new FileReader(); //创建文件读取器
    fileReader.onload = (event) =&amp;gt; {
        let result = event.target.result; //获取读取的结果
        let workBook = XLSX.read(result, { type: &amp;quot;binary&amp;quot; }); //XLSX读取返回的结果
        let jsonData = XLSX.utils.sheet_to_json(
            workBook.Sheets[workBook.SheetNames[0]]
        ); //将读取结果转换为json
        tabData.value = [];
        jsonData.forEach((j) =&amp;gt; {
            tabData.value.push({
                name: j.姓名,
                age: j.性别,
                department: j.部门,
            });
        }); //处理成需要的数据格式
        localStorage.setItem(&amp;quot;tabData&amp;quot;, JSON.stringify(tabData.value));//数据存入本地
        tabDataS.value = JSON.parse(localStorage.getItem(&amp;quot;tabData&amp;quot;));//取出数据
        emits(&amp;quot;getNameList&amp;quot;, tabData);
        isLoading.value = false;
    };
    fileReader.readAsBinaryString(file); //开始读取文件
    ((document.getElementsByClassName(&amp;quot;inp-xlsx&amp;quot;)[0]).value = &amp;quot;&amp;quot;); //置空选中的文件
};&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;h1-u7ED3u8BED&quot;&gt;&lt;a name=&quot;结语&quot; class=&quot;reference-link&quot; href=&quot;#&quot;&gt;&lt;/a&gt;&lt;span class=&quot;header-link octicon octicon-link&quot;&gt;&lt;/span&gt;结语&lt;/h1&gt;&lt;p&gt;项目很简单，但给我的时间很少，很多优化的地方都没做好，后面有时间了再优化下，顺便适配下移动端。&lt;/p&gt;
</description><pubDate>Fri, 20 Jan 2023 14:33:54 +0800</pubDate></item></channel></rss>