【JS】vis.js使用之vis-timeline使用攻略,vis-timeline在vue3中实现时间轴、甘特图

news2025/7/19 17:20:59

vis.js使用之vis-timeline使用攻略,vis-timeline实现时间轴、甘特图

  • 1、vis-timeline简介
  • 2、安装插件及依赖
  • 3、简单示例
  • 4、疑难问题集合
    • 1. 中文zh-cn本地化
    • 2. 关于自定义class样式无法被渲染
    • 3. 关于双向数据绑定

vis.js是一个基于浏览器的可视化库,它提供了多个组件,包括DataSet, Timeline, Network, Graph2d和Graph3d。该库具有易用性、能够处理大量动态数据和允许数据操作和交互的特点。

vis.js

1、vis-timeline简介

vis-timeline时间轴是一个交互式可视化图表,用于实时可视化时间数据。数据项可以只与某个时间点关联,也可以有开始和结束日期(即一个时间范围)。vis-timeline可以通过拖拽和滚动时间轴自由移动和缩放。可以在时间轴中创建、编辑和删除数据项目。轴上的时间尺度是自动调整的,支持从毫秒到年的尺度。

vis-time时间轴使用常规HTML DOM呈现时间轴和放在时间轴上的项目,这样的好处就是可以使用自定义css样式进行灵活定制。

Timeline地址
vis.js官网https://visjs.org/
vis-timeline官方英文文档https://visjs.github.io/vis-timeline/docs/timeline/
vis-timeline官方示例https://visjs.github.io/vis-timeline/examples/timeline/
vis-timeline的github源码https://github.com/visjs/vis-timeline

可实现效果如下:纵向可以分组,横向可以是时间轴,每个item项目可以自定义内容与样式。
实现效果

2、安装插件及依赖

// vis-timeline包
cnpm install -S vis-timeline

// vis.js提供的可以实现数据双向绑定的包
cnpm install -S vis-data

// 实现时间轴中文的moment.js库的包
cnpm install -S moment

3、简单示例

<template>
  <div class="bindNurseToRoom-container">
    <!-- 时间轴-绑定元素 -->
    <div ref="timelineRef" id="timeline" class="bindNurseToRoom-container"></div>
  </div>
</template>

<script setup lang="ts" name="bindNurseToRoom">
import { onMounted, ref, watch, nextTick, reactive, defineAsyncComponent } from 'vue';
import "vis-timeline/styles/vis-timeline-graph2d.min.css";
import { DataSet } from 'vis-data'; // 为timeline提供双向数据绑定,加快渲染速度
import { Timeline } from "vis-timeline"; //standalone,peer不同的包装方式
import moment from 'moment';
import  "moment/dist/locale/zh-cn.js";
import { ElMessage, ElMessageBox } from 'element-plus';
import { useOperatingRoomApi } from '/@/api/room/operatingRoom';
import { useOperationScheduleStore } from '/@/stores/operationScheduleStore';
const _useOperationScheduleStore = useOperationScheduleStore();

// 引入组件
const BindNurseToRoomDialog = defineAsyncComponent(() => import('./bindNurseToRoomDialog.vue'));

const timelineRef = ref(null);
const bindDialogRef = ref();
// 定义父组件传过来的值
const props = defineProps({
	// 当前操作时间
	operateTime: {
		type: String,
		default: () => '',
	},
	// 配置项
	config: {
		type: Object,
		default: () => {},
	},
});
const curOperateTime = ref(''); // 当前操作日期格式化的字符串 或 undefined 或 ""

