Vue实现JSON字符串格式化编辑器组件

相信很多同学都用过网上的在线JSON格式化工具来将杂乱的JSON数据转换成易于我们阅读和编辑的格式。那么,你有没有想过自己动手实现一个这样的工具呢?今天,我将介绍如何使用Vue.js来构建一个简单的JSON格式化工具。

功能简述

  • 支持格式化JSON字符串
  • 支持去除字符串中的空格
  • 支持全屏操作
  • 实时展示格式化状态
  • 控制台展示成功和失败的详情,支持错误定位
  • 编辑器精准计算字符串的行号

效果图展示

默认

全屏

功能介绍

按钮

其他

1、自动补全

输入”(“、”{“、”[“将会自动补全另一半

2、自动删除

删除括号时也会自动删除另一半

3、括号匹配

点击括号会高亮另一半括号,方便定位

4、支持ctrl+z撤销和ctrl+y重做功能

5、编辑器根据字符串的换行计算行号并展示

代码

vue文件
<!--JsonEditor.vue-->
<template>
  <div ref="center" id="editor_body" :style="{ height: editorHeight, width: editorWidth }">
    <div style="height: 80%">
      <div class="tool_slider">
        <div style="display: flex; align-items: center">
          <img
            src="@/assets/icons/format.svg"
            class="icon_hover"
            @click="prettyFormat(viewJsonStr)"
            title="格式化"
          />
          <div style="height: 18px; border: 1px solid #858585; margin: 0 3px"></div>
          <img
            src="@/assets/icons/clearLine.svg"
            class="icon_hover"
            @click="viewJsonStr = viewJsonStr.replace(/\s+/g, '')"
            title="去除空格"
          />
          <div
            style="
              display: flex;
              align-items: center;
              border-left: 2px solid #858585;
              height: 18px;
              margin: 0 3px;
              padding: 0 3px;
            "
          >
            <img
              src="@/assets/icons/full.svg"
              v-if="!isFullScreen"
              class="icon_hover"
              @click="fullScreen"
              title="全屏"
            />
            <img
              src="@/assets/icons/closeFull.svg"
              v-else
              class="icon_hover"
              @click="fullScreen"
              title="退出"
            />
          </div>
        </div>
        <div style="display: flex; align-items: center">
          <img
            src="@/assets/icons/success.svg"
            title="格式正确"
            v-if="isPass"
            style="height: 20px; width: 20px"
          />
          <img
            src="@/assets/icons/error.svg"
            title="格式错误"
            v-else
            style="height: 17px; width: 17px"
          />
        </div>
      </div>
      <div class="edit-container">
        <textarea
          wrap="off"
          cols="1"
          id="leftNum"
          disabled
          onscroll="document.getElementById('rightNum').scrollTop = this.scrollTop;"
        ></textarea>
        <textarea
          ref="myTextarea"
          id="rightNum"
          :key="isFullScreen"
          style="width: 100%"
          placeholder="请输入JSON字符串"
          onscroll="document.getElementById('leftNum').scrollTop = this.scrollTop;"
          :value="viewJsonStr"
          @click="handleClick"
          @input="handleTextareaInput1"
        />
      </div>
    </div>
    <div id="console">{{ jsonObj }}</div>
  </div>
</template>

<script lang="ts" setup>
  import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
  import { cloneDeep } from 'lodash-es';
  import {
    handleBackspace,
    handleClick,
    handleClickEnter,
    handleTabKey,
    handleTextareaInput,
    setAutoKey,
  } from '@/components/JsonEditor';

  const emit = defineEmits(['update:value']);

  const props = defineProps({
    value: {
      type: String,
      default: '',
    },
    width: {
      type: String,
      default: '1000px',
    },
    height: {
      type: String,
      default: '400px',
    },
  });

  const viewJsonStr: any = ref(props.value);
  const editorWidth: any = ref(JSON.parse(JSON.stringify(props.width)));
  const editorHeight: any = ref(JSON.parse(JSON.stringify(props.height)));

  // 自动补全
  function handleTextareaInput1(event) {
    handleTextareaInput(viewJsonStr, event);
  }

  const isPass = ref(true);
  watch(
    () => viewJsonStr.value,
    (newValue) => {
      calculateNum(newValue);
      emit('update:value', newValue);
    },
  );

  const num = ref('');

  function calculateNum(value) {
    let lineBbj: any = document.getElementById('leftNum');
    num.value = '';
    let str = value;
    if (str === null || str === undefined) {
      str = '';
    }
    str = str.replace(/\r/gi, '');
    str = str.split('\n');
    let n = str.length;
    if (n.toString().length > 3) {
      lineBbj.style.width = n.toString().length * 10 + 'px';
    } else {
      lineBbj.style.width = '30px';
    }

    for (let i = 1; i <= n; i++) {
      if (document.all) {
        num.value += i + '\r\n'; //判断浏览器是否是IE
      } else {
        num.value += i + '\n';
      }
    }
    lineBbj.value = num.value;
  }

  // 预览对象
  const jsonObj = computed(() => {
    const str = cloneDeep(viewJsonStr.value);
    // 如果输入的全是数字,JSON.parse(str)不会报错,需要手动处理一下
    const onlyNumber = /^\d+$/.test(str);
    const dom = document.getElementById('console');
    function setColor(color) {
      if (dom) {
        dom.style.color = color;
      }
    }
    if (str) {
      try {
        if (onlyNumber) {
          setColor('red');
          isPass.value = false;
          return getCurrentTime() + str + ' is not valid JSON';
        }
        setColor('black');
        isPass.value = true;
        if (JSON.parse(str)) {
          setColor('green');
          return `${getCurrentTime()}校验通过`;
        }
      } catch (e: any) {
        isPass.value = false;
        setColor('red');
        if (e.message?.match(/position\s+(\d+)/)) {
          const location = e.message?.match(/position\s+(\d+)/)[1];
          const str1 = str.substring(0, location).trim();
          const str2 = str1.split('\n');
          const message = e.message.substring(0, e.message.indexOf('position'));
          // 如果当前行或者前一行有'['
          if (str2[str2.length - 1]?.includes('[')) {
            const { line, column } = getLineAndColumn(str1, str1.length - 1);
            return `${message} at line ${line},column ${column}`;
          }
          const { line, column } = getLineAndColumn(str, location);
          return `${getCurrentTime()}${message} at line ${line},column ${column}`;
        } else {
          return getCurrentTime() + str + ' is not valid JSON';
        }
      }
    } else {
      return null;
    }
  });

  // 获取当前时间
  function getCurrentTime() {
    let now = new Date(); // 获取当前日期和时间
    let hours = now.getHours(); // 获取小时
    let minutes: string | number = now.getMinutes(); // 获取分钟
    let seconds: string | number = now.getSeconds(); // 获取秒
    let period = hours >= 12 ? '下午' : '上午'; // 判断是上午还是下午

    // 将小时转换为12小时制
    hours = hours % 12 || 12;

    // 格式化分钟和秒,确保它们是两位数
    minutes = minutes < 10 ? '0' + minutes : minutes;
    seconds = seconds < 10 ? '0' + seconds : seconds;

    // 构造最终的时间字符串
    let currentTime = period + hours + ':' + minutes + ':' + seconds;

    return '【' + currentTime + '】 ';
  }

  //计算错误信息所在行列
  function getLineAndColumn(str, index) {
    let line = 1;
    let column = 1;
    for (let i = 0; i < index; i++) {
      if (str[i] === '\n') {
        line++;
        column = 1;
      } else {
        column++;
      }
    }
    return { line, column };
  }

  //json格式美化
  function prettyFormat(str) {
    try {
      // 设置缩进为2个空格
      str = JSON.stringify(JSON.parse(str), null, 4);
      str = str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
      viewJsonStr.value = str.replace(
        /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
        function (match) {
          return match;
        },
      );
    } catch (e) {
      console.log('异常信息:' + e);
    }
  }

  const center = ref();
  const isFullScreen = ref(false);

  // 添加或删除全屏属性

  function fullScreen() {
    if (center.value) {
      if (center.value.className.includes('fullScreen')) {
        editorHeight.value = JSON.parse(JSON.stringify(props.height));
        editorWidth.value = JSON.parse(JSON.stringify(props.width));
        center.value.className = center.value.className.replace(' fullScreen', '');
        isFullScreen.value = false;
      } else {
        editorHeight.value = '100vh';
        editorWidth.value = '100vw';
        center.value.className += ' fullScreen';
        isFullScreen.value = true;
      }
    }
  }

  const myTextarea: any = ref(null);

  function handleKeyDown(event) {
    if (myTextarea.value) {
      if (event.key === 'Backspace') {
        handleBackspace(viewJsonStr, event);
      } else if (event.key === 'Enter') {
        handleClickEnter(viewJsonStr, event);
      } else if (event.key === 'Tab') {
        handleTabKey(event);
      } else if (event.key === 'Escape') {
        if (isFullScreen.value) {
          fullScreen();
        }
      }
    }
  }

  // 符号自动补全以及选中文本后输入符号自动包裹
  function getMouseCheck(event) {
    setAutoKey(viewJsonStr, event);
  }

  onMounted(() => {
    window.addEventListener('keydown', handleKeyDown);
    document.addEventListener('keydown', getMouseCheck);
    calculateNum(props.value);
  });
  onBeforeUnmount(() => {
    window.removeEventListener('keydown', handleKeyDown);
    document.removeEventListener('keydown', getMouseCheck);
  });
</script>

<style scoped lang="less">
  #editor_body {
    border: 1px solid #d9d9d9;
    border-radius: 4px;
    padding: 5px;
    box-sizing: border-box;
  }
  .tool_slider {
    padding-left: 5px;
    padding-right: 5px;
    display: flex;
    width: 100%;
    box-sizing: border-box;
    justify-content: space-between;
    align-items: center;
    height: 25px;
    border: 1px solid #d9d9d9;
    border-bottom: 0;
  }
  .icon_hover {
    height: 20px;
    width: 20px;
    cursor: pointer;
    &:hover {
      color: #5c82ff;
    }
  }

  #leftNum {
    overflow: hidden;
    padding: 3px 2px;
    height: 100%;
    width: 30px;
    line-height: 22px;
    font-size: 13px;
    color: rgba(0, 0, 0, 0.25);
    font-weight: bold;
    resize: none;
    text-align: center;
    outline: none;
    border: 0;
    background: #f5f7fa;
    box-sizing: border-box;
    border-right: 1px solid;
    font-family: 微软雅黑;
  }

  #rightNum {
    white-space: nowrap;
    height: 100%;
    padding: 3px;
    line-height: 22px;
    box-sizing: border-box;
    resize: none;
    border: 0;
    font-family: 微软雅黑;
    &::-webkit-scrollbar {
      width: 5px;
      height: 5px;
      background-color: #efeae6;
    }
    &:focus-visible {
      outline: none;
    }
    &:hover {
      border: 0;
    }
    &:focus {
      border: 0;
    }
  }

  .leftBox {
    height: 100%;
    text-align: left;
  }

  .edit-container {
    height: calc(100% - 25px);
    width: 100%;
    box-sizing: border-box;
    border: 1px solid #d9d9d9;
    display: flex;
  }

  .fullScreen {
    position: fixed;
    z-index: 9999;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    background-color: #f5f7fa;
  }
  #console {
    padding: 12px;
    box-sizing: border-box;
    height: calc(20% - 5px);
    margin-top: 5px;
    width: 100%;
    background-color: white;
    border: 1px solid #d9d9d9;
    overflow: auto;
    font-family: 微软雅黑;
    text-align: left;
  }
