鸿蒙OSUniApp开发富文本编辑器组件#三方框架 #Uniapp

news2025/5/17 6:12:23

使用UniApp开发富文本编辑器组件

富文本编辑在各类应用中非常常见,无论是内容创作平台还是社交软件,都需要提供良好的富文本编辑体验。本文记录了我使用UniApp开发一个跨平台富文本编辑器组件的过程,希望对有类似需求的开发者有所启发。

背景

前段时间接到一个需求,要求在我们的跨平台应用中加入富文本编辑功能,支持基础的文本格式化、插入图片、链接等功能。考虑到项目使用UniApp开发,需要兼容多个平台,市面上现成的富文本编辑器要么不支持跨平台,要么功能过于复杂。于是我决定自己动手,开发一个功能适中、性能良好的富文本编辑器组件。

技术选型

为何不直接使用现有组件?

首先,我调研了几个流行的富文本编辑器:

  1. quill.js - 功能强大,但在小程序环境中存在兼容性问题
  2. wangeditor - 针对Web端优化,小程序支持不佳
  3. mp-html - 专注于小程序,但编辑功能有限

UniApp官方提供的rich-text组件只具备富文本展示能力,不支持编辑。所以最终决定基于原生能力自己封装一个轻量级的富文本编辑器组件。

核心技术点

  • 使用uni.createSelectorQuery获取DOM节点
  • 基于contenteditable特性实现编辑功能
  • 自定义文本选区和格式化操作
  • 跨平台样式处理
  • 图片上传和展示

开发实现

1. 创建基础组件结构

首先,我们需要创建一个基础的编辑器组件结构:

