【Vue+ElementUI】Table表格实现自定义表头展示+表头拖拽排序(附源码)

news2024/11/17 4:59:02

效果图

在这里插入图片描述
在这里插入图片描述

因项目采用的是Vue2,所以这个功能目前采用的是Vue2的写法。
Vue3请自行修改扩展代码;或收藏关注帖子,后续Vue3项目如有用到会在本帖子更新修改。

安装vuedraggable(拖拽插件)

cnpm i vuedraggable

先说用法,下方附全源码

引入自定义表头组件

import indicatorTable from "@/components/indicatorTable/index.vue";

使用:(传参说明已在下方标识)

<indicatorTable
  ref="rois"
  :defaultArr="columns"
  :cardDataProp="cardDataProp"
  cacheKeyProp="keyROI"
  @propData="propsTableHander"
  currenKey="ROT"
/>

props参数说明:(均为必传字段)

// ref:用于调用子组件方法。
// columns:表头数据,例如:
[{
 prop: "cost_platform",
 label: "广告金",
}]

// cardDataProp:可选表头复选框,列如:
cardDataProp: [
  {
    title: "指标", // 每一项的分类title标题,详见第一张效果图
    checkboxes: [...columns], // columns这个就是上面的一样
  },
],

// cacheKeyProp:储存的key名,名字自定义来,避免缓存的key一样就行,列如:
cacheKeyProp="keyROI"

// propData:回调方法,用于更新表头,接受函数,直接表头columns数据 = 参数即可

// currenKey:保存的指标key,避免缓存的key一样就行。

页面table使用方法,需用循环:

<el-table
  v-loading="loading"
  :data="tableList"
  border
  @sort-change="tableSort"
  :height="tableHeight"
  ref="tableRef"
>
  <el-table-column
    v-for="item in columns"
    :prop="item.prop"
    :label="item.label"
    :width="item.width"
    align="center"
    sortable="custom"
    :show-overflow-tooltip="true"
  >
  </el-table-column>
</el-table>

上面表格的参数不用多说了吧,除非你不会前端!

附源码(拿来直接用!只要参数没问题!)

如遇到报错、不显示等问题,一定是参数不对!已自测 无任何报错或警告信息!
如需要Vue3版本,自行开发或私信,有空定会帮助!
新建组件直接复制:

<template>
  <div class="indicator-all-box">
    <el-popover placement="bottom" width="300" trigger="click">
      <div class="add-custom-indicator-container">
        <el-button type="primary" @click="addUserDefinedIndicators">
          新增自定义指标
        </el-button>
        <div class="indicator-list">
          <ul>
            <li
              v-for="(item, index) in pointerArr"
              :key="index"
              :class="currenPointIndex == index ? 'active-li' : ''"
              @click="pointClick(item, index)"
            >
              <div class="flex-indicator-item">
                <span>{{ item.title }}</span>
                <div class="right-indicator">
                  <i
                    class="el-icon-edit"
                    @click.stop.prevent="pointIndexHander(item, 'edit')"
                  ></i>
                  <i
                    class="el-icon-delete"
                    @click.stop.prevent="pointIndexHander(item, 'delete')"
                  ></i>
                </div>
              </div>
            </li>
          </ul>
        </div>
      </div>
      <el-button slot="reference" type="success">自定义指标</el-button>
    </el-popover>

    <!-- 弹窗自定义 -->
    <el-dialog :title="openTitle" :visible.sync="dialogVisible" width="70%">
      <div class="customize-indicator-data-container">
        <div class="card-checkbox-content-left">
          <el-card
            v-for="(item, index) in cardData"
            :key="index"
            class="box-card"
          >
            <div slot="header" class="clearfix">
              <span>{{ item.title }}</span>
              <el-checkbox
                v-model="item.selectedAll"
                @change="handleSelectAll(item)"
                :indeterminate="isIndeterminate(item)"
                style="float: right"
                >全选</el-checkbox
              >
            </div>
            <div class="check-card-item">
              <el-checkbox-group
                ref="checkboxGroup"
                v-model="selectedCheckboxes"
              >
                <el-checkbox
                  v-for="(checkbox, idx) in item.checkboxes"
                  :key="idx"
                  :label="checkbox.label"
                  >{{ checkbox.label }}</el-checkbox
                >
              </el-checkbox-group>
            </div>
          </el-card>
        </div>
        <div class="sort-view-dx">
          <el-divider>排序</el-divider>
          <div class="sort-row">
            <draggable
              v-if="selectedCheckboxes.length > 0"
              v-model="selectedCheckboxes"
              animation="300"
            >
              <p v-for="(item, index) in selectedCheckboxes" :key="index">
                {{ item }}
              </p>
            </draggable>
            <el-empty v-else></el-empty>
          </div>
        </div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addPointerSubmit">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import draggable from "vuedraggable";