</style>
配置文件
/*index.ts*/
import { nextTick } from 'vue';

// 获取文本框的值
export const handleTextareaInput = (viewJsonStr, event) => {
  const textarea = event.target;
  const newValue = textarea.value;
  viewJsonStr.value = newValue;
};

// 设置自动补全
export const setAutoKey = (viewJsonStr, event) => {
  const textarea: any = document.getElementById('rightNum');
  if (event.key === "'" || event.key === '"') {
    event.preventDefault(); // 阻止默认行为
    const selectedText = textarea.value.substring(textarea.selectionStart, textarea.selectionEnd);
    const newText = `${event.key}` + selectedText + `${event.key}`;
    const cursorPosition = textarea.selectionStart + 1;
    textarea.value =
      textarea.value.substring(0, textarea.selectionStart) +
      newText +
      textarea.value.substring(textarea.selectionEnd);
    textarea.setSelectionRange(cursorPosition, cursorPosition);
  } else if (event.key === '(') {
    event.preventDefault(); // 阻止默认行为
    const selectedText = textarea.value.substring(textarea.selectionStart, textarea.selectionEnd);
    const newText = '(' + selectedText + ')';
    const cursorPosition = textarea.selectionStart + 1;
    textarea.value =
      textarea.value.substring(0, textarea.selectionStart) +
      newText +
      textarea.value.substring(textarea.selectionEnd);
    textarea.setSelectionRange(cursorPosition, cursorPosition);
  } else if (event.key === '[') {
    event.preventDefault(); // 阻止默认行为
    const selectedText = textarea.value.substring(textarea.selectionStart, textarea.selectionEnd);
    const newText = '[' + selectedText + ']';
    const cursorPosition = textarea.selectionStart + 1;
    textarea.value =
      textarea.value.substring(0, textarea.selectionStart) +
      newText +
      textarea.value.substring(textarea.selectionEnd);
    textarea.setSelectionRange(cursorPosition, cursorPosition);
  } else if (event.key === '{') {
    event.preventDefault(); // 阻止默认行为
    const selectedText = textarea.value.substring(textarea.selectionStart, textarea.selectionEnd);
    const newText = '{' + selectedText + '}';
    const cursorPosition = textarea.selectionStart + 1;
    textarea.value =
      textarea.value.substring(0, textarea.selectionStart) +
      newText +
      textarea.value.substring(textarea.selectionEnd);
    textarea.setSelectionRange(cursorPosition, cursorPosition);
  }
  viewJsonStr.value = textarea.value;
};
/*------------------------------------------------括号高亮------------------------------------------------------------*/
const findOpeningBracketIndex = (text, startIndex, char) => {
  const openingBrackets = {
    ']': '[',
    '}': '{',
    ')': '(',
  };
  let count = 0;
  for (let i = startIndex; i >= 0; i--) {
    if (text.charAt(i) === char) {
      count++;
    } else if (text.charAt(i) === openingBrackets[char]) {
      count--;
      if (count === 0) {
        return i;
      }
    }
  }
  return -1;
};