<template>
  <view class="rich-editor">
    <view class="toolbar">
      <view 
        v-for="(item, index) in tools" 
        :key="index"
        class="tool-item"
        :class="{active: activeFormats[item.format]}"
        @tap="handleFormat(item.format, item.value)"
      >
        <text class="iconfont" :class="item.icon"></text>
      </view>
    </view>
    
    <!-- 编辑区域 -->
    <view 
      class="editor-container"
      :style="{ height: editorHeight + 'px' }"
    >
      <view
        class="editor-body"
        contenteditable="true"
        @input="onInput"
        @blur="onBlur"
        @focus="onFocus"
        id="editor"
        ref="editor"
      ></view>
    </view>
    
    <!-- 底部工具栏 -->
    <view class="bottom-tools">
      <view class="tool-item" @tap="insertImage">
        <text class="iconfont icon-image"></text>
      </view>
      <view class="tool-item" @tap="insertLink">
        <text class="iconfont icon-link"></text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  name: 'RichEditor',
  props: {
    value: {
      type: String,
      default: ''
    },
    height: {
      type: Number,
      default: 300
    },
    placeholder: {
      type: String,
      default: '请输入内容...'
    }
  },
  data() {
    return {
      editorHeight: 300,
      editorContent: '',
      selectionRange: null,
      activeFormats: {
        bold: false,
        italic: false,
        underline: false,
        strikethrough: false,
        alignLeft: true,
        alignCenter: false,
        alignRight: false
      },
      tools: [
        { format: 'bold', icon: 'icon-bold', value: 'bold' },
        { format: 'italic', icon: 'icon-italic', value: 'italic' },
        { format: 'underline', icon: 'icon-underline', value: 'underline' },
        { format: 'strikethrough', icon: 'icon-strikethrough', value: 'line-through' },
        { format: 'alignLeft', icon: 'icon-align-left', value: 'left' },
        { format: 'alignCenter', icon: 'icon-align-center', value: 'center' },
        { format: 'alignRight', icon: 'icon-align-right', value: 'right' }
      ]
    }
  },
  created() {
    this.editorHeight = this.height
    this.editorContent = this.value
  },
  mounted() {
    this.initEditor()
  },
  methods: {
    initEditor() {
      const editor = this.$refs.editor
      if (editor) {
        editor.innerHTML = this.value || `<p><br></p>`
      }
      
      // 设置placeholder
      if (!this.value && this.placeholder) {
        this.$nextTick(() => {
          editor.setAttribute('data-placeholder', this.placeholder)
        })
      }
    },
    
    // 监听输入
    onInput(e) {
      // 获取当前内容
      this.editorContent = e.target.innerHTML
      this.$emit('input', this.editorContent)
      this.saveSelection()
    },
    
    // 保存当前选区
    saveSelection() {
      const selection = window.getSelection()
      if (selection.rangeCount > 0) {
        this.selectionRange = selection.getRangeAt(0)
      }
    },
    
    // 恢复选区
    restoreSelection() {
      if (this.selectionRange) {
        const selection = window.getSelection()
        selection.removeAllRanges()
        selection.addRange(this.selectionRange)
        return true
      }
      return false
    },
    
    // 处理格式化
    handleFormat(format, value) {
      // 恢复选区
      if (!this.restoreSelection()) {
        console.log('No selection to format')
        return
      }
      
      // 根据不同格式执行不同操作
      switch(format) {
        case 'bold':
        case 'italic':
        case 'underline':
        case 'strikethrough':
          document.execCommand(format, false, null)
          break
        case 'alignLeft':
        case 'alignCenter':
        case 'alignRight':
          document.execCommand('justify' + format.replace('align', ''), false, null)
          break
        default:
          console.log('未知格式:', format)
      }
      
      // 更新激活状态
      this.checkActiveFormats()
      
      // 触发内容变化
      this.editorContent = this.$refs.editor.innerHTML
      this.$emit('input', this.editorContent)
    },
    
    // 检查当前激活的格式
    checkActiveFormats() {
      this.activeFormats.bold = document.queryCommandState('bold')
      this.activeFormats.italic = document.queryCommandState('italic')
      this.activeFormats.underline = document.queryCommandState('underline')
      this.activeFormats.strikethrough = document.queryCommandState('strikethrough')
      
      const alignment = document.queryCommandValue('justifyLeft') ? 'alignLeft' :
                       document.queryCommandValue('justifyCenter') ? 'alignCenter' :
                       document.queryCommandValue('justifyRight') ? 'alignRight' : 'alignLeft'
      
      this.activeFormats.alignLeft = alignment === 'alignLeft'
      this.activeFormats.alignCenter = alignment === 'alignCenter'
      this.activeFormats.alignRight = alignment === 'alignRight'
    },
    
    // 焦点事件
    onFocus() {
      this.saveSelection()
      this.checkActiveFormats()
    },
    
    onBlur() {
      this.saveSelection()
    },
    
    // 插入图片
    insertImage() {
      uni.chooseImage({
        count: 1,
        success: (res) => {
          const tempFilePath = res.tempFilePaths[0]
          // 上传图片
          this.uploadImage(tempFilePath)
        }
      })
    },
    
    // 上传图片
    uploadImage(filePath) {
      // 这里应该是实际的上传逻辑
      uni.showLoading({ title: '上传中...' })
      
      // 模拟上传过程
      setTimeout(() => {
        // 假设这是上传后的图片URL
        const imageUrl = filePath
        
        // 恢复选区并插入图片
        this.restoreSelection()
        document.execCommand('insertHTML', false, `<img src="${imageUrl}" style="max-width:100%;" />`)
        
        // 更新内容
        this.editorContent = this.$refs.editor.innerHTML
        this.$emit('input', this.editorContent)
        
        uni.hideLoading()
      }, 500)
    },
    
    // 插入链接
    insertLink() {
      uni.showModal({
        title: '插入链接',
        editable: true,
        placeholderText: 'https://',
        success: (res) => {
          if (res.confirm && res.content) {
            const url = res.content
            // 恢复选区
            this.restoreSelection()
            
            // 获取选中的文本
            const selection = window.getSelection()
            const selectedText = selection.toString()
            
            // 如果有选中文本,将其设为链接文本;否则使用URL作为文本
            const linkText = selectedText || url
            
            // 插入链接
            document.execCommand('insertHTML', false, 
              `<a href="${url}" target="_blank">${linkText}</a>`)
            
            // 更新内容
            this.editorContent = this.$refs.editor.innerHTML
            this.$emit('input', this.editorContent)
          }
        }
      })
    },
    
    // 获取编辑器内容
    getContent() {
      return this.editorContent
    },
    
    // 设置编辑器内容
    setContent(html) {
      this.editorContent = html
      if (this.$refs.editor) {
        this.$refs.editor.innerHTML = html
      }
      this.$emit('input', html)
    }
  }
}
</script>