let dataList:any = new DataSet([
  // {
  //   id: 1,
  //   content: "手术1",
  //   start: "2023-04-07 08:00",
  //   end: "2023-04-07 10:00",
  //   group: "5a92fde514c2c842f680885b1d31b9b8",
  //   style: "color: white; background-color: #1abc9c;",
  //   idCard: "123456",
  //   patientName: "李秀莲",
  //   doctorName: "李莲",
  //   anaesthesiaType: "局部",
  // },
  // {
  //   id: 2,
  //   content: "手术2",
  //   start: "2023-04-06 10:00",
  //   end: "2023-04-06 12:00",
  //   group: "手术室1",
  //   style: "color: white; background-color: #2ecc71;",
  //   idCard: "12346",
  //   patientName: "李秀",
  //   doctorName: "李莲",
  //   anaesthesiaType: "局部",
  // },
  // {
  //   id: 3,
  //   content: "手术3",
  //   start: "2023-04-06 08:30",
  //   end: "2023-04-06 09:30",
  //   group: "手术室2",
  //   style: "color: white; background-color: #3498db;",
  //   idCard: "1456",
  //   patientName: "莲",
  //   doctorName: "李",
  //   anaesthesiaType: "局部",
  // },
  // {
  //   id: 4,
  //   content: `<div style="display:block;height:100px;background:red;">
  //       123123123213
  //       </div> `, //content接收字符串类型的文本或html
  //   start: "2023-04-06 11:00",
  //   end: "2023-04-06 14:00",
  //   group: "手术室2",
  //   style: "color: white; background-color: #9b59b6;",
  //   idCard: "1236",
  //   patientName: "李的",
  //   doctorName: "李莲时",
  //   anaesthesiaType: "局部",
  // },
  // {
  //   id: 5,
  //   content: `<div style="display:block;height:100px;background:red;">手术5</div> `,
  //   start: "2023-04-06 06:30",
  //   end: "2023-04-06 10:10",
  //   group: "手术室5",
  //   className:'icu',
  //   editable: false, // 给某个特定的设置为不可编辑
  //   idCard: "156",
  //   patientName: "李秀消",
  //   doctorName: "李莲",
  //   anaesthesiaType: "局部",
  // },
]);

const state:any = reactive({
  groups: null, // 手术室分组-new DataSet()格式的数据集
  timeline: null, // 手术室当前排班时间轴-new DataSet()格式的数据集
});

// 监听当前操作日期变化
watch(
	() => props.operateTime,
	(newValue: any) => {
		if (newValue) {
      curOperateTime.value = newValue; // 保存当前操作日期到变量中,以便以后使用。
      nextTick(async () => {
        if(state.timeline){
          // state.timeline.setItems([], { clearNetwork: false });
          // state.timeline.destroy(); // 销毁时间轴
        } 
        await getOperationRoom();
        await renderTimeLine(); // 渲染时间轴
        state.timeline.redraw();
      });
		}
	},
  { immediate : true } //在组件初次加载的时候执行
);