export default {
  name: "indicatorTable",
  components: {
    draggable,
  },
  props: {
    // 默认指标
    defaultArr: {
      type: Array,
      required: true,
    },
    // 可选指标
    cardDataProp: {
      type: Array,
      required: true,
    },
    // 存储指标key
    cacheKeyProp: {
      type: String,
      required: true,
    },
    // 存储的索引key名
    currenKey: {
      type: String,
      required: true,
    },
  },
  data() {
    return {
      // 弹窗show
      dialogVisible: false,
      // 全部指标数组
      cardData: this.cardDataProp,
      // 勾选指标
      selectedCheckboxes: [],
      // 弹框title
      openTitle: "添加",
      // 下拉指标列表
      pointerArr: [],
      // 获取当前编辑item
      editItem: null,
      // 传出去的prop数组
      emitArr: null,
      // 当前选择的指标
      currenPointIndex: null,
    };
  },
  computed: {
    Local() {
      return {
        get(key) {
          const value = localStorage.getItem(key);
          if (value == "[]") {
            return null;
          } else {
            return value !== null ? JSON.parse(value) : null;
          }
        },
        set(key, value) {
          localStorage.setItem(key, JSON.stringify(value));
        },
        remove(key) {
          localStorage.removeItem(key);
        },
      };
    },
    // 指标指定label排序
    sortColumns() {
      return function (data, sort) {
        if (data) {
          return data.sort(
            (a, b) => sort.indexOf(a.label) - sort.indexOf(b.label)
          );
        }
      };
    },
    // 获取选指标label
    filterCheckbox() {
      return function (data, isSort = false, sortData) {
        if (data) {
          let filteredCheckboxes = [];
          this.cardData.forEach((item) => {
            item.checkboxes.forEach((checkbox) => {
              if (data.arrayCheck.includes(checkbox.label)) {
                filteredCheckboxes.push(checkbox);
              }
            });
          });
          // 获取后是否排序
          if (isSort) {
            return this.sortColumns(filteredCheckboxes, sortData);
          } else {
            return filteredCheckboxes;
          }
        }
      };
    },
  },
  created() {
    // this.Local.remove("displayType");
    this.getPointData("init");
  },
  methods: {
    // 存储key索引
    storeSetCurrentIndex(type = "set") {
      if (type === "set") {
        const getIndexObj = this.Local.get("pointIndex") || {};
        getIndexObj[this.currenKey] = this.currenPointIndex;
        this.Local.set("pointIndex", getIndexObj);
      } else {
        return this.Local.get("pointIndex") || {};
      }
    },
    // 选择当前指标
    pointClick(row, index) {
      if (this.currenPointIndex != index) {
        this.currenPointIndex = index;
        // 存储当前点击指标index
        this.storeSetCurrentIndex("set");
        const checkData = this.filterCheckbox(
          row,
          true,
          this.pointerArr[this.currenPointIndex].arrayCheck
        );
        this.$emit("propData", checkData);
      }
    },
    // 扩展方法-倍数ROI处理
    // roiDisposeFn() {
    //   const getPonit = this.Local.get(this.cacheKeyProp);
    //   const displayType = this.Local.get("displayType");
    //   const prointArrItem = this.pointerArr[this.currenPointIndex];
    //   const updatedArray = prointArrItem.arrayCheck.map((item) => {
    //     if (
    //       displayType == 2 &&
    //       item.startsWith("ROI") &&
    //       !item.includes("倍数")
    //     ) {
    //       return item + "倍数";
    //     } else if (displayType != 2 && item.includes("倍数")) {
    //       return item.replace("倍数", "");
    //     }
    //     return item;
    //   });
    //   const labelCheckBoxAll = this.filterCheckbox({
    //     arrayCheck: updatedArray,
    //   }).map((item) => item.label);
    //   if (prointArrItem.arrayCheck !== labelCheckBoxAll) {
    //     getPonit[this.currenPointIndex].arrayCheck = labelCheckBoxAll;
    //     this.Local.set(this.cacheKeyProp, getPonit);
    //     this.pointerArr[this.currenPointIndex].arrayCheck = labelCheckBoxAll;
    //   }
    // },
    // 获取-更新指标
    getPointData(type) {
      const getPonit = this.Local.get(this.cacheKeyProp);
      if (getPonit) {
        this.pointerArr = getPonit;
        this.currenIndexNob();
        const prointArrItem = this.pointerArr[this.currenPointIndex];
        this.roiDisposeFn();
        const checkData = this.filterCheckbox(
          this.pointerArr[this.currenPointIndex],
          true,
          prointArrItem.arrayCheck
        );
        if (checkData) {
          this.$emit("propData", checkData);
        }
      } else if (!getPonit && type !== "init") {
        // 如果是空
        this.Local.remove(this.cacheKeyProp);
        this.$emit("propData", []);
      } else {
        // 如果默认的
        if (this.defaultArr && type === "init" && this.pointerArr.length <= 0) {
          const arrs = JSON.parse(JSON.stringify(this.defaultArr));
          const labelsArray = arrs.map((item) => item.label);
          this.currenIndexNob();
          this.pointerArr.push({
            title: "默认指标",
            arrayCheck: labelsArray,
          });
          const prointArrItem = this.pointerArr[this.currenPointIndex];
          const checkData = this.filterCheckbox(
            prointArrItem,
            true,
            labelsArray
          );
          this.$emit("propData", checkData);
        }
      }
    },
    // 编辑-删除指标
    pointIndexHander(item, type) {
      if (type === "edit") {
        this.openTitle = "编辑";
        this.selectedCheckboxes = item.arrayCheck;
        this.editItem = item;
        this.dialogVisible = true;
      } else {
        const itemToDelete = this.pointerArr.find(
          (ls) => ls.title === item.title
        );
        if (itemToDelete) {
          const indexToDelete = this.pointerArr.indexOf(itemToDelete);
          if (indexToDelete > -1) {
            this.pointerArr.splice(indexToDelete, 1);
            this.Local.set(this.cacheKeyProp, this.pointerArr);
            // 删除当前行更新,否则不更新
            if (indexToDelete === this.currenPointIndex) {
              this.getPointData();
            } else {
              this.currenIndexNob();
            }
          }
        }
      }
    },
    // 全选当前指标
    handleSelectAll(item) {
      item.checkboxes.forEach((checkbox) => {
        const checkboxIndex = this.selectedCheckboxes.indexOf(checkbox.label);
        if (item.selectedAll && checkboxIndex === -1) {
          this.selectedCheckboxes.push(checkbox.label);
        } else if (!item.selectedAll && checkboxIndex !== -1) {
          this.selectedCheckboxes.splice(checkboxIndex, 1);
        }
      });
    },
    // 全选状态判断
    isIndeterminate(item) {
      const selectedLabels = this.selectedCheckboxes;
      const allLabels = item.checkboxes.map((checkbox) => checkbox.label);
      const selectedCount = selectedLabels.filter((label) =>
        allLabels.includes(label)
      ).length;
      item.selectedAll = selectedCount === allLabels.length;
      return selectedCount > 0 && selectedCount < allLabels.length;
    },
    // 指定索引
    currenIndexNob() {
      const getIndexObj = this.storeSetCurrentIndex("get");
      this.currenPointIndex = getIndexObj[this.currenKey];
      if (!this.currenPointIndex) {
        this.currenPointIndex = 0;
      } else {
        if (this.pointerArr.length <= 1) {
          this.currenPointIndex = 0;
        } else {
          this.currenPointIndex = getIndexObj[this.currenKey] || 0;
        }
      }
      this.storeSetCurrentIndex("set");
    },
    // 添加指标
    addPointerSubmit() {
      this.dialogVisible = false;
      this.emitArr = this.filterCheckbox(
        {
          arrayCheck: this.selectedCheckboxes,
        },
        true,
        this.selectedCheckboxes
      );
      const dataItem = {
        title: "",
        arrayCheck: this.selectedCheckboxes,
      };
      if (this.openTitle === "添加") {
        this.$prompt("请输入指标名", "提示", {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          closeOnClickModal: false,
          inputValidator: (value) => {
            if (!value) {
              return "不能为空!";
            }
          },
          beforeClose: (action, instance, done) => {
            if (action === "confirm") {
              const isDuplicate = this.pointerArr.some(
                (item) => item.title === instance.inputValue
              );
              if (isDuplicate) {
                this.$message.error("已存在相同指标名");
                return false;
              } else {
                done();
              }
            } else {
              done();
            }
          },
        }).then(({ value }) => {
          dataItem.title = value;
          if (this.pointerArr && Array.isArray(this.pointerArr)) {
            const updatedData = [...this.pointerArr, dataItem];
            this.Local.set(this.cacheKeyProp, updatedData);
          } else {
            const newData = [dataItem];
            this.Local.set(this.cacheKeyProp, newData);
          }
          this.$emit("propData", this.emitArr);
          this.getPointData();
        });
      } else {
        const editIndex = this.pointerArr.findIndex(
          (item) => item.title === this.editItem.title
        );
        if (editIndex !== -1) {
          (dataItem.title = this.editItem.title),
            (this.pointerArr[editIndex] = dataItem);
          this.Local.set(this.cacheKeyProp, this.pointerArr);
        }
        this.$emit("propData", this.emitArr);
      }
    },
    // 新增自定义指标
    addUserDefinedIndicators() {
      this.openTitle = "添加";
      this.selectedCheckboxes = [];
      this.dialogVisible = true;
    },
  },
};
</script>