<style>
.rich-editor {
  width: 100%;
  border: 1rpx solid #eee;
  border-radius: 10rpx;
  overflow: hidden;
}

.toolbar {
  display: flex;
  flex-wrap: wrap;
  padding: 10rpx;
  border-bottom: 1rpx solid #eee;
  background-color: #f8f8f8;
}

.tool-item {
  width: 80rpx;
  height: 80rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 40rpx;
  color: #333;
}

.tool-item.active {
  color: #007AFF;
  background-color: rgba(0, 122, 255, 0.1);
  border-radius: 8rpx;
}

.editor-container {
  width: 100%;
  overflow-y: auto;
}

.editor-body {
  min-height: 100%;
  padding: 20rpx;
  font-size: 28rpx;
  line-height: 1.5;
  outline: none;
}

.editor-body[data-placeholder]:empty:before {
  content: attr(data-placeholder);
  color: #999;
  font-style: italic;
}

.bottom-tools {
  display: flex;
  padding: 10rpx;
  border-top: 1rpx solid #eee;
  background-color: #f8f8f8;
}

/* 引入字体图标库 (需要自行配置) */
@font-face {
  font-family: 'iconfont';
  src: url('data:font/woff2;charset=utf-8;base64,...') format('woff2');
}
.iconfont {
  font-family: "iconfont" !important;
  font-style: normal;
}
</style>

2. 处理平台差异

UniApp支持多个平台,但在富文本编辑方面存在平台差异,特别是小程序限制较多。下面是一些关键的跨平台适配处理:

// 跨平台选区处理
saveSelection() {
  // #ifdef H5
  const selection = window.getSelection()
  if (selection.rangeCount > 0) {
    this.selectionRange = selection.getRangeAt(0)
  }
  // #endif
  
  // #ifdef MP-WEIXIN
  // 微信小程序不支持DOM选区,需使用特殊方法
  this.getEditContext().getSelectionRange({
    success: (res) => {
      this.selectionRange = res
    }
  })
  // #endif
},

// 获取编辑器上下文(微信小程序)
getEditContext() {
  // #ifdef MP-WEIXIN
  return this.editorCtx || wx.createSelectorQuery()
    .in(this)
    .select('#editor')
    .context(res => {
      this.editorCtx = res.context
    })
    .exec()
  // #endif
  
  return null
}

3. 增强图片处理能力

富文本编辑器的一个关键功能是图片处理,我们需要增强这方面的能力:

// 增强版图片上传处理
uploadImage(filePath) {
  uni.showLoading({ title: '上传中...' })
  
  // 压缩图片
  uni.compressImage({
    src: filePath,
    quality: 80,
    success: res => {
      const compressedPath = res.tempFilePath
      
      // 上传到服务器
      uni.uploadFile({
        url: 'https://your-upload-endpoint.com/upload',
        filePath: compressedPath,
        name: 'file',
        success: uploadRes => {
          try {
            const data = JSON.parse(uploadRes.data)
            const imageUrl = data.url
            
            // 插入图片
            this.insertImageToEditor(imageUrl)
          } catch (e) {
            uni.showToast({
              title: '上传失败',
              icon: 'none'
            })
          }
        },
        fail: () => {
          uni.showToast({
            title: '上传失败',
            icon: 'none'
          })
        },
        complete: () => {
          uni.hideLoading()
        }
      })
    },
    fail: () => {
      // 压缩失败,使用原图
      this.doUploadFile(filePath)
    }
  })
},

// 插入图片到编辑器
insertImageToEditor(imageUrl) {
  // #ifdef H5
  this.restoreSelection()
  document.execCommand('insertHTML', false, `<img src="${imageUrl}" style="max-width:100%;" />`)
  // #endif
  
  // #ifdef MP-WEIXIN
  this.getEditContext().insertImage({
    src: imageUrl,
    width: '100%',
    success: () => {
      console.log('插入图片成功')
    }
  })
  // #endif
  
  // 更新内容
  this.$nextTick(() => {
    // #ifdef H5
    this.editorContent = this.$refs.editor.innerHTML
    // #endif
    
    // #ifdef MP-WEIXIN
    this.getEditContext().getContents({
      success: res => {
        this.editorContent = res.html
      }
    })
    // #endif
    
    this.$emit('input', this.editorContent)
  })
}

4. 实现HTML与富文本互转