const findClosingBracketIndex = (text, startIndex, char) => {
  const closingBrackets = {
    '[': ']',
    '{': '}',
    '(': ')',
  };
  let count = 0;
  for (let i = startIndex; i < text.length; i++) {
    if (text.charAt(i) === char) {
      count++;
    } else if (text.charAt(i) === closingBrackets[char]) {
      count--;
      if (count === 0) {
        return i;
      }
    }
  }
  return -1;
};
const isBracket = (char) => {
  return ['[', ']', '{', '}', '(', ')'].includes(char);
};
// 点击括号寻找对应另一半
export const handleClick = (event) => {
  const textarea: any = document.getElementById('rightNum');
  const { selectionStart, selectionEnd, value } = textarea;
  const clickedChar = value.charAt(selectionStart);
  if (isBracket(clickedChar)) {
    const openingBracketIndex = findOpeningBracketIndex(value, selectionStart, clickedChar);
    const closingBracketIndex = findClosingBracketIndex(value, selectionStart, clickedChar);
    if (openingBracketIndex !== -1) {
      textarea.setSelectionRange(openingBracketIndex, openingBracketIndex + 1);
    } else if (closingBracketIndex !== -1) {
      textarea.setSelectionRange(closingBracketIndex, closingBracketIndex + 1);
    }
  }
};
/*键盘事件*/
export function handleClickEnter(viewJsonStr, event) {
  if (event.key == 'Enter') {
    const textarea = event.target;
    const cursorPosition: any = textarea.selectionStart; // 获取光标位置
    const value = textarea.value;
    if (
      (value[cursorPosition - 1] === '{' && value[cursorPosition] == '}') ||
      (value[cursorPosition - 1] === '[' && value[cursorPosition] == ']')
    ) {
      textarea.value = value.slice(0, cursorPosition) + '\n' + value.slice(cursorPosition);
      textarea.setSelectionRange(cursorPosition, cursorPosition);
      viewJsonStr.value = textarea.value;
      // 将光标移动到插入的空格后面
      setTimeout(() => {
        handleTabKey(syntheticEvent);
      }, 30);
    }
  }
}
// 新建tab按键对象
const syntheticEvent = new KeyboardEvent('keydown', {
  key: 'Tab',
});
// 按下tab键时的操作
export const handleTabKey = (event) => {
  const textarea: any = document.getElementById('rightNum');
  const { selectionStart, selectionEnd } = textarea;
  const tabSpaces = '    '; // 4 spaces
  event.preventDefault();
  // 在当前光标位置插入4个空格
  textarea.value =
    textarea.value.substring(0, selectionStart) +
    tabSpaces +
    textarea.value.substring(selectionEnd);
  // 将光标向右移动4个空格
  textarea.selectionStart = selectionStart + tabSpaces.length;
  textarea.selectionEnd = selectionStart + tabSpaces.length;
};

