当前位置: 首页 > news >正文

Luckysheet 实现 excel 多人在线协同编辑(全功能实现增强版)

前言

        感谢大家对 Multi person online edit(多人在线编辑器) 项目的支持,mpoe 项目使用 quill、luckysheet、canvas-editor 实现的 md、excel、word 在线协同编辑,欢迎大家Fork 代码,多多 Start哦~

Multi person online edit 多人协同编辑器项目icon-default.png?t=O83Ahttps://gitee.com/wfeng0/mpoe        经过大家反馈和咨询,还是对 luckysheet 的协同更加感兴趣,但是原项目有些乱,有些功能也没有完善,因此,单独将Luckysheet 抽离成新项目,争取实现完整的协同功能。

        本项目仅实现luckysheet协同哈,使用 sequelize 作为ORM数据库连接,方便大家迁移,同时,也做了兼容,没有数据库的用户,只是不能持久化数据,协同功能不受任何影响。为了规范代码,使用 typescript 构建,没有使用任何前端框架,实现最简单的luckysheet协同增强版。

创建两个实例对象

        由于luckysheet是挂载在window上,因此,同一个页面不能直接创建两个实例对象,但是可以通过 iframe 实现:

 实现效果如下:

初始化协同

        配置Lucky sheet的协同非常简单:

  const options = {allowUpdate: true, // 配置协同功能loadUrl: "/api/loadLuckysheet", // 初始化 celldata 数据updateUrl: WS_SERVER_URL, // 协同服务转发服务// ...other option};

配置 allowUpdate

        是否允许操作表格后的后台更新,与updateUrl配合使用。如果要开启共享编辑,此参数必须设置为true.

配置 loadUrl      

   loadUrl是初始化 celldata 数据的一个http接口请求,底层实现是通过post发送请求,初始化sheet 数据:

$.post(loadurl, {"gridKey" : server.gridKey}, function (d) {})

        因此,需要在服务端创建一个 post 请求的接口,处理并返回数据:

配置 updateUrl

        操作表格后,实时保存数据的websocket地址,此接口也是共享编辑的接口地址,过共享编辑功能,可以实现Luckysheet实时保存数据和多人同步数据,每一次操作都会发送不同的参数到后台,具体的操作类型和参数参见表格操作。

/*** 创建 Web Socket 服务*/
export function createWebSocketServer(port: number) {const wsServer = new WebSocketServer({ port });logger.info(`ws server is running at: ws://localhost:${port}`);wsServer.on("connection", (client) => {console.log("==> user connected");client.on("error", console.error);client.on("close", () => {});client.on("message", (data) => {console.log("received: %s", data);});});
}

        进行数据解析:根据官网的描述,发送给后端的数据默认是经过pako压缩过后的,需要进行解析,转换为可识别对象操作

/*** Pako 数据解析*/
export function unzip(str: string) {const chartData = str.toString().split("").map((i) => i.charCodeAt(0));const binData = new Uint8Array(chartData);const data = pako.inflate(binData);return decodeURIComponent(String.fromCharCode(...Array.from(new Uint16Array(data))));
}

 解析数据如下:

 配置协同数据结构

        上面的讲述的都是 前台向后台发送数据,那么,协同服务应该返回什么数据结构给 luckysheet呢? 根据 luckysheet/src/controller/server.js 中的返回参数分析,协同服务需要按照下列数据返回:

/*** 处理广播给其他客户端事件,客户端接收服务端要求数据结构:* * data: 修改的命令* id: "7a"   websocket的id* username: 用户名(用于显示 xxx 正在编辑)* type: *  # message === '用户退出' 用户退出时,关闭协同编辑时其提示框*  # type == 1 send 成功或失败*  # type == 2 更新数据*  # type == 3 多人操作不同选区("t": "mv")(用不同颜色显示其他人所操作的选区)*  # type == 4 批量指令更新*  # type == 5 showloading*  # type == 6 hideloading*/if (data === "exit") return JSON.stringify({ message: "用户退出", id: userid });// 这里仅做 2 3 类型处理,其他类型自行拓展哈
const info = { data, id: userid, username, type: data.t === "mv" ? 3 : 2 }
return JSON.stringify(info);

 配置上诉后,即可实现初步协同,如下:

Sequelize 

        Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。本项目使用其构建,意在只需要书写表模型,即可完成复杂的 luckysheet数据结构存储。同时,还能检测连接状态,使得没有数据库的用户,也可以体验协同。

class DataBase {private _connected: boolean = false; // 连接状态private _sequelize: Sequelize | null = null; // 连接对象/*** 初始化数据库*/public init() {// 创建连接const URL = `mysql://${user}:${password}@${host}:${port}/${database}`;this._sequelize = new Sequelize(URL, { logging });// 测试连接this._sequelize.authenticate().then(...).catch(...)}
}

初始化模型

        Sequelize 是通过模型进行数据操作的,因此,我们需要提供对应的模型结构:

/*** Worker Books 工作簿模型表*/import { Model, Sequelize } from "sequelize";export class WorkerBookModel extends Model {// 通过 declare 定义模型类型declare gridKey: string;declare title: string;declare lang?: string;// 需要向外提供 注册模型的静态方法static registerModule(sequelize: Sequelize) {WorkerBookModel.init(....)}
}

同步模型

  • Model.sync() - 如果表不存在,则创建该表(如果已经存在,则不执行任何操作)
  • Model.sync({ force: true }) - 将创建表,如果表已经存在,则将其首先删除
  • Model.sync({ alter: true }) - 这将检查数据库中表的当前状态(它具有哪些列,它们的数据类型等),然后在表中进行必要的更改以使其与模型匹配.

        force: true 会导致表数据丢失,请谨慎使用!!!

在这里就不过多介绍 sequelize 相关知识了,大家自行查阅文档哈。

协同存储实现

         Luckysheet 每一次操作都会保存历史记录,用于撤销和重做,如果在表格初始化的时候开启了共享编辑功能,则会通过websocket将操作实时更新到后台。因此,我们根据传递到后台的操作类型,更新数据库状态,不就实现了协同存储了嘛。

单个单元格刷新 

async function v(data: string) {// 1. 解析 rc 单元格const { t, r, c, v, i } = <OperateData>JSON.parse(data);logger.info("[CRDT DATA]:", data);// 纠错判断if (t !== "v") return logger.error("t is not v.");if (isEmpty(i)) return logger.error("i is undefined.");if (isEmpty(r) || isEmpty(c)) return logger.error("r or c is undefined.");// 场景一:单个单元格插入值if (v && v.v && v.m) {// 判断表内是否存在当前记录const exist = await CellDataService.hasCellData(i, r, c);if(exist) CellDataService.updateCellData() else CellDataService.createCellData()}// 场景二:剪切/粘贴到某个单元格 - 会触发两次广播if (v === null) {// 删除该记录await CellDataService.deleteCellData(i, r, c);}// 场景三: 删除单元格内容if (v && !v.v && !v.m){// 删除记录await CellDataService.deleteCellData(i, r, c);}
}

范围单元格刷新 

        上诉是一个标准的范围单元格协同消息,我们需要根据 range row column 和 v 的数组,循环处理每一条数据项:

 // 循环列,取 v 的内容,然后创建记录for (let index = 0; index < v.length; index++) {// 这里面的每一项,都是一条记录for (let j = 0; j < v[index].length; j++) {// 解析内部的 r c 值const item = v[index][j];const r = range.row[0] + index;const c = range.column[0] + j;// 根据 r c 存储数据}}

 

隐藏行/列 行高/列宽处理

        行高列宽及隐藏行列,均触发在 t="cg" 中:

 

边框及合并单元格处理

// k borderInfo 边框处理// {"t":"cg","i":"e73f971d606...","v":[{"rangeType":"range","borderType":"border-all","color":"#000","style":"1","range":[{"row":[0,0],"column":[0,0],"row_focus":0,"column_focus":0,"left":0,"width":73,"top":0,"height":19,"left_move":0,"width_move":73,"top_move":0,"height_move":19}]}],"k":"borderInfo"}// {"t":"cg","i":"e73f971d......","v":[{"rangeType":"range","borderType":"border-all","color":"#000","style":"1","range":[{"row":[2,7],"column":[1,2],"row_focus":2,"column_focus":1,"left":74,"width":73,"top":40,"height":19,"left_move":74,"width_move":147,"top_move":40,"height_move":119,}]}],"k":"borderInfo"}// {"t":"cg","i":"e73f971d......","v":[{"rangeType":"range","borderType":"border-bottom","color":"#000","style":"1","range":[{"left":148,"width":73,"top":260,"height":19,"left_move":148,"width_move":73,"top_move":260,"height_move":19,"row":[13,13],"column":[2,2],"row_focus":13,"column_focus":2}]}],"k":"borderInfo"}if (k === "borderInfo") {// 处理 rangeTypefor (let idx = 0; idx < borderInfo.length; idx++) {const border = borderInfo[idx];const { rangeType, borderType, color, style, range } = border;// 这里能拿到 i range 判断是否存在// declare row_start?: number;// declare row_end?: number;// declare col_start?: number;// declare col_end?: number;const info: ConfigBorderModelType = {worker_sheet_id: i,rangeType,borderType,row_start: range[0].row[0],row_end: range[0].row[1],col_start: range[0].column[0],col_end: range[0].column[1],};const exist = await ConfigBorderService.hasConfigBorder(info);if (exist) {// 更新await ConfigBorderService.updateConfigBorder({config_border_id: exist.config_border_id,...info,color,style: Number(style),});} else {// 创建新的边框记录await ConfigBorderService.createConfigBorder({...info,style: Number(style),color,});}}}

         合并单元格的处理可能麻烦些:

    // 合并单元格 - 又是一个先删除后新增的操作,由luckysheet 前台设计决定的// {"t":"all","i":"e73f971....","v":{"merge":{"1_0":{"r":1,"c":0,"rs":3,"cs":3}},},"k":"config"}// {"t":"all","i":"e73f971....","v":{"merge":{"1_0":{"r":1,"c":0,"rs":3,"cs":3},"9_1":{"r":9,"c":1,"rs":5,"cs":3}},},"k":"config"}// {"t":"all","i":"e73f971....","v":{"merge":{"9_1":{"r":9,"c":1,"rs":5,"cs":3}},},"k":"config"}// 先删除await ConfigMergeService.deleteMerge(i);// 再新增for (const key in v.merge) {if (Object.prototype.hasOwnProperty.call(v.merge, key)) {const { r, c, rs, cs } = v.merge[key];await ConfigMergeService.createMerge({worker_sheet_id: i,r,c,rs,cs,});}}

获取数据的时候,需要处理两个地方: config 及 celldata

      /* eslint-disable */// 4. 查询 merge 数据 - 这里不仅要体现在 config 中,还要体现在 celldata.mc 中const merges = await ConfigMergeService.findAll(worker_sheet_id);merges?.forEach((merge) => {// 拼接 r_c 格式const { r, c } = merge.dataValues;// @ts-ignoretemp.config.merge[`${r}_${c}`] = merge.dataValues;// 配置 celldata mc 属性const currentMergeCell = temp.celldata.find(// @ts-ignore(i) => i.r == r && i.c == c);// @ts-ignoreif (currentMergeCell) currentMergeCell.v.mc = merge.dataValues;});

 

图片及统计图处理

        这块内容还有些前台的东西需要二开,后面会同步更新 git ,大家关注下仓库,start 下。

luckysheet-crdt: Luckysheet 协同增强版(全功能实现)icon-default.png?t=O83Ahttps://gitee.com/wfeng0/luckysheet-crdt

        图片上传,需要使用到两个新的 API:uploadImage、imageUrlHandle,默认情况下,插入的图片是以base64的形式放入sheet数据中,但是图片放入 sheet 中,进行协同传输,会导致node 解析数据堆栈溢出,因此,需要自定义图片上传方法:

    // 处理协同图片上传uploadImage: async (file: File) => {// 此处拿到的是上传的 file 对象,进行文件上传 ,配合 node 接口实现const formData = new FormData();formData.append("image", file);const { data } = await fetch({url: "/api/uploadImage",method: "POST",data: formData,});// *** 关键步骤:需要返回一个地址给 luckysheet ,用于显示图片if (data.code === 200) return Promise.resolve(data.url);else return Promise.resolve("image upload error");},

 

看大家的接口设计哈,如果直接返回能访问的服务器路径,其实不用第二个接口也能实现,这里就都简单介绍一下:

    // 处理上传图片的地址imageUrlHandle: (url: string) => {// 已经是 // http data 开头则不处理if (/^(?:\/\/|(?:http|https|data):)/i.test(url)) {return url;}// 不然拼接服务器路径return SERVER_URL + url;},

        在协同存储上处理如下:

 查询数据库,并处理为 luckysheet 初始化数据类型:

即可实现图片协同存储:

 

统计图的后面再更新哈,还在研究中~ 

总结

1. luckysheet 的协同并不难,很多东西源码底层已经封装好了,我们只需要按照官网说明,处理响应的操作即可;

2. 当然,库还有些没有完善的功能,需要大家自行拓展;

3. 后续会持续更新,关注大家的需求,也会考虑封装一个 npm 包,提供给大家,下载即用;

4. 大家多多start 支持呀~这样才有动力更新哦!

相关文章:

Luckysheet 实现 excel 多人在线协同编辑(全功能实现增强版)

前言 感谢大家对 Multi person online edit(多人在线编辑器) 项目的支持&#xff0c;mpoe 项目使用 quill、luckysheet、canvas-editor 实现的 md、excel、word 在线协同编辑&#xff0c;欢迎大家Fork 代码&#xff0c;多多 Start哦~ Multi person online edit 多人协同编辑器…...

vue 给div增加title属性

省略号 移入显示文字 在很多时候&#xff0c;我们页面上其实有时候展示不出来很多很多文字的&#xff0c;这个时候我们就不得不对这个文字进行处理&#xff0c;但是我们鼠标放到文字上时&#xff0c;还想展示所有的文字&#xff0c;这种方式其实有2种 一Tooltip 文字提示 第一…...

设计模式之工厂模式:从汽车工厂到代码工厂

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” 工厂模式概述 想象一下你走进一家4S店准备买车。作为顾客&#xff0c;你不需要知道汽车是如何被制造出来的&#xff0c;你只需要告诉销售顾问&a…...

人脸识别Adaface之libpytorch部署

目录 1. libpytorch下载2. Adaface模型下载3. 模型转换4. c推理4.1 前处理4.2 推理4.3 编译运行4.3.1 写CMakeLists.txt4.3.2 编译4.3.3 运行 1. libpytorch下载 参考&#xff1a; https://blog.csdn.net/liang_baikai/article/details/127849577 下载完成后&#xff0c;将其解…...

vue3+echarts+websocket分时图与K线图实时推送

一、父组件代码&#xff1a; <template> <div class"chart-box" v-loading"loading"> <!-- tab导航栏 --> <div class"tab-box"> <div class"tab-list"> <div v-for"(item, index) in tabList…...

小程序开发实战项目:构建简易待办事项列表

随着移动互联网的飞速发展&#xff0c;小程序以其便捷性、即用即走的特点&#xff0c;成为了连接用户与服务的重要桥梁。无论是电商平台的购物助手&#xff0c;还是餐饮行业的点餐系统&#xff0c;小程序都在各个领域发挥着巨大的作用。 小程序开发基础 1. 小程序简介 小程序是…...

SD Express 卡漏洞导致笔记本电脑和游戏机遭受内存攻击

Positive Technologies 最近发布的一份报告揭示了一个名为 DaMAgeCard 的新漏洞&#xff0c;攻击者可以利用该漏洞利用 SD Express 内存卡直接访问系统内存。 该漏洞利用了 SD Express 中引入的直接内存访问 (DMA) 功能来加速数据传输速度&#xff0c;但也为对支持该标准的设备…...

前端node环境安装:nvm安装详细教程(安装nvm、node、npm、cnpm、yarn及环境变量配置)

需求&#xff1a;在做前端开发的时候&#xff0c;有的时候 这个项目需要 node 14 那个项目需要 node 16&#xff0c;我们也不能卸载 安装 。这岂不是很麻烦。这个时候 就需要 一个工具 来管理我们的 node 版本和 npm 版本。 下面就分享一个 nvm 工具 用来管理 node 版本。 这个…...

java之集合(详细-Map,Set,List)

1集合体系概述 1.1集合的概念 集合是一种容器&#xff0c;用来装数据的&#xff0c;类似于数组&#xff0c;但集合的大小可变&#xff0c;开发中也非常常用。 1.2集合分类 集合分为单列集合和多列集合 Collection代表单列集合&#xff0c;每个元素&#xff08;数据&#xff…...

常见LeetCode-Saw200

用来记录需要知道见过的题型&#xff1a; LeetCode2-两数相加 说明&#xff1a;以链表的形势给了你每个位的数字&#xff0c;而且是逆序&#xff0c;直接从开头&#xff08;个位&#xff09;遍历相加。带上进位即可。有一个为空就直接计算另一个和进位。 LeetCode-3.无重复字符…...

Unity 制作一个视频播放器(打包后,可在外部编辑并放置新的视频)

效果展示&#xff1a; 在这里&#xff0c;我把视频名称&#xff08;Json&#xff09;和对应的视频资源都放在了StreamingAssets文件夹下&#xff0c;以便于打包后&#xff0c;客户还可以自己在外部增加、删除、修改对应的视频资料。 如有需要&#xff0c;请联细抠抠。...

MySQL-SQL语句

文章目录 一. SQL语句介绍二. SQL语句分类1. 数据定义语言&#xff1a;简称DDL(Data Definition Language)2. 数据操作语言&#xff1a;简称DML(Data Manipulation Language)3. 数据查询语言&#xff1a;简称DQL(Data Query Language)4. 数据控制语言&#xff1a;简称DCL(Data …...

腾讯微信大数据面试题及参考答案

DNS 协议是否使用 UDP? DNS(Domain Name System)协议主要使用 UDP(User Datagram Protocol),但也会使用 TCP(Transmission Control Protocol)。 UDP 是一种无连接的传输协议,它的特点是简单、高效。DNS 在进行域名解析时,大部分情况下使用 UDP。因为 UDP 的开销小,对…...

Python跳动的爱心

系列文章 序号直达链接表白系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4Python李峋同款可写字版跳动的爱心5Python流星雨代码6Python漂浮爱心代码7Python爱心光波代码8Python普通的玫瑰花代码9Python炫酷的玫瑰花代码10Python多…...

计算机启动过程 | Linux 启动流程

注&#xff1a;本文为“计算机启动、 Linux 启动”相关文章合辑。 替换引文部分不清晰的图。 探索计算机的启动过程 Aleksandr Goncharov 2023/04/21 很多人对计算机的启动方式很感兴趣。只要设备开启&#xff0c;这就是魔法开始和持续的地方。在本文中&#xff0c;我们将概…...

反射简单介绍

反射就是从类里拿东西 有的人可能会想为什么不能用io流&#xff0c;从上往下一行一行的读也能获取类中的信息&#xff0c;为什么要用反射呢&#xff1f; 假如我们io流&#xff0c;从左到右一行一行的读取数据&#xff0c;如果碰到局部变量和成员变量同名&#xff0c;怎么区分&a…...

工具篇--GitHub Desktop 使用

文章目录 前言一、GitHub Desktop 的使用&#xff1a;1.1 通过官网下载GitHub Desktop和安装&#xff1a;1.2 安装和使用&#xff1a;1.2.1 填充自己的标识&#xff1a;1.2.3 克隆项目&#xff1a;1.2.4 git 常用忽略项配置&#xff1a; 二、代码的更新和提交&#xff1a;2.1 代…...

单臂路由配置

知识点 单臂路由指在路由器上的一个接口配置子接口&#xff08;逻辑接口&#xff09;来实现不同vlan间通信 路由器上的每个物理接口都可以配置多个子接口&#xff08;逻辑接口&#xff09; 公司的财务部、技术部和业务部有多台计算机&#xff0c;它们使用一台二层交换机进行互…...

河工oj第七周补题题解2024

A.GO LecturesⅠ—— Victory GO LecturesⅠ—— Victory - 问题 - 软件学院OJ 代码 统计 #include<bits/stdc.h> using namespace std;double b, w;int main() {for(int i 1; i < 19; i ) {for(int j 1; j < 19; j ) {char ch; cin >> ch;if(ch B) b …...

卷积的数学原理与作用

一、一维卷积 &#xff08;一&#xff09;定义 数学定义 给定一个输入序列 x [ x 1 , x 2 , ⋯ , x n ] x [x_1,x_2,\cdots,x_n] x[x1​,x2​,⋯,xn​] 和一个卷积核&#xff08;滤波器&#xff09; k [ k 1 , k 2 , ⋯ , k m ] k [k_1,k_2,\cdots,k_m] k[k1​,k2​,⋯,…...

路由介绍.

RIB和FIB Routing Information Base&#xff08;RIB&#xff09;&#xff0c;即路由信息库&#xff0c;是存储在路由器或联网计算机中的一个电子表格或类数据库&#xff0c;它保存着指向特定网络地址的路径信息&#xff0c;包括路径的路由度量值。RIB的主要目标是实现路由协议…...

CTFshow-命令执行(Web29-40)

CTFshow-命令执行(Web29-40) CTFWeb-命令执行漏洞过滤的绕过姿势_绕过空格过滤-CSDN博客 总结rce&#xff08;远程代码执行各种sao姿势&#xff09;绕过bypass_远程命令执行绕过-CSDN博客 对比两者的源代码&#xff0c;我们发现&#xff0c;cat指令把flag.php的内容导出后依…...

MySQL锁的类型有哪些

目录 共享锁(share lock)&#xff1a; 排他锁(exclusivelock)&#xff1a; 表锁(table lock)&#xff1a; 行锁&#xff1a; 记录锁(Record lock)&#xff1a; 页锁&#xff1a; 间隙锁&#xff1a; 基于锁的属性分类&#xff1a;共享锁&#xff0c;排他锁。 基于锁的粒…...

基于 JNI + Rust 实现一种高性能 Excel 导出方案(下篇)

衡量一个人是否幸福&#xff0c;不应看他有多少高兴的事&#xff0c;而应看他是否为小事烦扰。只有幸福的人&#xff0c;才会把无关痛痒的小事挂心上。那些真正经历巨大灾难和深重痛苦的人&#xff0c;根本无暇顾及这些小事的。因此人们往往在失去幸福之后&#xff0c;才会发现…...

关于Python程序消费Kafka消息不稳定问题的处理方法

在使用Python程序消费Kafka消息的过程中&#xff0c;有时会遇到各种不稳定的情况&#xff0c;如自动提交偏移量无效、CommitFailedError错误等。这些问题不仅影响了数据处理的可靠性&#xff0c;还可能导致重复消费或丢失消息。本文将针对这两个常见问题提供详细的解决方案和最…...

【OpenCV】Canny边缘检测

理论 Canny 边缘检测是一种流行的边缘检测算法。它是由 John F. Canny 在 1986 年提出。 这是一个多阶段算法&#xff0c;我们将介绍算法的每一个步骤。 降噪 由于边缘检测易受图像中的噪声影响&#xff0c;因此第一步是使用 5x5 高斯滤波器去除图像中的噪声。我们在前面的章…...

算法-二进制和位运算

一.二进制 &#xff08;1&#xff09;.无符号数&#xff1a; 无符号数是一种数据表示方式&#xff0c;它只表示非负整数&#xff0c;即没有符号位&#xff0c;所有的位都用来表示数值大小。在 C 等编程语言中&#xff0c;常见的无符号类型有 unsigned int、unsigned char 等。…...

OpenAI Chatgpt 大语言模型

OpenAI 一个美国人工智能研究实验室&#xff0c;由非营利组织 OpenAI Inc&#xff0c;和其营利组织子公司 OpenAI LP 所组成。该组织于 2015 年由萨姆阿尔特曼、里德霍夫曼、杰西卡利文斯顿、伊隆马斯克、伊尔亚苏茨克维、沃伊切赫萨伦巴、彼得泰尔等人在旧金山成立&#xff0…...

SpringBoot【九】mybatis-plus之自定义sql零基础教学!

一、前言&#x1f525; 环境说明&#xff1a;Windows10 Idea2021.3.2 Jdk1.8 SpringBoot 2.3.1.RELEASE mybatis-plus的基本使用&#xff0c;前两期基本讲的差不多&#xff0c;够日常使用&#xff0c;但是有的小伙伴可能就会抱怨了&#xff0c;若是遇到业务逻辑比较复杂的sq…...

C#,人工智能,深度学习,目标检测,OpenCV级联分类器数据集的制作与《层级分类器一键生成器》源代码

一、目标识别技术概述 1、摘要 目标检测是计算机视觉中最基本和最具挑战性的问题之一&#xff0c;它试图从自然图像中的大量预定义类别中定位目标实例。深度学习技术已成为直接从数据中学习特征表示的强大策略&#xff0c;并在通用目标检测领域取得了显著突破。鉴于这一快速发…...

人力资源和社会保障部网站/友链网

::v-deep .el-table td, .el-table th.is-leaf {border-top: 1px solid #EBEEF5;border-bottom: 0; } :v-deep .el-table__body{border-bottom: 1px solid #EBEEF5; }...

郑州网站建设网站推广/哪个网站做推广效果好

android:resizeableActivity[“true” | “false”] 如果该属性设置为 true&#xff0c;Activity 将能以分屏和自由形状模式启动。 如果此属性设置为 false&#xff0c;Activity 将不支持多窗口模式。 如果该值为 false&#xff0c;且用户尝试在多窗口模式下启动 Activity&…...

五台网站建设/seo优化方法网站快速排名推广渠道

**前情提要&#xff1a;已可将模型载入gazebo与rviz&#xff0c;且可用按键控制 **《教程 Re:Zero ROS &#xff08;五&#xff09;—— 导入模型&#xff0c;关节控制器》 https://blog.csdn.net/Lovely_him/article/details/107806662 教程 Re:Zero ROS &#xff08;六&#…...

新浪sae安装wordpress/东莞搜索引擎推广

大家在做淘宝的时候&#xff0c;每个小伙伴的店铺都肯定会有新品上架&#xff0c;既然是新品上架&#xff0c;遇到的问题肯定也是非常多的&#xff0c;那么今天我们来讲的是新品上架时如何获得高权重&#xff1f; 第一步&#xff1a;产品上架 一、小伙伴们都知道新的产品上架的…...

企业管理培训课程目录/山东seo推广公司

博主QQ&#xff1a;819594300博客地址&#xff1a;http://zpf666.blog.51cto.com/有什么疑问的朋友可以联系博主&#xff0c;博主会帮你们解答&#xff0c;谢谢支持&#xff01;一、 zabbix简介zabbix是完全开源的工具&#xff0c;整合了cacti和nagios等特性附&#xff1a;SN…...

德宏北京网站建设/云和数据培训机构怎么样

DNS许多服务的基础&#xff0c;如&#xff1a;在网络发送邮件、浏览网页文件等都依赖DNS服务&#xff0c;如果没有DNS的配合&#xff0c;那么网络技术推广与发展必受到限制。既然它这么重要我们就有必要对它的学习更深入一些&#xff0c;下面介绍一些比较重要方面的信息&#x…...