编辑器需要支持HTML格式的导入导出,以便存储和展示:

// HTML转富文本对象
htmlToJson(html) {
  const tempDiv = document.createElement('div')
  tempDiv.innerHTML = html
  
  const parseNode = (node) => {
    if (node.nodeType === 3) { // 文本节点
      return {
        type: 'text',
        text: node.textContent
      }
    }
    
    if (node.nodeType === 1) { // 元素节点
      const result = {
        type: node.nodeName.toLowerCase(),
        children: []
      }
      
      // 处理元素属性
      if (node.attributes && node.attributes.length > 0) {
        result.attrs = {}
        for (let i = 0; i < node.attributes.length; i++) {
          const attr = node.attributes[i]
          result.attrs[attr.name] = attr.value
        }
      }
      
      // 处理样式
      if (node.style && node.style.cssText) {
        result.styles = {}
        const styles = node.style.cssText.split(';')
        styles.forEach(style => {
          if (style.trim()) {
            const [key, value] = style.split(':')
            if (key && value) {
              result.styles[key.trim()] = value.trim()
            }
          }
        })
      }
      
      // 递归处理子节点
      for (let i = 0; i < node.childNodes.length; i++) {
        const childResult = parseNode(node.childNodes[i])
        if (childResult) {
          result.children.push(childResult)
        }
      }
      
      return result
    }
    
    return null
  }
  
  const result = []
  for (let i = 0; i < tempDiv.childNodes.length; i++) {
    const nodeResult = parseNode(tempDiv.childNodes[i])
    if (nodeResult) {
      result.push(nodeResult)
    }
  }
  
  return result
},

// 富文本对象转HTML
jsonToHtml(json) {
  if (!json || !Array.isArray(json)) return ''
  
  const renderNode = (node) => {
    if (node.type === 'text') {
      return node.text
    }
    
    // 处理元素节点
    let html = `<${node.type}`
    
    // 添加属性
    if (node.attrs) {
      Object.keys(node.attrs).forEach(key => {
        html += ` ${key}="${node.attrs[key]}"`
      })
    }
    
    // 添加样式
    if (node.styles) {
      let styleStr = ''
      Object.keys(node.styles).forEach(key => {
        styleStr += `${key}: ${node.styles[key]};`
      })
      if (styleStr) {
        html += ` style="${styleStr}"`
      }
    }
    
    html += '>'
    
    // 处理子节点
    if (node.children && node.children.length > 0) {
      node.children.forEach(child => {
        html += renderNode(child)
      })
    }
    
    // 关闭标签
    html += `</${node.type}>`
    
    return html
  }
  
  let result = ''
  json.forEach(node => {
    result += renderNode(node)
  })
  
  return result
}

实战案例:评论编辑器

下面是一个简化版的评论编辑器实现,可以在社区或博客应用中使用:

<template>
  <view class="comment-editor">
    <view class="editor-title">
      <text>发表评论</text>
    </view>
    
    <rich-editor
      v-model="commentContent"
      :height="200"
      placeholder="说点什么吧..."
      ref="editor"
    ></rich-editor>
    
    <view class="action-bar">
      <view class="action-btn cancel" @tap="cancel">取消</view>
      <view class="action-btn submit" @tap="submitComment">发布</view>
    </view>
  </view>
</template>

<script>
import RichEditor from '@/components/rich-editor/rich-editor.vue'

export default {
  components: {
    RichEditor
  },
  data() {
    return {
      commentContent: '',
      replyTo: null
    }
  },
  props: {
    articleId: {
      type: [String, Number],
      required: true
    }
  },
  methods: {
    cancel() {
      this.commentContent = ''
      this.$refs.editor.setContent('')
      this.$emit('cancel')
    },
    
    submitComment() {
      if (!this.commentContent.trim()) {
        uni.showToast({
          title: '评论内容不能为空',
          icon: 'none'
        })
        return
      }
      
      uni.showLoading({ title: '发布中...' })
      
      // 提交评论
      this.$api.comment.add({
        article_id: this.articleId,
        content: this.commentContent,
        reply_to: this.replyTo
      }).then(res => {
        uni.hideLoading()
        
        if (res.code === 0) {
          uni.showToast({
            title: '评论发布成功',
            icon: 'success'
          })
          
          // 清空编辑器
          this.commentContent = ''
          this.$refs.editor.setContent('')
          
          // 通知父组件刷新评论列表
          this.$emit('submit-success', res.data)
        } else {
          uni.showToast({
            title: res.msg || '评论发布失败',
            icon: 'none'
          })
        }
      }).catch(() => {
        uni.hideLoading()
        uni.showToast({
          title: '网络错误,请重试',
          icon: 'none'
        })
      })
    },
    
    // 回复某条评论
    replyComment(comment) {
      this.replyTo = comment.id
      this.$refs.editor.setContent(`<p>回复 @${comment.user.nickname}:</p>`)
      this.$refs.editor.focus()
    }
  }
}
</script>

