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

news2024/9/17 7:22:07

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();
                    // 获取nativeImage
                    const 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 = true
    let 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 = true
    try {
        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 = index
    window.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.coloradmin.cn/o/1957963.html

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

相关文章

嵌入式人工智能(28-基于树莓派4B的语音播报模块-SYN6288)

1、语音播报模块 语音播报在一些嵌入式场景中很常见&#xff0c;广泛应用于游戏篮球机音效语音播报&#xff0c;跑步机语音导航&#xff0c;按摩椅语音操作指引&#xff0c;设备故障提示&#xff0c;设备操作引导语音&#xff0c;车载安全语音警示&#xff0c;公共场所语音提示…

系统移植(七)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…

Executable Code Actions Elicit Better LLM Agents

Executable Code Actions Elicit Better LLM Agents Github: https://github.com/xingyaoww/code-act 一、动机 大语言模型展现出很强的推理能力。但是现如今大模型作为Agent的时候&#xff0c;在执行Action时依然还是通过text-based&#xff08;文本模态&#xff09;后者JSO…

Java Web——第一天

Web开发课程安排 Web标准 Web标准也称为网页标准&#xff0c;由一系列的标准组成&#xff0c;大部分由W3C( World Wide Web Consortium&#xff0c;万维网联盟)负责制定 三个组成部分: HTML:负责网页的结构 (页面素和内容) CSS:负责网页的表现 (页面元素的外观、位置等页面…

Ubuntu 20.04.6 安装 Elasticsearch

1.准备 -- 系统更新 sudo apt update sudo apt upgrade -- 安装vim 文本编辑器 sudo apt install vim-- jdk 版本确认 java -versionjdk 安装可以参照&#xff1a;https://blog.csdn.net/CsethCRM/article/details/140768670 2.官方下载Elasticsearch 官方地址&#xff1a;h…

PPT图表制作

一、表格的底纹 插入→表格→绘制表格→表设计→选择单元格→底纹 二、把一张图片做成九宫格 1. 把一张图片画成九宫格&#xff08;处理过后还是一张图片&#xff0c;但是有框线&#xff09; 绘制33表格→插入图片→全选表格单元格→右键设置形状格式→填充→图片或纹理填充…

Teamcenter RAC开发,创建Item的两种方式

1、如果描述不必填&#xff0c;采用胖客户端的创建方式 newItem itemType.create(newItemId, "", targetTypeComp.getTypeName(), item_name, // "test1", null, null2、如果描述必填&#xff0c;则需要采用SOA的创…

汇川CodeSysPLC教程03-2-6 ModBus TCP

什么是ModBus TCP&#xff1f; ModBus TCP是一种基于TCP/IP协议的工业网络通信协议&#xff0c;常用于工业自动化和控制系统。它是ModBus协议的一个变种&#xff0c;ModBus协议最初由Modicon&#xff08;现在是施耐德电气的一部分&#xff09;在1979年开发。 以下是ModBus TC…

二分类、多分类、多标签分类的评价指标

前言 在机器学习和深度学习中&#xff0c;常见的分类任务可以分为&#xff1a;二分类&#xff08;Binary Classification); 多分类&#xff08;Multi-class Classification); 多标签分类&#xff08;Multi-label Classification); 序列分类 (Sequence Classification); 图分类…

SQL优化相关

文章目录 SQL优化1. 数据插入2. 主键优化页分裂页合并索引设计原则 3. order by 优化4. group by 优化5. limit优化6. count优化7. update 优化 SQL优化 1. 数据插入 当我们需要插入多条数据时候&#xff0c;建议使用批量插入&#xff0c;因为每次插入数据都会执行一条SQL&am…

使用YApi平台来管理接口

快速上手 进入YApi官网&#xff0c;进行注册登录https://yapi.pro/添加项目 3. 添加分类 4. 添加接口 5. 添加参数 添加返回数据 可以添加期望 验证 YAPI&#xff08;Yet Another Practice Interface&#xff09;是一个现代化的接口管理平台&#xff0c;由淘宝团队…

pip install albumentations安装下载遇19kB/s超级慢细水管解决办法

albumentations 是一个用于图像增强的 Python 库&#xff0c;它提供了丰富的图像变换功能&#xff0c;可以用于数据增强&#xff0c;从而提高深度学习模型的泛化能力。 直接安装命令&#xff1a; pip install albumentations但是如果半夜遇到这种19kB/s的下载速度 为头发着想&…

【通信模块】LoRa与LoRaWAN简介

LoRaWAN网络 技象科技相关文章总结&#xff0c;学习笔记&#xff0c;原文链接如下&#xff0c;转载请标明该出处&#xff1a; LORA&#xff1a; https://www.techphant.cn/tag/l-2 LORAWAN&#xff1a;https://www.techphant.cn/tag/l-3 其他&#xff1a;如LAN https://www…

网络通信---TCP协议1

今日内容 三次握手: 指建立tcp连接时&#xff0c;需要客户端和服务端总共发送三次报文确认连接。 四次挥手&#xff1a; 断开一个tcp连接&#xff0c;需要客户端和服务端发送四个报文以确认断开。 编程模型 TCP报文 客户端 服务端

第6篇文献研读生态廊道相关综述

该文发在生态与农村环境学报。该文章写了生态廊道概念的发展历程、生态廊道类型及功能、生态廊道划定的理论和方法、生态廊道的时间和国内大型生态廊道建设实践。 这篇文章可以让大家了解生态廊道的知识。

Rocketmq-5.3.0和对应dashboard的最新版本Docker镜像,本人亲自制作,部署十分方便,奉献给大家

一、Rocketmq 最新版本5.3.0&#xff0c;采用docker镜像安装方式 二、官方rocketmq镜像对内存等参数配置非常不灵活便利 为了快速、灵活部署rocketmq&#xff0c;以及能方便对其内存等参数进行设置&#xff0c;特意制作了关于它的docker镜像。 三、镜像获取 最新rocketmq-5.…

使用二进制来理解数据和二进制的计算

1 使用二进制来理解数据 和人类的思维习惯不同的是&#xff0c;计算机将把所有的东西数字化之后才会进行处理。那么计算机能理解的数字是什么样的呢&#xff1f;其实&#xff0c;在计算机内部&#xff0c;不管是什么信息都使用二进制来保存和处理的。 计算机为什么要用二进制…

芋道微服务全栈开发日记(商品sku数据归类为规格属性)

商品的每一条规格和属性在数据库里都是单一的一条数据&#xff0c;从数据库里查出来后&#xff0c;该怎么归类为对应的规格和属性值&#xff1f;如下图&#xff1a; 在商城模块&#xff0c;商品的单规格、多规格、单属性、多属性功能可以说是非常完整&#xff0c;如下图&#x…

搭建规范化的vue2项目

项目包含的库 Vue2VuexRouterEslintPrettier 环境 vue&#xff1a;2.6.14 eslint&#xff1a;7.32.0 prettier&#xff1a;2.4.1 eslint-plugin-prettier&#xff1a;4.0.0 eslint-plugin-vue&#xff1a;8.0.3 vue/cli&#xff1a;5.0.8 步骤 全局安装cli工具 npm in…

Vue3----扩展 element Plug card

扩展 element Plug card 增加全屏&#xff0c;折叠操作项 核心代码 <template><div class"cc-card-component"><el-card v-if"state.isShow" :class"state.class" :bodyStyle"bodyStyle" :shadow"props.shadow…