onMounted(async () => {
  state.timeline = new Timeline(
    timelineRef.value as unknown as HTMLElement, //document.getElementById("timeline") as HTMLElement, 
    dataList, 
    {
      locale: 'zh-cn', //moment.locale('zh-cn'), // 时间轴国际化
      editable: {
        add: true,         // 双击添加新项-add new items by double tapping
        updateTime: true,  // 水平拖拉项目-drag items horizontally
        updateGroup: true, // 从一个分组拖拽到另一个分组-drag items from one group to another
        remove: true,       // 通过右上角按钮删除项目-delete an item by tapping the delete button top right
        // overrideItems: false  // allow these options to override item.editable
      },
      selectable: true,
      // height: '730px', // 时间轴高度
      minHeight: 400, // timeline表格的最小高度
      maxHeight: 750, // timeline表格的最大高度
      groupHeightMode: 'fixed', // 指定分组高度: 自动auto, fixed固定, fitItems适应项目
      stack: false, // ture则不重叠
      verticalScroll: true, // 竖向滚动
      orientation: 'top', // 时间轴位置
      showCurrentTime: true, // 显示当前时间
      zoomKey: "ctrlKey", // 缩放按键
      zoomMax: 1000 * 60 * 60 * 24,
      zoomMin: 1000 * 60 * 30,
      moment: function(date:any) {
        return moment(date).locale('zh-cn'); //vis.moment(date).utcOffset('+08:00');
      },
      // 显式将此选项设置为true以完全禁用Timeline的XSS保护
      xss: {
        disabled: true,
      },
      //可以提供模板处理程序。(或许可以直接放插槽?待测试)
      //此处理程序是一个函数,接受项的数据作为第一个参数,项元素作为第二个参数,编辑后的数据作为第三个参数,并输出格式化的HTML:
      template: function (sourceData:any, targetElement:any, parsedData:any) {
        console.log('parsedData: ', parsedData);
        targetElement.className = 'custom-item-template-class'; // 将自定义class写在className属性中
        return `<div class="custom-item ${sourceData.customClassName}">
                  <div class="top">
                    <span>
                      ${moment(sourceData.start).format('YYYY-MM-DD HH:mm:ss').split(' ')[1]}
                      -${moment(sourceData.end).format('YYYY-MM-DD HH:mm:ss').split(' ')[1]}
                    </span> 
                    <span>${sourceData.doctorName}</span>
                  </div>
                  <div class="center-box">
                    <div class="info">
                      <span>${sourceData.patientName}</span> &nbsp;
                      <span>${sourceData.idCard}</span> &nbsp;
                      <span>${sourceData.sex?'男':'女'}</span> &nbsp;
                      <span>${sourceData.age}岁</span>
                    </div>
                    <h3>${sourceData.content}</h3>
                  </div>
                  <div class="nurse-box">
                    <span>${ sourceData?.selectedNurse?.xshs1.name ? sourceData.selectedNurse.xshs1.name :'---' }</span>
                    <span>${ sourceData?.selectedNurse?.xhhs1.name ? sourceData.selectedNurse.xhhs1.name :'---' }</span>
                  </div>
                  <div class="bottom-box">
                    <h4>${sourceData.anaesthesiaType}</h4>
                  </div>
                </div>`;
      },
       tooltip: {
         followMouse: false,
         template: (originalItemData:any, parsedItemData:any) => {
           console.log('hover-parsedItemData: ', parsedItemData);
           return `<div>
                     <p>
                       <span>开始时间:</span>
                       <span>${moment(originalItemData.start).format('YYYY-MM-DD HH:mm:ss')}</span>
                     </p>
                     <p>
                       <span>结束时间:</span>
                       <span>${moment(originalItemData.end).format('YYYY-MM-DD HH:mm:ss')}</span>
                     </p>
                     <p>
                       <span>手术内容:</span>
                       <span>${originalItemData.content}</span>
                     </p>
                   </div>`
         }
       },
      // onAdd(item, callback)在将要添加新项时触发。如果未实现,将使用默认文本内容添加该项。
      onAdd: (originalItemData:any, callback:any) => {
        debugger
        console.log('新增originalItemData: ', originalItemData);
        if (originalItemData.id) {
          originalItemData.customClassName = 'un-submit'; // 未提交状态的样式
          callback(originalItemData); // 成功返回 这行相当于调用了dataList.add(originalItemData)
        }
        else {
          callback(null); // 失败取消
        }
      },
      // onDropObjectOnItem(objectData,Item)在将对象放入现有时间轴项时触发。
      // 当拖动数据中包含target:'item'的对象被放入时间轴项时触发回调函数。
      onDropObjectOnItem: function (objectData:any, item:any) {
        debugger
        if (!item) {
          ElMessage({message: '请拖动护士到对应的手术项目中',type: 'warning'})
          return;
        }
        onDropToItem(objectData, item);
      },
      // onUpdate(item,callback)在项目即将更新时触发(双击item时)。此函数通常会显示用户更改项目的对话框。如果不执行,什么都不会发生。
      // 示例:https://visjs.github.io/vis-timeline/examples/timeline/editing/editingItemsCallbacks.html
      onUpdate: function (item:any, callback:any) {
        if (item.id) {
          callback(item); // send back adjusted item
          bindDialogRef.value.openDialog(item); // 打开弹窗
        }
        else {
          callback(null); // cancel updating the item
        }
      },
      // 当项目被移动时重复触发的回调函数。仅在selectable和editable.updateTime或editable.updateGroup选项都设置为true时才适用
      onMoving: function (item:any, callback:any) {
        console.log('item: ', item);
        item.moving = true;
        callback(item);
      },
      // 当项目即将被删除时触发onRemove(item, callback)。如果未实现,该项将始终被删除。
      onRemove: (item:any, callback:Function) => {
        onDeleteByItemType(item,callback);
      },
    }
  );
});

// 获取医院手术室信息
const getOperationRoom = async () => {
	let { data } = await useOperatingRoomApi().selectAdministrativeOffice();
	if(data.length){
    let temp = data.map((item:any) => {
      return {
        ...item,
        content: item.administrativeOfficeCard,
        style: "color: #fff; background: #5E8DFF;",
      }
    }).sort((a:any, b:any) => {return a.content - b.content});
    state.groups =  new DataSet(temp);
	}
}