<style>
.comment-editor {
  padding: 20rpx;
  background-color: #fff;
  border-radius: 10rpx;
}

.editor-title {
  margin-bottom: 20rpx;
  font-size: 32rpx;
  font-weight: bold;
}

.action-bar {
  display: flex;
  justify-content: flex-end;
  margin-top: 20rpx;
}

.action-btn {
  padding: 10rpx 30rpx;
  border-radius: 30rpx;
  font-size: 28rpx;
  margin-left: 20rpx;
}

.cancel {
  color: #666;
  background-color: #f3f3f3;
}

.submit {
  color: #fff;
  background-color: #007AFF;
}
</style>

踩坑记录

开发过程中遇到了不少坑,这里分享几个关键问题及解决方案:

1. 小程序富文本能力受限

小程序不支持通过contenteditable实现的富文本编辑,需要使用平台提供的editor组件。解决方案是使用条件编译,H5使用contenteditable,小程序使用官方editor组件。

<!-- H5编辑器 -->
<!-- #ifdef H5 -->
<div 
  class="editor-body"
  contenteditable="true"
  @input="onInput"
  id="editor"
  ref="editor"
></div>
<!-- #endif -->

<!-- 小程序编辑器 -->
<!-- #ifdef MP-WEIXIN -->
<editor 
  id="editor" 
  class="editor-body" 
  :placeholder="placeholder"
  @ready="onEditorReady"
  @input="onInput"
></editor>
<!-- #endif -->

2. 选区处理差异

不同平台的选区API差异很大,需要分别处理:

// 处理选区问题
getSelectionRange() {
  return new Promise((resolve) => {
    // #ifdef H5
    const selection = window.getSelection()
    if (selection.rangeCount > 0) {
      resolve(selection.getRangeAt(0))
    } else {
      resolve(null)
    }
    // #endif
    
    // #ifdef MP-WEIXIN
    this.editorCtx.getSelectionRange({
      success: (res) => {
        resolve(res)
      },
      fail: () => {
        resolve(null)
      }
    })
    // #endif
  })
}

3. 图片上传大小限制

多端应用中,图片上传和展示需要考虑不同平台的限制:

// 处理图片大小限制
async handleImageUpload(file) {
  // 检查文件大小
  if (file.size > 5 * 1024 * 1024) { // 5MB
    uni.showToast({
      title: '图片不能超过5MB',
      icon: 'none'
    })
    return null
  }
  
  // 压缩图片
  try {
    // H5与小程序压缩方式不同
    // #ifdef H5
    const compressedFile = await this.compressImageH5(file)
    return compressedFile
    // #endif
    
    // #ifdef MP
    const compressedPath = await this.compressImageMP(file.path)
    return { path: compressedPath }
    // #endif
  } catch (e) {
    console.error('图片压缩失败', e)
    return file // 失败时使用原图
  }
}

性能优化

为了让编辑器运行更流畅,我做了以下优化:

  1. 输入防抖 - 减少频繁更新导致的性能问题
  2. 延迟加载图片 - 使用懒加载机制
  3. 减少DOM操作 - 尽量批量更新DOM
  4. 使用虚拟DOM - 在复杂场景下考虑使用Vue的虚拟DOM机制
// 输入防抖处理
onInput(e) {
  if (this.inputTimer) {
    clearTimeout(this.inputTimer)
  }
  
  this.inputTimer = setTimeout(() => {
    // #ifdef H5
    this.editorContent = this.$refs.editor.innerHTML
    // #endif
    
    // #ifdef MP-WEIXIN
    this.editorContent = e.detail.html
    // #endif
    
    this.$emit('input', this.editorContent)
  }, 300)
}