<style lang="scss" scoped>
.indicator-all-box {
  float: right;
  margin-right: 5px;
}
.indicator-list {
  ul {
    padding: 0;

    li {
      padding: 10px 0;
      border-bottom: 1px solid #e1e1e1;
    }
  }

  .flex-indicator-item {
    display: flex;
    justify-content: space-between;
    padding: 0 15px;

    .right-indicator {
      i {
        padding-left: 10px;
        display: inline-block;
        font-size: 16px;
        cursor: pointer;
      }
    }
  }
}

.box-card {
  margin-bottom: 10px;
}

.el-divider {
  margin: 10px 0;
}

.rihgt-all-check {
  float: right;
  padding: 3px 0;
}

.el-checkbox {
  margin-bottom: 10px;
}

.customize-indicator-data-container {
  display: flex;
  min-height: 60vh;

  .card-checkbox-content-left {
    max-height: 600px;
    overflow-y: scroll;
    flex: 1;
  }

  .sort-view-dx {
    width: 300px;
    margin-left: 15px;
    .sort-row {
      height: 60vh;
      overflow-y: scroll;
      p {
        background-color: #fdfdfd;
        height: 32px;
        line-height: 32px;
        border: 1px solid #ebebeb;
        padding: 0 10px;
        margin: 5px 0;

        &:hover {
          cursor: move;
        }
      }
    }
  }
}