// 渲染时间轴timeline = new Timeline(container, items, groups, options);
const renderTimeLine = async () => {
  // 清空数据集
  // dataList.clear();
  dataList = new DataSet([]);
  // 获取当天已经排班的数据
  await getScheduledData();

  // 设置setItems
  state.timeline.setItems(dataList);
  // 更新配置选项
  state.timeline.setOptions({
    min: moment(curOperateTime.value + ' 7:00:00').format('YYYY-MM-DD HH:mm:ss'), // 设置时间轴可见范围的最小日期
    max: moment(curOperateTime.value).endOf('day').format('YYYY-MM-DD HH:mm:ss'), // 设置时间轴可见范围的最大日期
    groupTemplate: (groupData:any, element:any) => {
      element.className = 'custom-group-template-class'; // 将自定义class写在className属性中
      return `<div class="group" title="${groupData.description}">
                <span class="group-id">${groupData.content}</span>
                <span class="group-name">${groupData.administrativeOfficeName}</span>
              </div>`;
    },
  });
  // 跳转到当前时间轴
  state.timeline.moveTo(curOperateTime.value);
  // 设置分组
  state.timeline.setGroups(state.groups);
  // 打印当前数据
  dataList.forEach((element: any) => {
    console.log('---------dataList: ', element);
  });
}


</script>

<style scoped lang="scss">
.bindNurseToRoom-container {
	width: 100%;
  position: relative;
}

// vis-timeline样式
:deep(#timeline){
  .vis-top{
    background-color: #90e0db9c;

    .vis-even,.vis-odd{
      border-left: 1px solid;
    }
  }

  // (此项目必须设置)自定义group分组样式
  .custom-group-template-class{
    height: 160px;
    width: 80px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    .group{
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      .group-id{
        font-size: 22px;
      }
      .group-name{
        font-size: 14px;
        margin-top: 10px;
      }
    }
  }

  // (此项必须设置)自定义item样式
  .custom-item-template-class{
    // color: #fff;
    .custom-item {
      .top{
        font-size: 18px;
        font-weight: bold;
        color: #5E8DFF;
        border-bottom: 1px dashed #C4C4C4;
      }
      .center-box{
        padding: 5px;
      }
      .nurse-box{
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
        justify-content: space-around;

        span{
          width: 40%;
          height: 30px;
          padding: 5px;
          border: 1px dashed #5E8DFF;
          border-radius: 5px;
          text-align: center;
        }
      }
      .bottom-box{
        padding: 5px;
      }
    }
    // 未提交状态的样式
    .un-submit{
      border: 2px solid #698df0;
      padding: 5px;
    }
    .ed-submit{
      border: 2px solid #efb03f;
      padding: 5px;
    }
  }

  // 使用自定义class实现不同手术状态
  .vis-item.icu {
    color: white;
    background-color: rgb(228, 210, 93);
    border-color: darkred;
    height: 100px;
  }
}

// groups样式
:deep(.group-icu){
  background-color: rgba(244, 176, 59, 0.2);
}
</style>


<!-- 
为啥我的template中的自定义的class并没有被渲染到元素中?
你的自定义class选择器写错了:如果在template中自定义了class,但是并没有在CSS样式表中定义,那么这个class将不会生效。
请检查你的CSS样式表中是否已经定义了相应的类选择器,或者将class直接写在style属性中。

vis-timeline对class属性进行了过滤:vis-timeline默认会对content和className等属性进行过滤,以避免XSS攻击。
如果你的class名称被视为可疑字符,那么它将被自动过滤掉。你可以通过在options选项中增加content属性的设置,来打开这个过滤功能:

vis-timeline缓存了渲染数据,导致更新不及时:有时候,即使你已经在代码中正确设置了class属性,但是图表仍然没有反应出来。
这可能是因为vis-timeline缓存了渲染数据,需要手动调用timeline.redraw()方法来更新图表。
你可以在修改了item对象的class之后,手动调用timeline.redraw()方法,以更新图表。
-->

4、疑难问题集合

1. 中文zh-cn本地化

import moment from 'moment';
// 需要引入下方这个文件
import  "moment/dist/locale/zh-cn.js";