总结

通过这次开发实践,我实现了一个跨平台的富文本编辑器组件,总结几点经验:

  1. 平台差异是最大挑战,需要利用条件编译提供各平台最佳实现
  2. 功能要适中,不是所有Web富文本功能都适合移动端
  3. 性能优化很重要,尤其是在低端设备上
  4. 良好的用户体验需要细节打磨,如适当的反馈、容错处理等

富文本编辑是一个复杂的课题,即使是成熟的Web编辑器也有各种问题。在移动端和小程序环境中,受限更多。我们的方案虽然不完美,但通过合理的取舍和平台适配,已经能满足大部分应用场景的需求。

后续还可以继续完善这个组件,比如添加表格支持、代码高亮、Markdown转换等高级功能。希望本文对你有所启发,欢迎在评论区交流讨论!

参考资料

  1. UniApp官方文档
  2. execCommand API参考
  3. ContentEditable详解

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

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

相关文章

Axure设计的“广东省网络信息化大数据平台”数据可视化大屏

在数据驱动决策的时代&#xff0c;数据可视化大屏成为了展示数据、洞察趋势的重要工具。今天&#xff0c;让我们一同深入了解由Axure设计的“广东省网络信息化大数据平台”数据可视化大屏&#xff0c;看看它如何通过精心的布局和丰富的图表类型&#xff0c;将复杂的数据以直观易…

2025认证杯数学建模第二阶段C题完整论文(代码齐全)化工厂生产流程的预测和控制

2025认证杯数学建模第二阶段C题完整论文&#xff08;代码齐全&#xff09;化工厂生产流程的预测和控制&#xff0c;详细信息见文末名片 第二阶段问题 1 分析 在第二阶段问题 1 中&#xff0c;由于在真实反应流程中输入反应物的量改变后&#xff0c;输出产物会有一定延时&#…

Redis——底层数据结构

SDS&#xff08;simple dynamic string&#xff09;&#xff1a; 优点&#xff1a; O1时间获取长度&#xff08;char *需要ON&#xff09;快速计算剩余空间&#xff08;alloc-len&#xff09;&#xff0c;拼接时根据所需空间自动扩容&#xff0c;避免缓存区溢出&#xff08;ch…

ChatGPT 能“记住上文”的原因

原因如下 你把对话历史传给了它 每次调用 OpenAI 接口时&#xff0c;都会把之前的对话作为参数传入&#xff08;messages 列表&#xff09;&#xff0c;模型“看见”了之前你说了什么。 它没有长期记忆 它不会自动记住你是谁或你说过什么&#xff0c;除非你手动保存历史并再次…

大疆无人机自主飞行解决方案局限性及增强解决方案-AIBOX:特色行业无人机巡检解决方案

大疆无人机自主飞行解决方案局限性及增强解决方案-AIBOX&#xff1a;特色行业无人机巡检解决方案 大疆无人机是低空行业无人机最具性价比的产品&#xff0c;尤其是大疆机场3的推出&#xff0c;以及持续自身产品升级迭代&#xff0c;包括司空2、大疆智图以及大疆智运等专业软件和…

医学影像系统性能优化与调试技术:深度剖析与实践指南

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;10年以上C/C, C#, Java等多种编程语言开发经验&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开…

day 25

*被遗忘的一集 程序&#xff1a;二进制文件&#xff0c;文件存储在磁盘中&#xff0c;例如/usr/bin/目录下 进程&#xff1a;进程是已启动的可执行程序的运行实例。 *进程和程序并不是一一对应的关系&#xff0c;相同的程序运行在不同的数据集上就是不同的进程 *进程还具有并…

吉客云数据集成到金蝶云星空的最佳实践

吉客云数据集成到金蝶云星空的技术案例分享 在本次技术案例中&#xff0c;我们将探讨如何通过仓库方案-I0132&#xff0c;将吉客云的数据高效集成到金蝶云星空。此方案旨在解决企业在数据对接过程中遇到的多种技术挑战&#xff0c;包括数据吞吐量、实时监控、异常处理和数据格…

使用Mathematica制作Lorenz吸引子的轨道追踪视频