.active-li {
  background-color: #efefef;
}
</style>

上方注释扩展方法说明:
比如你上方有筛选条件需要关联切换的,拿我自己的例子,见顶部ROI区域
他筛选条件有一个ROI、ROI倍数的筛选。然后字段展示是ROI123456…等,是循环的数量。切换ROI倍数的时候 表头原有的ROI需要变成ROI倍数 以及prop也一样要变化。
列如顶部ROI附加复选框的方法:
在这里插入图片描述

this.cardDataProp[1] = {
  title: "ROI指标",
  checkboxes: Array.from(
    { length: this.queryParams.displayNum },
    (_, i) => ({
      prop: `roi${i + 1}${
        this.queryParams.displayType == 1 ? "_rate" : ""
      }`,
      label: `ROI${i + 1}${
        this.queryParams.displayType == 2 ? "倍数" : ""
      }`,
    })
  ),
};

筛选条件选择切换displayType类型后调用 this.$refs.rois.getPointData("init"); 刷新表头

以上根据了选项displayType变化label和prop 但又是属于同一个label表头 只是字段不一样的 或者要用循环的,可采用这种方式,扩展方法自己研究…估计没有其他人需要用这个扩展的,就注释了,不用的可以删掉!

在这里插入图片描述
感谢你的阅读,如对你有帮助请收藏+关注!
只分享干货实战精品从不啰嗦!!!
如某处不对请留言评论,欢迎指正~
博主可收徒、常玩QQ飞车,可一起来玩玩鸭~

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

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