// 按下Backspace按键时
export function handleBackspace(viewJsonStr, event) {
  const textarea = event.target;
  const cursorPosition = textarea.selectionStart;
  const textBeforeCursor = viewJsonStr.value.slice(0, cursorPosition);
  const textAfterCursor = viewJsonStr.value.slice(cursorPosition);
  if (
    (textBeforeCursor.endsWith('"') && textAfterCursor.startsWith('"')) ||
    (textBeforeCursor.endsWith("'") && textAfterCursor.startsWith("'")) ||
    (textBeforeCursor.endsWith('[') && textAfterCursor.startsWith(']')) ||
    (textBeforeCursor.endsWith('{') && textAfterCursor.startsWith('}')) ||
    (textBeforeCursor.endsWith('(') && textAfterCursor.startsWith(')'))
  ) {
    event.preventDefault(); // 阻止默认的删除行为
    viewJsonStr.value = textBeforeCursor.slice(0, -1) + textAfterCursor.slice(1);
    nextTick(() => {
      textarea.selectionStart = cursorPosition - 1;
      textarea.selectionEnd = cursorPosition - 1;
    }).then((r) => {});
  }
}
调用方式
 <JsonEditor  v-model:value="testStr" />

 const testStr = ref('123');

总结

这个JSON编辑器不仅能够让你方便地格式化JSON字符串,还能帮你去掉不必要的空格。而且,它的全屏功能让编辑更加顺畅。最酷的是,它还能实时告诉你格式化的进度,如果遇到问题了,控制台会详细告诉你哪里出错了,这样你就能快速找到问题并解决它。编辑器还能精确地计算行号,这对于查找问题也是很有帮助的。而且,它还有自动补全、自动删除和括号匹配这些贴心的功能,让你的编辑工作变得更加轻松。如果你不小心做错了,也不用担心,因为它支持撤销和重做。希望它能帮助到大家,让我们的工作更加愉快!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一起读《奔跑吧Linux内核(第2版)卷1:基础架构》- 大小端字节序

