探索 Electron:如何进行网址收藏并无缝收录网页图片内容?

Electron是一个开源的桌面应用程序开发框架,它允许开发者使用Web技术(如 HTML、CSS 和 JavaScript)构建跨平台的桌面应用程序,它的出现极大地简化了桌面应用程序的开发流程,让更多的开发者能够利用已有的 Web 开发技能来构建功能强大且跨平台的应用程序,这对于提升开发效率和应用程序的快速交付具有重要意义。

今天借助electron实现添加网址的应用功能,这里我们通过electron-vite框架搭建项目,详细的配置请参考我之前的文章:地址 这里不再赘述,接下来开始项目的正式讲解:

目录

头部内容搭建

列表数据传递

列表内容处理

头部搜索处理

列表网站弹框

保存弹框图片

收藏本地图片


头部内容搭建

这里我们在头部添加一个添加按钮和搜索框,用于对数据进行处理,如下所示:

<template><div class="search-container"><div class="button" @click="handleAdd">+</div><div class="input"><input type="text" placeholder="请输入关键字"></div></div>
</template>

效果如下所示:

然后这里我们手写一个弹框的效果,如下我们封装一个dialog组件,然后通过showDialog进行判断显示与隐藏:

<template><div class="dialog" v-if="showDialog"><div class="content"><div class="input"><input type="text" placeholder="请输入网址"></div><div class="btns"><button>添加</button><button @click="setIsShow(false)">取消</button></div></div></div>
</template>

我们在首页的index父组件中,通过provide和inject进行父组件与其所有子孙组件之间进行跨层级数据传递的高级选项,这对于复杂的应用程序结构或深层级嵌套的组件特别有用,如下我们通过其设置了一个变量和控制变量的函数:

<template><div class="home"><searchBar></searchBar></div><Dialog></Dialog>
</template><script setup lang="ts">
import { ref, provide } from "vue"
import searchBar from "./components/searchBar.vue"
import Dialog from "./components/dialog.vue"// 窗口的显示状态
const showDialog = ref(false)
const setIsShow = (isShow: boolean) => {showDialog.value = isShow
}
provide("dialog-visible", {showDialog,setIsShow
})
</script>

然后我们在父组件下的两个子组件进行数据的传递,如下所示:

最终呈现的效果如下所示:

列表数据传递

接下来我们开始设置列表内容,然后在home的父组件下进行引入,如下所示:

<template><div class="list"><div class="no-item">暂无数据...</div><div class="item"><div class="read-item"><img src="" alt=""><h2>百度一下</h2><button> x </button></div><div class="read-item"><img src="" alt=""><h2>百度一下</h2><button> x </button></div></div></div>
</template>

接下来我们在对话框中的添加按钮设置点击事件,然后通过ipcRenderer函数中的invoke函数进行主进程与渲染进程的双向通信,这里我们把对话框中的输入框的内容作为数据传递给主进程:

在真正的项目中,主进程可以有许许多多的与渲染进程互通传递数据的函数,为了方便管理,这里我们把与主进程通信的函数抽离出去,然后再在主进程中进行引入,这里我们抽离出一个获取url资源数据的函数进行设置,如下所示:

