excel表格在线编辑(开源版)
文章目录
- 前言
- 一、Luckysheet
- vue3+vite 例子
- `如有启发,可点赞收藏哟~`
前言
本文记录好用的开源在线表格
具体如图显示

另外记录下更名后的univer~,如下图(有兴趣可自行详细了解)
univer


- 在线思维导图

一、Luckysheet
参考gitee
一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。
vue3+vite 例子
vue3+vite
- 引入样式及脚本
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/css/pluginsCss.css" /><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/plugins.css" /><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/luckysheet/dist/css/luckysheet.css" /><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/luckysheet/dist/assets/iconfont/iconfont.css" /><script src="https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js"></script><script src="https://cdn.jsdelivr.net/npm/luckysheet/dist/luckysheet.umd.js"></script>
- 安装所需依赖(
exceljs、file-saver和luckyexcel)
npm i exceljs file-saver luckyexcel -S
- 新建
components/excel.vue
<template><div style="position: absolute; top: 0"><input id="uploadBtn" type="file" @change="loadExcel" /><span>Or Load remote xlsx file:</span><select v-model="selected" @change="selectExcel"><option disabled value="">Choose</option><option v-for="option in options" :key="option.text" :value="option.value">{{ option.text }}</option></select><a href="javascript:void(0)" @click="downloadExcel">Download source xlsx file</a></div><div id="luckysheet"></div><div v-show="isMaskShow" id="tip">Downloading</div>
</template><script setup>
import { ref, onMounted } from 'vue'
import { exportExcel } from './export'
import LuckyExcel from 'luckyexcel'const isMaskShow = ref(false)
const selected = ref('')
const jsonData = ref({})
const options = ref([{ text: 'Money Manager.xlsx', value: 'https://minio.cnbabylon.com/public/luckysheet/money-manager-2.xlsx' },{text: 'Activity costs tracker.xlsx',value: 'https://minio.cnbabylon.com/public/luckysheet/Activity%20costs%20tracker.xlsx'},{text: 'House cleaning checklist.xlsx',value: 'https://minio.cnbabylon.com/public/luckysheet/House%20cleaning%20checklist.xlsx'},{text: 'Student assignment planner.xlsx',value: 'https://minio.cnbabylon.com/public/luckysheet/Student%20assignment%20planner.xlsx'},{text: 'Credit card tracker.xlsx',value: 'https://minio.cnbabylon.com/public/luckysheet/Credit%20card%20tracker.xlsx'},{ text: 'Blue timesheet.xlsx', value: 'https://minio.cnbabylon.com/public/luckysheet/Blue%20timesheet.xlsx' },{text: 'Student calendar (Mon).xlsx',value: 'https://minio.cnbabylon.com/public/luckysheet/Student%20calendar%20%28Mon%29.xlsx'},{text: 'Blue mileage and expense report.xlsx',value: 'https://minio.cnbabylon.com/public/luckysheet/Blue%20mileage%20and%20expense%20report.xlsx'}
])const loadExcel = (evt) => {const files = evt.target.filesif (files == null || files.length == 0) {alert('No files wait for import')return}let name = files[0].namelet suffixArr = name.split('.'),suffix = suffixArr[suffixArr.length - 1]if (suffix != 'xlsx') {alert('Currently only supports the import of xlsx files')return}LuckyExcel.transformExcelToLucky(files[0], function (exportJson, luckysheetfile) {if (exportJson.sheets == null || exportJson.sheets.length == 0) {alert('Failed to read the content of the excel file, currently does not support xls files!')return}console.log('exportJson', exportJson)jsonData.value = exportJsonwindow.luckysheet.destroy()window.luckysheet.create({container: 'luckysheet', //luckysheet is the container idshowinfobar: false,data: exportJson.sheets,title: exportJson.info.name,userInfo: exportJson.info.name.creator})})
}
const selectExcel = (evt) => {const value = selected.valueconst name = evt.target.options[evt.target.selectedIndex].innerTextif (value == '') {return}isMaskShow.value = trueLuckyExcel.transformExcelToLuckyByUrl(value, name, (exportJson, luckysheetfile) => {if (exportJson.sheets == null || exportJson.sheets.length == 0) {alert('Failed to read the content of the excel file, currently does not support xls files!')return}console.log('exportJson', exportJson)jsonData.value = exportJsonisMaskShow.value = falsewindow.luckysheet.destroy()window.luckysheet.create({container: 'luckysheet', //luckysheet is the container idshowinfobar: false,data: exportJson.sheets,title: exportJson.info.name,userInfo: exportJson.info.name.creator})})
}
const downloadExcel = () => {// const value = selected.value;;//// if(value.length==0){// alert("Please select a demo file");// return;// }//// var elemIF = document.getElementById("Lucky-download-frame");// if(elemIF==null){// elemIF = document.createElement("iframe");// elemIF.style.display = "none";// elemIF.id = "Lucky-download-frame";// document.body.appendChild(elemIF);// }// elemIF.src = value;exportExcel(luckysheet.getAllSheets(), '下载')
}// !!! create luckysheet after mounted
onMounted(() => {luckysheet.create({container: 'luckysheet'})
})
</script><style scoped>
#luckysheet {margin: 0px;padding: 0px;position: absolute;width: 100%;left: 0px;top: 30px;bottom: 0px;
}#uploadBtn {font-size: 16px;
}#tip {position: absolute;z-index: 1000000;left: 0px;top: 0px;bottom: 0px;right: 0px;background: rgba(255, 255, 255, 0.8);text-align: center;font-size: 40px;align-items: center;justify-content: center;display: flex;
}
</style>
- 新建
exports.js
// import { createCellPos } from './translateNumToLetter'
import Excel from 'exceljs'import FileSaver from 'file-saver'const exportExcel = function(luckysheet, value) {// 参数为luckysheet.getluckysheetfile()获取的对象// 1.创建工作簿,可以为工作簿添加属性const workbook = new Excel.Workbook()// 2.创建表格,第二个参数可以配置创建什么样的工作表if (Object.prototype.toString.call(luckysheet) === '[object Object]') {luckysheet = [luckysheet]}luckysheet.forEach(function(table) {if (table.data.length === 0) return true// ws.getCell('B2').fill = fills.const worksheet = workbook.addWorksheet(table.name)const merge = (table.config && table.config.merge) || {}const borderInfo = (table.config && table.config.borderInfo) || {}// 3.设置单元格合并,设置单元格边框,设置单元格样式,设置值setStyleAndValue(table.data, worksheet)setMerge(merge, worksheet)setBorder(borderInfo, worksheet)return true})// return// 4.写入 bufferconst buffer = workbook.xlsx.writeBuffer().then(data => {// console.log('data', data)const blob = new Blob([data], {type: 'application/vnd.ms-excel;charset=utf-8'})console.log("导出成功!")FileSaver.saveAs(blob, `${value}.xlsx`)})return buffer
}var setMerge = function(luckyMerge = {}, worksheet) {const mergearr = Object.values(luckyMerge)mergearr.forEach(function(elem) {// elem格式:{r: 0, c: 0, rs: 1, cs: 2}// 按开始行,开始列,结束行,结束列合并(相当于 K10:M12)worksheet.mergeCells(elem.r + 1,elem.c + 1,elem.r + elem.rs,elem.c + elem.cs)})
}var setBorder = function(luckyBorderInfo, worksheet) {if (!Array.isArray(luckyBorderInfo)) return// console.log('luckyBorderInfo', luckyBorderInfo)luckyBorderInfo.forEach(function(elem) {// 现在只兼容到borderType 为range的情况// console.log('ele', elem)if (elem.rangeType === 'range') {let border = borderConvert(elem.borderType, elem.style, elem.color)let rang = elem.range[0]// console.log('range', rang)let row = rang.rowlet column = rang.columnfor (let i = row[0] + 1; i < row[1] + 2; i++) {for (let y = column[0] + 1; y < column[1] + 2; y++) {worksheet.getCell(i, y).border = border}}}if (elem.rangeType === 'cell') {// col_index: 2// row_index: 1// b: {// color: '#d0d4e3'// style: 1// }const { col_index, row_index } = elem.valueconst borderData = Object.assign({}, elem.value)delete borderData.col_indexdelete borderData.row_indexlet border = addborderToCell(borderData, row_index, col_index)// console.log('bordre', border, borderData)worksheet.getCell(row_index + 1, col_index + 1).border = border}// console.log(rang.column_focus + 1, rang.row_focus + 1)// worksheet.getCell(rang.row_focus + 1, rang.column_focus + 1).border = border})
}
var setStyleAndValue = function(cellArr, worksheet) {if (!Array.isArray(cellArr)) returncellArr.forEach(function(row, rowid) {row.every(function(cell, columnid) {if (!cell) return truelet fill = fillConvert(cell.bg)let font = fontConvert(cell.ff,cell.fc,cell.bl,cell.it,cell.fs,cell.cl,cell.ul)let alignment = alignmentConvert(cell.vt, cell.ht, cell.tb, cell.tr)let value = ''if (cell.f) {value = { formula: cell.f, result: cell.v }} else if (!cell.v && cell.ct && cell.ct.s) {// xls转为xlsx之后,内部存在不同的格式,都会进到富文本里,即值不存在与cell.v,而是存在于cell.ct.s之后// value = cell.ct.s[0].vcell.ct.s.forEach(arr => {value += arr.v})} else {value = cell.v}// style 填入到_value中可以实现填充色let letter = createCellPos(columnid)let target = worksheet.getCell(letter + (rowid + 1))// console.log('1233', letter + (rowid + 1))for (const key in fill) {target.fill = fillbreak}target.font = fonttarget.alignment = alignmenttarget.value = valuereturn true})})
}var fillConvert = function(bg) {if (!bg) {return {}}// const bgc = bg.replace('#', '')let fill = {type: 'pattern',pattern: 'solid',fgColor: { argb: bg.replace('#', '') }}return fill
}var fontConvert = function(ff = 0,fc = '#000000',bl = 0,it = 0,fs = 10,cl = 0,ul = 0
) {// luckysheet:ff(样式), fc(颜色), bl(粗体), it(斜体), fs(大小), cl(删除线), ul(下划线)const luckyToExcel = {0: '微软雅黑',1: '宋体(Song)',2: '黑体(ST Heiti)',3: '楷体(ST Kaiti)',4: '仿宋(ST FangSong)',5: '新宋体(ST Song)',6: '华文新魏',7: '华文行楷',8: '华文隶书',9: 'Arial',10: 'Times New Roman ',11: 'Tahoma ',12: 'Verdana',num2bl: function(num) {return num === 0 ? false : true}}// 出现Bug,导入的时候ff为luckyToExcel的vallet font = {name: typeof ff === 'number' ? luckyToExcel[ff] : ff,family: 1,size: fs,color: { argb: fc.replace('#', '') },bold: luckyToExcel.num2bl(bl),italic: luckyToExcel.num2bl(it),underline: luckyToExcel.num2bl(ul),strike: luckyToExcel.num2bl(cl)}return font
}var alignmentConvert = function(vt = 'default',ht = 'default',tb = 'default',tr = 'default'
) {// luckysheet:vt(垂直), ht(水平), tb(换行), tr(旋转)const luckyToExcel = {vertical: {0: 'middle',1: 'top',2: 'bottom',default: 'top'},horizontal: {0: 'center',1: 'left',2: 'right',default: 'left'},wrapText: {0: false,1: false,2: true,default: false},textRotation: {0: 0,1: 45,2: -45,3: 'vertical',4: 90,5: -90,default: 0}}let alignment = {vertical: luckyToExcel.vertical[vt],horizontal: luckyToExcel.horizontal[ht],wrapText: luckyToExcel.wrapText[tb],textRotation: luckyToExcel.textRotation[tr]}return alignment
}var borderConvert = function(borderType, style = 1, color = '#000') {// 对应luckysheet的config中borderinfo的的参数if (!borderType) {return {}}const luckyToExcel = {type: {'border-all': 'all','border-top': 'top','border-right': 'right','border-bottom': 'bottom','border-left': 'left'},style: {0: 'none',1: 'thin',2: 'hair',3: 'dotted',4: 'dashDot', // 'Dashed',5: 'dashDot',6: 'dashDotDot',7: 'double',8: 'medium',9: 'mediumDashed',10: 'mediumDashDot',11: 'mediumDashDotDot',12: 'slantDashDot',13: 'thick'}}let template = {style: luckyToExcel.style[style],color: { argb: color.replace('#', '') }}let border = {}if (luckyToExcel.type[borderType] === 'all') {border['top'] = templateborder['right'] = templateborder['bottom'] = templateborder['left'] = template} else {border[luckyToExcel.type[borderType]] = template}// console.log('border', border)return border
}function addborderToCell(borders, row_index, col_index) {let border = {}const luckyExcel = {type: {l: 'left',r: 'right',b: 'bottom',t: 'top'},style: {0: 'none',1: 'thin',2: 'hair',3: 'dotted',4: 'dashDot', // 'Dashed',5: 'dashDot',6: 'dashDotDot',7: 'double',8: 'medium',9: 'mediumDashed',10: 'mediumDashDot',11: 'mediumDashDotDot',12: 'slantDashDot',13: 'thick'}}// console.log('borders', borders)for (const bor in borders) {// console.log(bor)if (borders[bor].color.indexOf('rgb') === -1) {border[luckyExcel.type[bor]] = {style: luckyExcel.style[borders[bor].style],color: { argb: borders[bor].color.replace('#', '') }}} else {border[luckyExcel.type[bor]] = {style: luckyExcel.style[borders[bor].style],color: { argb: borders[bor].color }}}}return border
}function createCellPos(n) {let ordA = 'A'.charCodeAt(0)let ordZ = 'Z'.charCodeAt(0)let len = ordZ - ordA + 1let s = ''while (n >= 0) {s = String.fromCharCode((n % len) + ordA) + sn = Math.floor(n / len) - 1}return s
}export {exportExcel
}
运行后预览显示,可导入,下载

如有启发,可点赞收藏哟~
相关文章:
excel表格在线编辑(开源版)
文章目录 前言一、Luckysheetvue3vite 例子如有启发,可点赞收藏哟~ 前言 本文记录好用的开源在线表格 具体如图显示 另外记录下更名后的univer~,如下图(有兴趣可自行详细了解) univer 在线思维导图 一、Luckysheet 参考git…...
17.字符串处理函数——字符串比较函数
文章目录 前言一、题目描述 二、解题 程序运行代码 总结 前言 本系列为字符串处理函数编程题,点滴成长,一起逆袭。 一、题目描述 二、解题 程序运行代码 #include<stdio.h> #include<string.h> int main() {char *str1 "hello wo…...
【面试HOT200】二叉树——深度优先搜索篇
系列综述: 💞目的:本系列是个人整理为了秋招面试的,整理期间苛求每个知识点,平衡理解简易度与深入程度。 🥰来源:材料主要源于【CodeTopHot200】进行的,每个知识点的修正和深入主要参…...
价值投资选股的方法
价值投资法是一种长期投资策略,其核心思想是寻找被市场低估的股票,即股票的市场价格低于其内在价值。这种策略认为,投资者应该关注公司的基本面,如盈利能力、成长潜力、财务状况等,而不是短期的市场波动。以下是价值投…...
java中如何将mysql里面的数据取出来然后通过stream流的方式进行数据处理代码实例?
在 Java 中使用 Stream 流的方式从 MySQL 数据库中取出数据并进行处理,你可以通过 JDBC(Java Database Connectivity)来实现。下面是一个简单的代码示例: import java.sql.*; import java.util.stream.Stream; public class MySQ…...
C++服务器 支持http、tcp protobuf、websocket,linux开源框架 零依赖轻松编译部署 Reactor
开源地址: https://github.com/crust-hub/tubekit/tree/main Github:https://github.com/gaowanlu 诚招有兴趣的小伙伴加入开发维护 Tubekit The C TCP server framework based on the Reactor model continues to implement POSIX thread pool, Epoll, non blocking IO, obj…...
1688API接口系列,1688开放平台接口使用方案(商品详情数据+搜索商品列表+商家订单类)
1688商品详情接口是指1688平台提供的API接口,用于获取商品详情信息。通过该接口,您可以获取到商品的详细信息,包括商品标题、价格、库存、描述、图片等。 要使用1688商品详情接口,您需要先申请1688的API权限,并获取ac…...
CentOS服务器网页版Rstudio-server及R包批量安装最佳实践
CentOS服务器安装网页版Rstudio-server及R包批量安装 以下为CentOS 7/8的Rstudio-server安装、配置和R包安装操作 1. 软件包安装 Centos 7安装 # 下载安装包,大小115.14 MB wget -c https://download2.rstudio.org/server/centos7/x86_64/rstudio-server-rhel-…...
centos7内核升级(k8s基础篇)
1.查看系统内核版本信息 uname -r 2.升级内核 2.1更新yum源仓库 yum -y update更新完成后,启用 ELRepo 仓库并安装ELRepo仓库的yum源 ELRepo 仓库是基于社区的用于企业级 Linux 仓库,提供对 RedHat Enterprise (RHEL) 和 其他基于 RHEL的 Linux 发行…...
数据结构与算法设计分析——NP完全理论
目录 一、P类问题与NP类问题的定义二、常见的NP类问题(一)旅行商问题(TSP)(二)哈密尔顿回路问题(三)判断回路问题(四)图的着色问题(五)…...
AGNES层次聚类
已知数据集D中有9个数据点,分别是(1,2),(2,3),(2,1), (3,1),(2,4),(3,5),(4,3),(1,5),(4,2)。要求: (1)采用层次聚类的聚集算法进行聚类,k2。 (2)距离计算采用欧几里得距离。 (3)簇之间的距离采用单链接方…...
HCIP —— 双点重发布 + 路由策略 实验
目录 实验拓扑: 实验要求: 实验配置: 1.配置IP地址 2.配置动态路由协议 —— RIP 、 OSPF R1 RIP R4 OSPF R2 配置RIP、OSPF 双向重发布 R3配置RIP、OSPF 双向重发布 3.查询路由表学习情况 4.使用路由策略控制选路 R2 R3 5.检…...
Python标准库:datetime模块【侯小啾python领航班系列(二十五)】
Python标准库:datetime模块【侯小啾python领航班系列(二十五)】 大家好,我是博主侯小啾, 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ…...
新版idea如何开启多台JVM虚拟机
1.看看自己的项目 2.可能开始的时候啥也没有,就点Run Configuration Type 3.再点击Edit Configurations... 4.点击号添加SpringBoot 5.主类选择一下,一般就一个,点他选了就行。 6.然后点击Modify Options 选择添加add VM Options 7.点击appl…...
软件工程单选多选补充
2. 4. 5. 6. 7. 8. 9. 10. 12。 13....
6-66.时间
本题要求输入小时、分钟和秒数,并将其输出。针对时间表示中出现的异常进行处理。例如小时数不应超过23,分钟不应超过59,秒数不应超过59。此外,以上三个变量均应大于等于0。 输入样例: 在这里给出三组输入。例如&…...
面试多线程八股文十问十答第一期
面试多线程八股文十问十答第一期 作者:程序员小白条,个人博客 相信看了本文后,对你的面试是有一定帮助的! ⭐点赞⭐收藏⭐不迷路!⭐ 1.ThreadLocal如何实现线程安全 Java的ThreadLocal是一个线程本地变量࿰…...
Mybatis 操作续集(结合上文)
当我们增加一个数据之后,如果我们想要获取它的 Id 进行别的操作,我们该如何获取 Id 呢? 用那个Options package com.example.mybatisdemo.mapper;import com.example.mybatisdemo.model.UserInfo; import org.apache.ibatis.annotations.*;import java.util.List;Mapper pub…...
JVM基础篇:垃圾回收
目录 1.前言 1.1C/C的内存管理 1.2Java的内存管理 2.方法区的回收 3.堆回收 3.1引用计数法和可达性分析法 3.2五种对象引用 强引用 软引用 弱引用 虚引用 终结器引用 3.3垃圾回收算法评价标准 ①吞吐量 ②最大暂停时间 ③堆使用效率 3.4垃圾回收算法 ①标记清…...
蓝桥杯ACwing习题
题目 :https://www.acwing.com/problem/content/4409/ 解析 :根据题目我们可以知道 问的是方案数 那么首先就想到了 dp 仔细想一下 发现类似于蒙德里安的梦想那道状态压缩的题 , 所以我们先考虑怎么定义 f[i][j] f[i][j] 表示的是 已经放了…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
ubuntu中安装conda的后遗症
缘由: 在编译rk3588的sdk时,遇到编译buildroot失败,提示如下: 提示缺失expect,但是实测相关工具是在的,如下显示: 然后查找借助各个ai工具,重新安装相关的工具,依然无解。 解决&am…...