关注 点赞 不错过精彩内容 大家好&#xff0c;我是硬核王同学&#xff0c;最近在做免费的嵌入式知识分享&#xff0c;帮助对嵌入式感兴趣的同学学习嵌入式、做项目、找工作! Hello&#xff0c;大家好我是硬核王同学&#xff0c;是一名刚刚工作一年多的Linux工程师&#xff0…

2024年【陕西省安全员A证】考试报名及陕西省安全员A证模拟试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 陕西省安全员A证考试报名考前必练&#xff01;安全生产模拟考试一点通每个月更新陕西省安全员A证模拟试题题目及答案&#xff01;多做几遍&#xff0c;其实通过陕西省安全员A证作业模拟考试很简单。 1、【多选题】《建…

基于ssm的职业高中学情成绩系统设计论文

摘 要 如今的时代&#xff0c;是有史以来最好的时代&#xff0c;随着计算机的发展到现在的移动终端的发展&#xff0c;国内目前信息技术已经在世界上遥遥领先&#xff0c;让人们感觉到处于信息大爆炸的社会。信息时代的信息处理肯定不能用之前的手工处理这样的解决方法&#x…

计算机毕业设计------学习论坛小程序

项目介绍 本项目分为两种用户类型&#xff0c;分别是普通用户&#xff0c;管理员用户&#xff1b; 管理员用户包含以下功能&#xff1a; 管理员登录,个人中心,用户管理,分类信息管理,论坛帖子管理&#xff0c;资料帖子管理&#xff0c;举报信息管理&#xff0c;系统管理功能…