const { ipcMain, BrowserWindow } = require('electron')export const getSource = () => {ipcMain.handle('add-url', (_, url) => {const win = new BrowserWindow({width: 500,height: 500,show: false,webPreferences: {offscreen: true, // 开启 offscreen},})win.loadURL(url)win.webContents.on('did-finish-load', () => {const title = win.getTitle()console.log(title)})})
}

如下我们再主进程中进行调用:

当我们点击对话框中的添加按钮后,上述代码会将百度的标题进行一个获取,如下所示:

如果主进程打印的数据出现乱码的情况,这里只需要对package.json文件中运行的命令进行如下配置即可:

接下来我们在getSource文件中,对渲染进程传递过来的url进行一个数据的抓取,这里我们使用了一个异步的Promise进行一个数据的获取并将其return出去,代码如下所示:

const { ipcMain, BrowserWindow } = require('electron');export const getSource = () => {ipcMain.handle('add-url', async (_, url) => {const win = new BrowserWindow({width: 500,height: 500,show: false,webPreferences: {offscreen: true, // 开启 offscreen},});win.loadURL(url);return new Promise((resolve, reject) => {win.webContents.on('did-finish-load', async () => {try {const title = win.getTitle();// 获取nativeImageconst image = await win.webContents.capturePage();const screenShot = image.toDataURL();resolve({title,screenShot,url});} catch (error) {reject(error);}// 关闭窗口,避免内存泄漏win.close();});win.webContents.on('did-fail-load', () => {reject(new Error('Failed to load the URL'));win.close();});});});
}

然后我们在dialog组件中对渲染进程传递的数据进行一个异步的获取结果:

最终达到的效果如下所示,可以看到我们的数据已经成功获取到了:

列表内容处理

添加内容:为了方便处理,这里我们把渲染进程获取到的数据进行一个pinia仓库数据管理,关于pinia仓库及其持久化的配置可以参考我开局分享的链接,这里不再赘述,具体仓库内容如下所示:

// 网站模块信息仓库
import { defineStore } from "pinia";
import { ref } from "vue"export const useWebSiteStore = defineStore("webSite", () => {let webSites = ref<any>([]);// 添加网站信息const addWebSite = (data) => {console.log(data)webSites.value = [ data, ...webSites.value ]}return { webSites, addWebSite }
}, { persist: true }) // 开启持久化

然后我们在对话框中的输入的数据在主进程解析并传递过来之后,这里我们将其存储到仓库当中然后通过一个状态判断当前按钮是否是数据存入的状态,避免用户在数据还没返回来之前,对按钮进行重复点击:

<template><div class="dialog" v-if="showDialog"><div class="content"><div class="input"><input v-model="url" type="text" placeholder="请输入网址"></div><div class="btns"><button @click="handleAdd" :disabled="isSumbit">添加</button><button @click="setIsShow(false)" :disabled="isSumbit">取消</button></div></div></div>
</template><script setup lang="ts">
import { ref, inject } from 'vue'
const { ipcRenderer } = require('electron')
import { useWebSiteStore } from '@renderer/store/modules/webSite'let url = ref('https://www.baidu.com')
const webSiteStore = useWebSiteStore()
const isSumbit = ref(false) // 是否提交const { showDialog, setIsShow } = inject('dialog-visible') as any
const handleAdd = async () => {isSumbit.value = truelet result = await ipcRenderer.invoke('add-url', url.value)webSiteStore.addWebSite(result)isSumbit.value = false  setIsShow(false)
}
</script>

存储完仓库之后,在list组件中我们开始把仓库当中的数据进行一个取出,然后进行一个数据的渲染,如下所示:

<template><div class="list"><div class="no-item" v-if="webSiteStore.webSites.length <= 0">暂无数据...</div><div class="item" v-for="(item, index) in webSiteStore.webSites" :key="index"><div class="read-item"><img :src="item.screenShot" :alt="item.title"><h2>{{ item.title }}</h2><button> x </button></div></div></div>
</template><script setup lang="ts">
import { useWebSiteStore } from '@renderer/store/modules/webSite'
const webSiteStore = useWebSiteStore()
</script>

最终呈现的效果如下所示,可以看到我们的数据已经抓取渲染成功,并且存储到本地磁盘当中:

删除内容:接下来我们在仓库中编写相应的删除数据的函数,这里通过一个filter进行一个url的过滤

// 删除网站信息
const deleteWebSite = (url) => {webSites.value = webSites.value.filter(item => item.url !== url)
}

然后在list组件中传递对应的url即可:

最终呈现的效果如下所示:

过滤重复:如果重复添加同一个网站,这里还需要进行一个消息弹框的提示,这里我们在主进程中抽离一个弹框文件进行提示,这里使用到了electron自带的对话框操作:

然后来到我们的仓库里面,对添加的函数进行一个判断,如果已经存在的网址进行一个弹框的提示

最终呈现的效果如下所示:

网址合法:接下来我们要对输入的网址的合法性进行一个处理,如果用户是随便输入的网址,这里我们也要对其进行一个弹框提示,首先我们先在getSource函数中对数据进行一个处理,如果当前的网址不合法,肯定是获取不了图片元素的,这里我们就对其进行一个判断:

然后在dialog组件中,这里我们对添加按钮进行一个情况的判断:

<script setup lang="ts">
import { ref, inject } from 'vue'
const { ipcRenderer } = require('electron')
import { useWebSiteStore } from '@renderer/store/modules/webSite'let url = ref('https://www.')
const webSiteStore = useWebSiteStore()
const isSumbit = ref(false) // 是否提交const { showDialog, setIsShow } = inject('dialog-visible') as any
const handleAdd = async () => {isSumbit.value = truetry {if (url.value.startsWith('https://www.')) {let result = await ipcRenderer.invoke('add-url', url.value)webSiteStore.addWebSite(result)isSumbit.value = false  setIsShow(false)} else {ipcRenderer.invoke('onShowMessage', '当前输入不是正确网址!')url.value = 'https://www.'isSumbit.value = false}} catch (error) {ipcRenderer.invoke('onShowMessage', '无法访问该站点!')url.value = 'https://www.'isSumbit.value = false}url.value = 'https://www.'
}
const handleCannel = () => {setIsShow(false)url.value = 'https://www.'
}
</script>

最终呈现的效果如下所示:

头部搜索处理

接下来我们开始实现在头部搜索框输入内容之后,对网站的标题进行一个模糊搜索,因为头部组件和列表内容组件是兄弟组件,搜索框用户输入的数据列表内容组件是要拿到的,兄弟组件进行通信可以使用事件总线bus,或者使用provide和inject方式,这里就使用后者吧!

在父组件定义相关的数据和处理数据的函数,然后使用provide进行暴露出去:

// 搜索组件的数据
const searcKeyWord = ref('')
const setSearchKeyWord = (key: string) => {searcKeyWord.value = key
}
provide("search-key", {searcKeyWord,setSearchKeyWord
})

在搜索组件中,通过keyup鼠标抬起事件来获取输入框数据,并进行inject注入:

在列表内容组件,通过注入拿到对应的数据,可以渲染到页面上,如下所示:

最终呈现的效果如下所示:

然后这里我们开始对输入框进行一个模糊查询,这里我们使用计算属性进行操作,代码如下:

// 获取关键字网站信息 
const filteredWebSites = computed(() => {const keyword = searcKeyWord.value.trim().toLowerCase()if (!keyword) {return webSiteStore.webSites} else {return webSiteStore.webSites.filter(item => item.title.toLowerCase().includes(keyword))}
})

最终呈现的效果如下所示:

列表网站弹框

接下来我们要实现点击列表中的某个网站之后,会弹出对应网站的链接内容的弹框,这里我们要对其列表中的数据设置对应的点击事件,这里顺便把点击列表内容进行一个样式激活操作的内容做掉,具体代码如下所示:

<template><div class="list"><div class="no-item" v-if="webSiteStore.webSites.length <= 0">暂无数据...</div><div class="item" v-for="(item, index) in webSiteStore.webSites" :key="index"><div class="read-item" :class="{ selected: currentWebSite === index }" @click="handleClick(index, item.url)"><img :src="item.screenShot" :alt="item.title"><h2>{{ item.title }}</h2><button @click="webSiteStore.deleteWebSite(item.url)"> x </button></div></div></div>
</template><script setup lang="ts">
import { ref } from 'vue'
import { useWebSiteStore } from '@renderer/store/modules/webSite'
const webSiteStore = useWebSiteStore()// 当前点击的网站
const currentWebSite = ref<number>(0)
// 点击网站
const handleClick = (index, url) => {currentWebSite.value = index
}
</script>

效果如下所示,可以看到我们实现了点击之后,样式激活状态的显示:

然后我们在点击事件上可以使用最简单的window自带的打开网页函数:

// 点击网站
const handleClick = (index, url) => {currentWebSite.value = indexwindow.open(url, '_blank')
}

这里我们主要使用electron带的弹框效果进行列表内容网址弹框,如下我们在点击事件处通过渲染进程往主进程发送一个url数据:

// 点击网站
const handleClick = (index, url) => {currentWebSite.value = index// window.open(url, '_blank')ipcRenderer.invoke('on-open-window', url)
}

在主进程这里通过BrowserWindow函数再次创建一个新的窗口,为了保持窗口的修改状态,这里我们可以使用第三方插件:electron-window-state 进行操作,安装命令如下所示:

npm install --save electron-window-state

接下来我们借助该插件再次创建窗口,并把创建的窗口的状态保存下来,代码如下所示:

const { ipcMain, BrowserWindow } = require('electron');
const WinState = require('electron-window-state')export const openWindow = () => {ipcMain.handle('on-open-window', (_, url) => {// 窗口状态管理const winState = new WinState({defaultWidth: 800,defaultHeight: 600,electronStoreOptions: { // 存储窗口状态信息name: 'win-state'}});const win = new BrowserWindow({width: winState.width, height: winState.height,x: winState.x, y: winState.y,show: false,})win.on('ready-to-show', () => {win.show()})win.loadURL(url)winState.manage(win) // 窗口状态管理})
}

最终呈现的效果如下所示:

保存弹框图片

接下来我们对弹框网站中的一些图片进行一个右键保存的效果实现,这里我们在打开的新窗口中调用webContents对象来监听网页的右键上下文菜单(context-menu)事件,并在触发该事件时尝试执行一个名为saveas的函数,该函数意图是保存与右键点击相关的资源(如图片、链接指向的文件等),如下所示:

接下来我们开始编写对应的 saveas 函数中的内容,这里我们使用got模块,got是一个简化和增强Node.js原生http模块的HTTP客户端,用于发送HTTP请求,目前11版本还支持require导入的写法,这里就安装11版本,命令如下:

npm i got@11 -S

然后我们开始调用got模块发起请求,代码如下所示:

const { Menu } = require('electron');
const got = require('got');export const saveas = (srcUrl) => {if (srcUrl) {const contextMenu = Menu.buildFromTemplate([{label: '图片另存为',click() {got.get(srcUrl).then((res: any) => {const chunk = Buffer.from(res.rawBody);console.log(chunk.toString())})}},])contextMenu.popup();}
}

然后我们在主进程中可以看到我们打印出了图片的二进制流:

拿到二进制流之后,接下来我们开始对其进行一个处理,这里我们通过如下的安装命令,可以获取到我们获取图片的后缀名:

npm i image-type@4.1.0 -S

然后这里我们通过调用electron的对话框函数,弹出保存图片的对话框,然后通过path获取当前保存图片的路径,然后将其下载到我们规定的路径当中:

const { Menu, dialog } = require('electron');
const path = require('path');
const got = require('got');
const imageType = require('image-type');export const saveas = (srcUrl) => {if (srcUrl) {const contextMenu = Menu.buildFromTemplate([{label: '图片另存为',click() {got.get(srcUrl).then(async (res: any) => {const chunk = Buffer.from(res.rawBody);const imgType = imageType(chunk);console.log(imgType.ext)const { filePath, canceled } = await dialog.showSaveDialog({title: '图片另存为',defaultPath: path.resolve(__dirname, '../../src/renderer/src/assets/images'),})if (!canceled) {console.log(filePath)}})}},])contextMenu.popup();}
}

接下来我们通过一个随机数来对下载图片进行一个命名操作, 通过安装如下命令来获取随机数:

npm i randomstring -D

生成的随机数然后再拼接我们的后缀名,下载图片的前期准备工作可以说是基本完成了:

接下来我们开始对我们下载的图片进行一个写入操作,可以看到图片被成功写入到文件中了:

收藏本地图片

上文将图片保存在本地之后,接下来我们需要把本地保存的图片,再读取到electron桌面端上面,这里我们在桌面页面的顶部上再存放一个按钮,然后进行路由的跳转,这里的路由配置不再赘述,可以参考开局分享的链接,我们在根组件设置如下代码:

<template><div class="container"><div class="header"><div class="menu"><router-link to="/home" :class="{ active: currentIndex === 0 }" @click="currentIndex = 0">网站收藏</router-link><router-link to="/imageGallery" :class="{ active: currentIndex === 1 }" @click="currentIndex = 1">图片收藏</router-link></div><div class="close" @click="close">关闭</div></div><router-view></router-view></div>
</template><script setup lang="ts">
import { ref } from 'vue'
const { ipcRenderer } = require('electron')const currentIndex = ref(0)
// 关闭窗口
const close = () => {ipcRenderer.send('close-main-window')
}
</script>

呈现的效果如下所示:

然后我们编写获取本地文件的主进程代码,如下所示:

const { ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');export const getFilesList = () => {ipcMain.handle('on-getfiles-event', (_, msg) => {fs.readdir(path.resolve(__dirname, '../../src/renderer/src/assets/images/'), (err, files) => {console.log(files)})})
}

在控制台给我们打印出当前文件目录下的所有文件:

然后这里我们将获取到的文件结果给return出去,然后在渲染进程中拿到对应的数据进行一个打印

获取到图片的资源信息之后,接下来通过v-for对数据进行一个渲染:

<template><div class="imageGallery"><div class="img-item" v-for="(item, index) in imgList" :key="index"><img :src="`/src/assets/images/${item}`" alt="图片"></div></div>
</template><script setup lang="ts">
import { ref, onMounted } from 'vue'
const { ipcRenderer } = require('electron')const imgList = ref([])// 获取本地图片资源
const getLocalImages = async () => {const fileList = await ipcRenderer.invoke('on-getfiles-event')imgList.value = fileList
}
onMounted(() => {getLocalImages()
})
</script>

最终呈现的效果如下所示:

目前项目就暂时写这么多吧,如果大家有想法的也可以在项目中进行一个二开操作,项目地址分享如下,项目地址分享:地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/51554.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EtherNet/IP转CAN协议转化网关(功能与配置)

怎么样把EtherNet/IP和CAN两个协议连接起来?有很多朋友想要了解这个问题&#xff0c;那么作者在这里统一说明一下。其实有一个不错的设备产品可以很轻易地解决这个问题&#xff0c;名为JM-EIP-ECAT网关。接下来作者就从该设备的功能及配置详细说明一下。 一&#xff0c;设备主…

Angular 遍历列表时的key

在Angular中&#xff0c;你可以使用keyvalue管道来遍历对象的键。这里是一个简单的例子&#xff0c;展示了如何在Angular模板中使用它&#xff1a; <div *ngFor"let key of myObject | keyvalue:key">Key: {{ key }} - Value: {{ myObject[key] }} </div&g…

springboot中hutool-core依赖的使用

springboot中hutool-core依赖的使用 依赖安装1、StrUtil.isBlank() 依赖安装 <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-core --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-core</artifactId><ver…

在Android开发中,如何优化onCreate()和onResume()方法以提高应用性能?

在Android开发中&#xff0c;onCreate()和onResume()方法是活动生命周期中非常重要的两个回调方法&#xff0c;它们分别在活动创建和重新获得焦点时被调用。为了提高应用的性能&#xff0c;以下是一些优化这两个方法的策略&#xff1a; 对于onCreate()方法的优化&#xff1a; …

聊聊基于Alink库的主成分分析(PCA)

概述 主成分分析&#xff08;Principal Component Analysis&#xff0c;PCA&#xff09;是一种常用的数据降维和特征提取技术&#xff0c;用于将高维数据转换为低维的特征空间。其目标是通过线性变换将原始特征转化为一组新的互相无关的变量&#xff0c;这些新变量称为主成分&…

TinyMCE一些问题

1.element 在el-dialog中使用tinymce导致富文本弹窗在el-dialog后面的问题 原因是富文本的弹窗层级太低了 在APP.vue中添加样式即可解决 /* 富文本菜单 */ .tox-tinymce-aux {z-index: 9999 !important; }2.element 在el-dialog中点击富文本的功能栏报错 由于 aria-hidden 属…

Midjourney、Sora和硅谷机密-《分析模式》漫谈15

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的Preface&#xff08;前言&#xff09;有这么一句&#xff1a; Kent Beck, Ward Cunningham, and Jim Coplein encouraged me to get involved with the commu…

虚假的互联网信息?不妨从IT的角度理解【景观社会】

博主前言&#xff1a;“我思故我在”&#xff0c;笛卡尔的这一哲学命题&#xff0c;大抵上次还比较熟络的时光还是高中亦或复习考研政治的岁月里。这是一个光怪陆离的社会——或者说网络社会&#xff0c;形形色色的消息充斥在脑海之时&#xff0c;你是否还能认识真正的自己&…

YOLOV8-源码解读-SPP-SPPF

先给出YOLOV8中一键三连卷积模块 def autopad(k, pNone, d1): # kernel, padding, dilation"""Pad to same shape outputs."""if d > 1:k d * (k - 1) 1 if isinstance(k, int) else [d * (x - 1) 1 for x in k] # actual kernel-sizeif…

全国区块链职业技能大赛样题第9套智能合约+数据库表设计

后端源码地址:https://blog.csdn.net/Qhx20040819/article/details/140746050 前端源码地址:https://blog.csdn.net/Qhx20040819/article/details/140746216 智能合约+数据库表设计:https://blog.csdn.net/Qhx20040819/article/details/140746646 nice.sql /* Navicat MySQ…

分布式事务解决方案(一) 2PC、3PC、TCC、Sega

目录 1.绪论 2.2PC 2.1 基本原理 2.1.1 组成 2.1.2 步骤 1.prepare阶段 2.commit阶段 2.2 2PC 存在的问题 2.2.1 阻塞问题 2.2.2 单点故障问题 1. 事务协调器宕机 2.部分数据不一致问题 2.资源管理器宕机 3. 事务协调器和资源管理管理器同时宕机 2.2 实现 2.2.1…

怎么将几个pdf合成为一个pdf?pdf合成为一个的常用方法

在现代的职场和学术环境中&#xff0c;如何将多个独立的PDF文档合并成一个统一的文件已经成为提高工作效率、优化文档管理和促进信息共享的重要手段。PDF格式以其卓越的跨平台兼容性、强大的数据保护能力以及清晰易读的版面设计&#xff0c;在全球范围内得到了广泛的应用和认可…

2-45 基于matlab的递归最小二乘法(RLS)对声音信号去噪

基于matlab的递归最小二乘法&#xff08;RLS&#xff09;对声音信号去噪,并对消噪前后的信号进行FFT分析&#xff0c;对比消噪前后的效果。可替换自己的声音信号进行分析。程序已调通&#xff0c;可直接运行。 2-45 递归最小二乘法&#xff08;RLS&#xff09; FFT分析 - 小红书…

系统移植(七)u-boot移植 ④ trusted版本

文章目录 一、U-boot源码适配&#xff08;一&#xff09;执行make stm32mp15_trusted_defconfig命令进行配置&#xff0c;生成.config文件&#xff08;二&#xff09;执行make menuconfig命令&#xff0c;对u-boot源码进行重新配置1. 对u-boot源码进行配置&#xff0c;移除pmic…

wire和reg的区别

在 Verilog 中&#xff0c;wire 和 reg 是两种不同的数据类型&#xff0c;用于表示信号或变量。它们在 Verilog 中的使用场景和行为有一些区别&#xff1a; ### wire&#xff1a; - wire 类型用于连接组合逻辑电路中的信号&#xff0c;表示电路中的连线或信号传输线。 - wire …

【C++进阶学习】第十弹——哈希的原理与实现——链地址法的原理与讲解

开放地址法&#xff1a;【C进阶学习】第九弹——哈希的原理与实现——开放寻址法的讲解-CSDN博客 前言&#xff1a; 哈希的整体思想就是建立映射关系&#xff0c;前面的开放地址法的讲解中&#xff0c;也对哈希的原理做了详细的讲解&#xff0c;今天就来讲解一下实现哈希的另一…

Java NIO (一)

因工作需要我接触到了netty框架&#xff0c;这让我想起之前为夺高薪而在CSDN购买的Netty课程。如今看来&#xff0c;这套课程买的很值。这套课程中关于NIO的讲解&#xff0c;让我对Tomcat产生了浓厚的兴趣&#xff0c;于是我阅读了Tomcat中关于服务端和客户端之间连接部分的源码…

题解|2024暑期牛客多校04

【原文链接】 比赛链接&#xff1a;2024牛客暑期多校训练营4 A.LCT 题目大意 给定一棵有根树&#xff0c;问按顺序给定的前 i i i 条边组成的森林中&#xff0c;以 c i c_i ci​ 为根的树的深度。 解题思路 按步骤生成森林的过程&#xff0c;与并查集合并的过程一致。 …

Matlab freqz 代码简单实现

相关代码打开matlab源码也可以看到&#xff0c;这里做了简单实现&#xff0c;与源码并不完全一样。 实现代码 [h2 w2] freqzfir(data); [h1 w1] freqz(data); h2h2; h12 [h1, h2];[h4 w4] freqziir(b,a, 2001,true); [h3 w3] freqz(b,a, w4, whole); h4 h4; h34 h…