相关文章

Mysql8的优化(DBA)

Mysql8的优化 1、Mysql的安装优化1.1 修改配置参数&#xff08;命令行、配件文件&#xff09;1.1.1 命令行修改配置参数1.1.2 参数持久化1.1.3 Mysql多实例启动&#xff0c;以及配置密码文件 1.2 查询表的相关参数&#xff0c;以及表空间管理 2、Mysql高级优化&#xff08;SQL&…

使用Opencv库直接进行人脸检测

import cv2abs_path cv2.__file__ xml_path abs_path.rsplit("/",1)[0] "/data/haarcascade_frontalface_default.xml"# 加载人脸检测器 face_cascade cv2.CascadeClassifier(xml_path)# 加载图像 img cv2.imread(/media/datasets/face/liuyigei_duo.…

逆向分析 FSViewer 并写出注册机

逆向分析 FSViewer 并写出注册机 FSViewer是一款老牌的图片管理查看编辑软件, 个人使用免费, 商用收费 本文将逆向分析FSViewer 7.5版本的注册算法并编写注册机 0. 前言 最近在整理之前的资料, 发现了一篇几年前刚学逆向那会儿写的文章, 是跟着看雪一位大牛的文章做的, 但逆向…

三井住友保险中国区信息技术部负责人陈婧,将出席“ISIG-RPA超级自动化产业发展峰会”

3月16日&#xff0c;第四届「ISIG中国产业智能大会」将在上海中庚聚龙酒店拉开序幕。本届大会由苏州市金融科技协会指导&#xff0c;企智未来科技&#xff08;RPA中国、AIGC开放社区、LowCode低码时代&#xff09;主办。大会旨在聚合每一位产业成员的力量&#xff0c;深入探索R…

Linux:预备

计算机结构基础 操作系统: 内核 (管理软硬件) shell(给用户使用操作系统的方式) 操作系统的目标 对硬件抽象 原因:操作系统是对软硬件资源管理的应用软件抽象:内存管理, 进程管理, 文件管理, 驱动管理软件:驱动程序(给软件提供访问硬件的软件)硬件:磁盘(对应文件), 网卡等隔离…

【C++】什么是类与对象?

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:C ⚙️操作环境:Visual Studio 2022 面向对象概述 面向对象是一种符合人类思维习惯的编程思想。现实生活中存在各种形态不同的事物,这些事物之间存在着各种各样的联系。在程序中使用对象来映射现实中的事物,使用对象的关…

基于SpringBoot+Vue+ElementUI+Mybatis前后端分离管理系统超详细教程(四)——前后端数据交互

经过前面几个章节的学习我们掌握了&#xff1a; 1、如何使用Vue快速搭建前端工程化项目&#xff0c;并结合elementUI优化了界面&#xff1b; 基于SpringBootVueElementUIMybatis前后端分离管理系统超详细教程&#xff08;一&#xff09; 基于SpringBootVueElementUIMybatis前后…

软件测试入门

文章目录 一、入门1. 软件2. 软件基本组成3. 软件产生过程4. 软件测试5. 软件测试目的&#x1f3c6; 小结 二、测试主流技能1. 功能测试2. 自动化测试3. 接口测试4. 性能测试&#x1f3c6; 小结 三、测试分类1. 按测试阶段划分2. 按代码可见度划分&#x1f3c6; 小结 三、质量模…

信息系统项目管理师003:信息化(1信息化发展—1.1信息与信息化—1.1.3 信息化)