网上说的在配置项options中引入locale: moment.locale('zh-cn')无法实现本地化

2. 关于自定义class样式无法被渲染

写了自定义样式后,发现没有在元素中渲染出对应的class,原因有两点:

  • vis-time本身为了防止xss攻击,自动过滤了你写的样式类。需要在options中配置打开xss: {disabled:true,},
  • 你写的样式类需要通过:v-deep()渲染到界面,不写对应的样式,只写class类名是无法渲染class名字

3. 关于双向数据绑定

需要使用let dataList = new DataSet([ ]);

官网真的写的挺详细的,不得不说国外这种开源网站确实非常给力。
有啥疑问可以一起交流哦

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

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

相关文章

深度探索vector

vector是什么 &#xff1f; vector就是一个可以自动扩充的array。 源码解析 vector主要是通过三个指针来维护的&#xff0c;分别是起点&#xff0c;当前终点&#xff0c;以及当前最大空间 sizeof(vector对象) 3 * 指针大小 vector每当遇到空间不同的情况&#xff0c;都会…

Windows逆向安全(一)之基础知识(十三)

Switch语句 先前讲了分支结构的if else形式&#xff0c;除此之外还有一种分支结构&#xff1a;switch 此次就来以反汇编的角度研究switch语句&#xff0c;并与if else进行比较 Switch语句的使用 有关Switch语句在vc6.0中生成的反汇编可分为4种情况&#xff0c;这4种情况的区…

不用科学上网,免费的GPT-4 IDE工具Cursor保姆级使用教程

1、Cursor 编辑器 可以直接官方网站下载&#xff1a;https://www.cursor.so/ &#xff08;这里以Mac为例&#xff09; 这是一款与OpenAI合作并且基于GPT4的新一代辅助编程神器&#xff0c;它支持多种文件类型&#xff0c;支持格式化文本、多种主题、多语言语法高亮、快捷键设…

react-7 组件库 Ant Design Mobile(移动端)

1.安装组件库 npm install --save antd-mobile 常用组件 tabbar 底部导航 Swiper 轮播图&#xff08;走马灯&#xff09; NavBar&#xff08;顶部返回累&#xff09; 配合 Dialog&#xff0c;Toast InfiniteScroll 无限滚动&#xff08;实现下拉刷新&#xff09; Skeleto…

ROS学习第九节——服务通信

1.基本介绍 服务通信较之于话题通信更简单些&#xff0c;理论模型如下图所示&#xff0c;该模型中涉及到三个角色: ROS master(管理者)Server(服务端)Client(客户端) ROS Master 负责保管 Server 和 Client 注册的信息&#xff0c;并匹配话题相同的 Server 与 Client &#…

远程控制电脑的软件哪个比较好用

有多种软件选项可用于远程控制计算机&#xff0c;最适合您的软件选项取决于您的具体需要和要求。 以下是一些最流行的远程控制软件选项及其功能和优势&#xff1a; TeamViewer TeamViewer 是使用最广泛的远程控制软件选项之一。 它具有用户友好的界面&#xff0c;并提供文件传…

Vue核心 Vue简介 初识

Vue核心 Vue简介 初识 1.1.Vue核心 Vue简介 初识 1.1.1.Vue核心 Vue简介 初识 英文官网 中文官网 1.1.2.介绍与描述 Vue 是一套用来动态构建用户界面的渐进式JavaScript框架 构建用户界面&#xff1a;把数据通过某种办法变成用户界面渐进式&#xff1a;Vue可以自底向上逐层…

YOLO V8实战入门篇 | Anaconda3 | ultralytics

目录 一、虚拟环境1.1 Anaconda3 安装1.2 创建适用YOLO V8的虚拟环境 二、YOLO v82.1 YOLO v8简介2.2 下载YOLO v8源码2.3 安装依赖 三、运行目标检测模型 一、虚拟环境 1.1 Anaconda3 安装 https://blog.csdn.net/weixin_42855758/article/details/122795125 参考这个链接&a…

NDK OpenCV 身份证信息离线识别

NDK系列之OpenCV 身份证信息离线识别技术实战&#xff0c;本节主要是通过OpenCV C库&#xff0c;实现身份证信息识别&#xff0c;如身份证号码识别&#xff0c;本节使用到的技术点同样适用于车牌号识别、银行卡号码识别等。 实现效果&#xff1a; 本节主要内容&#xff1a; 1…