【华为机试】2023年真题B卷(python)-靠谱的车

一、题目 题目描述&#xff1a; 程序员小明打了一辆出租车去上班。出于职业敏感&#xff0c;他注意到这辆出租车的计费表有点问题&#xff0c;总是偏大。 出租车司机解释说他不喜欢数字4&#xff0c;所以改装了计费表&#xff0c;任何数字位置遇到数字4就直接跳过&#xff0c;其…

简易机器学习笔记(四)初识卷积神经网络

前言 第一次写卷积神经网络&#xff0c;也是照着paddlepaddle的官方文档抄&#xff0c;这里简单讲解一下心得。 首先我们要知道之前写的那些东西都是什么&#xff0c;之前写的我们称之为简单神经网络&#xff0c;也就是简单一层连接输出和输出&#xff0c;通过前向计算和逆向…

C#使用 OpenHardwareMonitor获取CPU或显卡温度、使用率、时钟频率相关方式

C# 去获取电脑相关的基础信息&#xff0c;还是需要借助 外部的库&#xff0c;我这边尝试了自己去实现它 网上有一些信息&#xff0c;但不太完整&#xff0c;都比较零碎&#xff0c;这边尽量将代码完整的去展示出来 OpenHardwareMonitor获取CPU的温度和频率需要管理员权限 在没…

基于SSM的游戏资源管理系统+vue论文

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自古…

深入数组扩展应用

一、字符串反转 例如&#xff1a;‘123abc’----->‘cba321’ 字符串方法实现: var str 123abcvar reversFn function(str) {var newStr for (var i str.length - 1; i > 0; i--) {newStr str[i]}return newStr }console.log(reversFn(str));数组方法实现&#xf…

vue保姆级教程----深入了解Vuex的工作原理

&#x1f4e2; 鸿蒙专栏&#xff1a;想学鸿蒙的&#xff0c;冲 &#x1f4e2; C语言专栏&#xff1a;想学C语言的&#xff0c;冲 &#x1f4e2; VUE专栏&#xff1a;想学VUE的&#xff0c;冲这里 &#x1f4e2; CSS专栏&#xff1a;想学CSS的&#xff0c;冲这里 &#x1f4…

从有向带权图判断最短路径里各目标顶点顺序

对如下有向带权图&#xff0c;若采用迪杰斯特拉(Dijkstra)算法求从源点a到其他各顶点的最短路径&#xff0c;则得到的第一路径的目标顶点是b&#xff0c;第二条最短路径的目标顶点是c&#xff0c;后续得到的其余各最短路径的目标顶点依次是() A.d,e,f B.e,d,f C.f,d,e D.f,…