文章目录 1.1.3 信息化1.信息化内涵2.信息化体系3.信息化趋势 要点总结 1.1.3 信息化 信息化是一个过程&#xff0c;与工业化、现代化一样&#xff0c;是一个动态变化的过程。信息化是指培养、发展以计算机为主的智能化工具为代表的新生产力&#xff0c;并使之造福于社会的历史…

通过sqoop把hive数据到mysql,脚本提示成功,mysql对应的表中没有数

1、脚本执行日志显示脚本执行成功&#xff0c;读写数量不为0 2、手动往Mysql对应表中写入数据十几秒后被自动删除了 问题原因&#xff1a; 建表时引擎用错了&#xff0c;如下图所示 正常情况下应该用InnoDB

7-4 哲哲打游戏(Python)

哲哲是一位硬核游戏玩家。最近一款名叫《达诺达诺》的新游戏刚刚上市&#xff0c;哲哲自然要快速攻略游戏&#xff0c;守护硬核游戏玩家的一切&#xff01; 为简化模型&#xff0c;我们不妨假设游戏有 N 个剧情点&#xff0c;通过游戏里不同的操作或选择可以从某个剧情点去往另…

偶极子和环形天线的辐射机理仿真分析

目录 0 引言 1 偶极子天线的辐射因素分析 1.1 偶极子天线模型设计 1.2 谐振点的出现规律 1.3 天线尺寸对辐射的影响 1.4 天线角度对辐射的影响

浅显易懂:WinForms、WPF和Electron的区别和优缺点

在开发桌面应用的时候&#xff0c;WinForms、WPF和Electron是绕不过去的三个技术栈&#xff0c;本文就详细据介绍了三者的区别和优缺点&#xff0c;帮助老铁们做个抉择。 一、winform wpf Electron 三者区别 WinForms、WPF和Electron是三种不同的框架和技术&#xff0c;用于开…

计算机中丢失缺少mfc100.dll文件该如何解决?

当你打开某个应用程序时&#xff0c;有时候会遇到一个“mfc100.dll丢失”或找不到mfc100.dll的错误信息提示。这种情况表明你的计算机缺少一个名为mfc100.dll的动态链接库文件。这个文件是由Microsoft VC 2010 Redistributable Package提供的&#xff0c;它是一组可重用的组件&…

【校园导航小程序】2.0版本 静态/云开发项目 升级日志

演示视频 【校园导航小程序】2.0版本 静态/云开发项目 演示 首页 重做了首页&#xff0c;界面更加高效和美观 校园指南页 新增了 “校园指南” 功能&#xff0c;可以搜索和浏览校园生活指南 地图页 ①弃用路线规划插件&#xff0c;改用SDK开发包。可以无阻通过审核并发布…

Linux的top命令解析

Top命令是什么 TOP命令是Linux下常用的性能分析工具&#xff0c;能够实时显示系统中各个进程的资源占用状况。 TOP是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止.比较准确的说,top命令提供了实时的对系…

qml中toolbox控件、ComboBox控件、PlainText实现及美化

一. 内容简介 qml中toolbox控件、ComboBox控件、PlainText实现及美化 二. 软件环境 2.1vsCode 2.2Anaconda version: conda 22.9.0 2.3pytorch 安装pytorch(http://t.csdnimg.cn/GVP23) 2.4QT 5.14.1 新版QT6.4,&#xff0c;6.5在线安装经常失败&#xff0c;而5.9版本…

相对于 Linux,Windows Server 存在的意义是什么?

相对于 Linux&#xff0c;Windows Server 存在的意义是什么&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「Linux 的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给…

写作文的ai的软件有吗?分享4款热门的软件!

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经渗透到我们生活的方方面面&#xff0c;包括写作领域。许多AI工具如今能够帮助我们快速、高效地创作文章&#xff0c;无论是新闻稿、广告文案还是博客文章&#xff0c;它们都能提供有力的支持。今天&#…

linux安装todesk

xunilToDesk远程桌面软件-免费安全流畅的远程连接电脑手机ToDesk远程控制软件是一款稳定流畅的远程控制电脑手机连接软件,可远程桌面办公,远程协助运维.采用端对端加密,让每一次远程访问都安全可靠。https://www.todesk.com/linux.htmlToDesk远程控制软件是一款稳定流畅的远程控…