Lorenz奇异吸引子是混沌理论中最早被发现和研究的吸引子之一&#xff0c;它由Edward Lorenz在1963年研究确定性非周期流时提出。Lorenz吸引子以其独特的"蝴蝶"形状而闻名&#xff0c;是混沌系统和非线性动力学的经典例子。 L NDSolveValue[{x[t] -3 (x[t] - y[t]),…

简单图像自适应亮度对比度调整

一、背景介绍 继续在刷对比度调整相关算法&#xff0c;偶然间发现了这个简单的亮度/对比度自适应调整算法&#xff0c;做个简单笔记记录。也许后面用得到。 二、自适应亮度调整 1、基本原理 方法来自论文:Adaptive Local Tone Mapping Based on Retinex for High Dynamic Ran…

深入理解二叉树:遍历、存储与算法实现

在之前的博客系列中&#xff0c;我们系统地探讨了多种线性表数据结构&#xff0c;包括顺序表、栈和队列等经典结构&#xff0c;并通过代码实现了它们的核心功能。从今天开始&#xff0c;我们将开启一个全新的数据结构篇章——树结构。与之前讨论的线性结构不同&#xff0c;树形…

【Win32 API】 lstrcmpA()

作用 比较两个字符字符串&#xff08;比较区分大小写&#xff09;。 lstrcmp 函数通过从第一个字符开始检查&#xff0c;若相等&#xff0c;则检查下一个&#xff0c;直到找到不相等或到达字符串的末尾。 函数 int lstrcmpA(LPCSTR lpString1, LPCSTR lpString2); 参数 lpStr…

(C语言)超市管理系统 (正式版)(指针)(数据结构)(清屏操作)(文件读写)

目录 前言&#xff1a; 源代码&#xff1a; product.h product.c fileio.h fileio.c main.c 代码解析&#xff1a; 一、程序结构概述 二、product.c 函数详解 1. 初始化商品列表 Init_products 2. 添加商品 add_product 3. 显示商品 display_products 4. 修改商品 mo…

NAT转换和ICMP

NAT nat原理示意 nat实现 ICMP ICMP支持主机或路由器&#xff1a; 差错或异常报告网络探寻 2类icmp报文&#xff1a; 差错报告报文&#xff08;5种&#xff09; 目的不可达源抑制--拥塞控制超时&超期--TTL超时参数问题--问题报文丢弃重定向--不应该由这个路由器转发&a…

【专利信息服务平台-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

BUUCTF——web刷题第一页题解

共31题&#xff0c;admin那题没有&#xff0c;因为环境问题&#xff0c;我做的非常卡 目录 极客大挑战 2019]Havefun [HCTF 2018]WarmU [ACTF2020 新生赛]Include [ACTF2020 新生赛]Exec [GXYCTF2019]Ping Ping Ping [SUCTF 2019]EasySQL [极客大挑战 2019]LoveSQL [极…

哪个品牌的智能对讲机好用?推荐1款,能扛事更智能

在专业通信领域&#xff0c;智能对讲机早已突破传统设备的局限&#xff0c;成为集通信、调度、数据传输于一体的智能化终端。面对复杂多变的作业环境&#xff0c;用户对设备的稳定性、通信效率和智能化水平提出了更高要求。但是&#xff0c;市面上产品同质化严重&#xff0c;部…

【Win32 API】 lstrcpyA()

作用 将字符串复制到指定的字符串缓冲区。 函数 LPSTR lstrcpyA(LPSTR lpString1, LPCSTR lpString2); 参数 lpString1 类型&#xff1a;LPTSTR 一个缓冲区&#xff0c;用于接收由 lpString2 参数指向的字符串的内容。 缓冲区必须足够大才能包含字符串&#xff0c;包括终止…

Vue3——Watch侦听器

目录 手动指定监听对象 侦听ref对象 侦听ref对象中的某个属性 reactive写法 watchEffect 自动侦听 多源侦听 一次性侦听器 watch 是⼀个⽤于观察和响应Vue响应式系统中数据变化的⽅法。它允许你指定⼀个数据源&#xff08;可以是 响应式引⽤、计算属性、组件的属性等&#xf…

Go的单测gomock及覆盖率命令

安装gomock&#xff1a; go get github.com/golang/mock/gomockgo get github.com/golang/mock/mockgen 使用 mockgen 生成 mock 代码: 参考 mockgen -sourceservice/user.go -destinationservice/mocks/mock_user_service.go -packagemocks go test -coverprofilecoverage.out…