通过IP地址进行网络安全防护

随着互联网的普及&#xff0c;网络安全问题日益凸显。一个重要的网络安全防护手段是通过IP地址进行控制和管理。本文将介绍如何通过IP进行网络安全防护。 一、IP地址的基本概念 IP地址是互联网协议地址的简称&#xff0c;用于标识网络中的设备。IP地址由32位二进制数字组成&am…

odoo 客制化审批流

以BPM、OA为代表的应用平台&#xff0c;低代码处理为前提的审批流功能定制化 功能介绍&#xff1a; 业务对象&#xff1a;针对侵入式注册BPM业务场景&#xff1a;设置审批场景&#xff1a;如&#xff1a;请假大于三天的场景、金额大于1000的场景节点条件&#xff1a; 当符合某…

运动蓝牙耳机哪个品牌好?2024年热销运动蓝牙耳机推荐

​作为一个热爱跑步的运动爱好者&#xff0c;我在过去四年里尝试了许多不同类型的运动蓝牙耳机&#xff0c;包括入耳式、半入耳式、颈挂式和开放式等。在这个过程中&#xff0c;我逐渐总结出了挑选运动耳机的一些心得&#xff0c;了解到一款优秀的运动耳机需要满足哪些基本条件…

网络故障排查和流量分析利器-Tcpdump命令

Tcpdump是一个在Unix/Linux系统上广泛使用的命令行网络抓包工具。它能够捕获经过网络接口的数据包&#xff0c;并将其以可读的格式输出到终端或文件中。Tcpdump是一个强大的命令行工具&#xff0c;能够捕获和分析网络数据包&#xff0c;为网络管理员和安全专业人员提供了深入了…

【hyperledger-fabric】将智能合约部署到通道

简介 本文主要来自于B站视频教学视频&#xff0c;也主要参看了官方文档中下图这一章节。针对自己开发的代码做出相应的总结。 1.启动网络 # 跳转到指定的目录 cd /root/fabric/fabric-samples/test-network# 启动docker容器并且创建通道 ./network.sh up createChannel2.打…

拆分文本文件,TXT文本拆分器

在数字化飞速发展的时代&#xff0c;我们经常碰到需要处理大容量TXT文件的情况。这些文件可能包含大量的数据、日志信息或是其他重要内容。然而&#xff0c;传统的文本编辑器在处理这些庞然大物时往往会显得力不从心&#xff0c;这个时候&#xff0c;【首助编辑高手】的出现恰如…

STM32存储左右互搏 SPI总线读写FRAM MB85RS2M

STM32存储左右互搏 SPI总线读写FRAM MB85RS2M 在中低容量存储领域&#xff0c;除了FLASH的使用&#xff0c;&#xff0c;还有铁电存储器FRAM的使用&#xff0c;相对于FLASH&#xff0c;FRAM写操作时不需要预擦除&#xff0c;所以执行写操作时可以达到更高的速度&#xff0c;其…

Docker就应该这么学-01

第一章 容器与开发语言 1.1 Docker 最近一段时间&#xff0c;云计算领域最火的莫过于“容器”一词。提到容器&#xff0c;就不得不提 Docker,可以说 Docker 己经成为了容器的代名词。那么&#xff0c;什么是 Docker ? Docker 又能做什么呢&#xff1f;本章 我们就来简单介绍…

【MPC学习笔记】01:MPC简介(Lecture 1_1 Unconstrained MPC)

本笔记来自北航诸兵老师的课程 课程地址&#xff1a;模型预测控制&#xff08;2022春&#xff09;lecture 1-1 Unconstrained MPC 文章目录 0 MPC 简介0.1 案例引入0.2 系统模型0.3 MPC的优点0.4 MPC的缺点0.5 MPC的未来 1 详细介绍 0 MPC 简介 0.1 案例引入 MPC&#xff08;…