数据库底层运行原理之——事务管理器

一般所有关系型数据库内部都有自己的事务机制&#xff0c;进程是如何保证每个查询在自己的事务内执行的&#xff0c;通过这篇文章来简单介绍一下。 我们可以理解为数据库是由多种相互交互的组件构成的&#xff0c;数据库一般可以用如下图形来理解&#xff1a; 事务管理器就是今…

两种方法,计算带地形起伏的地表面积

很多同学会经常计算占地面积, 就会用到投影面积计算和椭球体面积计算; 还有一些,需要去计算表面积, 也就是带地形起伏的地表面积, 这......咋办呢? 我们来介绍两种方法, 计算下面这个区域的地表面积—— 两种方法各有优势, 大家各取所需 方法一:表面体积工具 这…

【hello Linux】进程优先级

目录 1. 基本概念 2. 查看系统进程&#xff1a;&#xff08;包括优先级&#xff09; 2.1 使用命令查看系统进程 2.2 各字段分析 2.3 优先级的修正解释 2.4 使用 top 命令修改优先级 其他概念 Linux&#x1f337; 1. 基本概念 进程的优先权&#xff08; priority&#xff09;&…

Java版企业电子招投标系统源代码之电子招投标系统建设的重点和未来趋势

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及…

网工神器:PNETLab模拟器踩坑过程(一)

0、前言 由于工作需要&#xff0c;想测试一下SD-WAN&#xff0c;手边既没有测试环境又没有测试设备。突然想到为什么不用模拟器测试。经过我一番操作好像发现了新大陆&#xff0c;没想到模拟器的世界发生了翻天覆地的变化。真是“一日学习一日功&#xff0c;一日不学十日空”。…

【IoT】以一款实际产品为例,来谈谈如何做商业计划分析

本篇内容以笔者早期刚转型做产品时,实际负责的一款产品为例,来谈谈如何做产品的商业计划分析。 首先简单介绍一下这款产品: 这是一款电子便签产品,目的是为了替换纸质便签,增加一些智能化的提醒控制。 该产品通过蓝牙与手机端连接,应用端配置好提醒信息后一键同步至产…

鼠标悬停发光按钮,流转边框

提示&#xff1a;css 动画实现&#xff0c;鼠标悬停发光按钮&#xff0c;流转边框。鼠标border可以旋转 前言 提示&#xff1a;以下是本篇文章的代码内容,供大家参考,相互学习 一、html代码 <!DOCTYPE html> <html><head><meta http-equiv"content…

企业信息化建设该怎么做?方向和手段都在这了

企业信息化建设该怎么做&#xff1f; 如果现在是十年前&#xff0c;我一定会说&#xff0c;做信息化需要寻找熟悉不同编程语言、有经验的程序员。 但是现在&#xff0c;如果不是特别复杂的信息化系统&#xff0c;其实公司完全可以使用零代码平台自主开发&#xff0c;不需要再…

[计算机图形学]光线追踪:加速结构(前瞻预习/复习回顾)

一、前言 上篇我们提到了&#xff0c;如果在光线追踪中&#xff0c;我们真的用每个像素发出的光线&#xff0c;以及在场景中弹射之后的光线与场景中的许多模型的上千万个三角形求交那将是一个非常慢的计算过程&#xff0c;所以&#xff0c;本篇我们将介绍一些加速结构来加速这个…

【FTP服务】

目录 一、FTP服务二、FTP服务器安装配置FTP服务的安装匿名访问开启防火墙设置本地用户修改配置文件 以图形化的格式来写入文件 三、设置白名单&#xff0c;黑名单用户 一、FTP服务 作用: 是用来传输文件的协议 端口: FTP服务器默认使用TCP协议的20、21端口与客户端进行通信 2…

OpenHarmony生态贡献获肯定,华秋践行加速硬件创业初心

4月19日,以“开源正当时,共赢新未来”为主题的开放原子开源基金会OpenHarmony开发者大会2023(以下简称“大会”)成功举办。大会现场,来自开放原子开源基金会和OpenHarmony项目的领导与专家、以及共建单位、行业伙伴和社区开发者们共聚一堂。值得信赖的电子产业一站式服务平台华…