react-quill 富文本组件编写和应用

news2025/1/12 23:39:43
  •  index.tsx文件
import React, { useRef, useState } from 'react';
import { Modal, Button } from 'antd';
import RichEditor from './RichEditor';

const AnchorTouchHistory: React.FC = () => {
  const editorRef = useRef<any>(null);
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [isEditModalVisible, setIsEditModalVisible] = useState(false);
  const [contentHtml, setContentHtml] = useState('<p>heheda</p>' );

  const openAddModal = () => setIsModalVisible(true);
  const submitContent = () => {
    const content = editorRef.current?.getRichContent();
    console.log(content);
    setIsModalVisible(false);
    editorRef.current?.resetContent();
  };

  const openEditModal = () => setIsEditModalVisible(true);
  const submitEditContent = () => {
    const content = editorRef.current?.getRichContent();
    console.log(content);
    setIsEditModalVisible(false);
    editorRef.current?.resetContent();
  };

  return (
    <div>
      <Button onClick={openAddModal}>打开添加对话框</Button>
      <Modal
        visible={isModalVisible}
        onCancel={() => setIsModalVisible(false)}
        onOk={submitContent}
      >
        <RichEditor ref={editorRef} />
      </Modal>

      <Button onClick={openEditModal}>打开编辑对话框</Button>
      <Modal
        visible={isEditModalVisible}
        onCancel={() => setIsEditModalVisible(false)}
        onOk={submitEditContent}
      >
        <RichEditor ref={editorRef} initialContent={contentHtml} />
      </Modal>
    </div>
  );
};

export default AnchorTouchHistory;
  • RichEditor.tsx
// RichEditor.tsx
import React, { useState, useEffect, useRef, useMemo, forwardRef } from 'react';
import ReactQuill, { Quill } from 'react-quill';
import COS from 'cos-js-sdk-v5';
import 'react-quill/dist/quill.snow.css';
import { Modal, Input, Upload, Button, Tabs, Alert } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import ImageResize from 'quill-image-resize-module-react';
import { getTxyCosConf } from '@/services/anchor-touch/history';
import '@/styles/quillEditor.css';

// 引入 Quill 的基础类
const BlockEmbed = Quill.import('blots/block/embed');

// 自定义图片 Blot,支持宽度和高度属性
class CustomImage extends BlockEmbed {
  static create(value) {
    const node = super.create();
    node.setAttribute('src', value.src);
    if (value.width) {
      node.setAttribute('width', value.width);
    }
    if (value.height) {
      node.setAttribute('height', value.height);
    }
    return node;
  }

  static value(node) {
    return {
      src: node.getAttribute('src'),
      width: node.getAttribute('width'),
      height: node.getAttribute('height'),
    };
  }
}
CustomImage.blotName = 'customImage';
CustomImage.tagName = 'img';

Quill.register(CustomImage);
Quill.register('modules/imageResize', ImageResize);

const RichEditor = forwardRef((props, ref) => {
  const { value = '', onChange } = props;
  const [editorValue, setEditorValue] = useState(value);
  const [isCosReady, setIsCosReady] = useState(false);
  const quillRef = useRef<any>(null);
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [isLinkModalVisible, setIsLinkModalVisible] = useState(false);
  const [bucket, setBucket] = useState('');
  const [region, setRegion] = useState('');
  const [cos, setCos] = useState<COS | null>(null);
  const [width, setWidth] = useState('');
  const [height, setHeight] = useState('');
  const [previewUrl, setPreviewUrl] = useState<string | null>(null);
  const [currentFile, setCurrentFile] = useState<File | null>(null);
  const [originalWidth, setOriginalWidth] = useState<number | null>(null);
  const [originalHeight, setOriginalHeight] = useState<number | null>(null);
  const [imageUrl, setImageUrl] = useState('');
  const [uploadMode, setUploadMode] = useState<'local' | 'url'>('local');
  const [linkUrl, setLinkUrl] = useState('');
  const [linkText, setLinkText] = useState('');
  const [urlError, setUrlError] = useState('');
  const [isImageValid, setIsImageValid] = useState(false);

  useEffect(() => {
    setEditorValue(value);
  }, [value]);

  useEffect(() => {
    const fetchCosConfig = async () => {
      try {
        const response = await getTxyCosConf();
        setBucket(response.data.bucket);
        setRegion(response.data.region);
        const cosInstance = new COS({
          SecretId: response.data.secretid,
          SecretKey: response.data.secretkey,
        });
        setCos(cosInstance);
        setIsCosReady(true);
      } catch (error) {
        console.error('获取 COS 配置失败:', error);
      }
    };
    fetchCosConfig();
  }, []);

  const handleEditorChange = (content: string) => {
    setEditorValue(content);
    if (onChange) {
      onChange(content);
    }
  };

  const showImageUploadModal = () => {
    setIsModalVisible(true);
  };

  const showLinkModal = () => {
    setIsLinkModalVisible(true);
  };

  const handleLinkOk = () => {
    if (!linkUrl.startsWith('http://') && !linkUrl.startsWith('https://')) {
      setUrlError('链接地址格式不正确,请输入有效的链接地址。');
      return;
    }
    const editor = quillRef.current?.getEditor();
    if (editor) {
      editor.focus();
      const range = editor.getSelection();
      const position = range ? range.index : editor.getLength();
      editor.insertText(position, linkText, 'link', linkUrl);
      editor.setSelection(position + linkText.length);
      handleLinkCancel();
    }
  };

  const handleLinkCancel = () => {
    setIsLinkModalVisible(false);
    setLinkUrl('');
    setLinkText('');
    setUrlError('');
  };

  const handleOk = () => {
    if (uploadMode === 'local') {
      if (!currentFile || !cos) {
        handleCancel();
        return;
      }
      const uniqueFileName = `${Date.now()}_${currentFile.name}`;
      cos.uploadFile(
        {
          Bucket: bucket,
          Region: region,
          Key: uniqueFileName,
          Body: currentFile,
          SliceSize: 1024 * 1024,
        },
        (err, data) => {
          if (err) {
            console.error('上传失败:', err);
          } else {
            const imageUrl = `https://${data.Location}`;
            insertImageToEditor(imageUrl);
          }
        }
      );
    } else {
      insertImageToEditor(imageUrl);
    }
  };

  const insertImageToEditor = (imageUrl: string) => {
    const editor = quillRef.current?.getEditor();
    if (editor) {
      editor.focus();
      const range = editor.getSelection();
      const position = range ? range.index : editor.getLength();
      editor.insertEmbed(position, 'customImage', {
        src: imageUrl,
        width: width,
        height: height,
      });
      editor.setSelection(position + 1);
      handleCancel();
    }
  };

  const handleCancel = () => {
    setIsModalVisible(false);
    setPreviewUrl(null);
    setCurrentFile(null);
    setWidth('');
    setHeight('');
    setImageUrl('');
  };

  const beforeUpload = (file: File) => {
    if (!file.type.startsWith('image/')) {
      console.error('不是有效的图像文件');
      return false;
    }
    const reader = new FileReader();
    reader.onload = (e) => {
      const preview = e.target?.result as string;
      setPreviewUrl(preview);
      setCurrentFile(file);
      const img = new Image();
      img.onload = () => {
        setOriginalWidth(img.naturalWidth);
        setOriginalHeight(img.naturalHeight);
        setWidth(img.naturalWidth.toString());
        setHeight(img.naturalHeight.toString());
      };
      img.onerror = (error) => {
        console.error('图像加载失败:', error);
      };
      img.src = preview;
    };
    reader.onerror = (error) => {
      console.error('文件读取失败:', error);
    };
    reader.readAsDataURL(file);
    return false;
  };

  const handleWidthBlur = () => {
    const widthValue = parseFloat(width);
    if (isNaN(widthValue)) {
      console.error('无效的宽度: ', width);
      return;
    }
    if (originalWidth && originalHeight && widthValue > 0) {
      const calculatedHeight = (widthValue / originalWidth) * originalHeight;
      setHeight(calculatedHeight.toFixed(0).toString());
    }
  };

  const handleHeightBlur = () => {
    const heightValue = parseFloat(height);
    if (originalWidth && originalHeight && heightValue > 0) {
      const calculatedWidth = (heightValue / originalHeight) * originalWidth;
      setWidth(calculatedWidth.toFixed(0).toString());
    }
  };

  const handleLinkUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const url = e.target.value;
    setLinkUrl(url);
    if (url.startsWith('http://') || url.startsWith('https://')) {
      setUrlError('');
    } else if (url) {
      setUrlError('链接地址格式不正确,请输入有效的链接地址。');
    }
  };

  const sizes = [false, '14px', '16px', '18px', '20px', '22px', '26px', '28px', '30px'];
  const Size = Quill.import('formats/size');
  Size.whitelist = sizes;

  const fonts = [
    'SimSun',
    'SimHei',
    'Microsoft-YaHei',
    'KaiTi',
    'FangSong',
    'Arial',
    'Times-New-Roman',
    'sans-serif',
  ];
  const Font = Quill.import('formats/font');
  Font.whitelist = fonts;
  Quill.register(Font, true);

  const modules = useMemo(
    () => ({
      toolbar: {
        container: [
          ['bold', 'italic', 'underline'],
          [{ size: sizes }],
          [{ header: [1, 2, 3, 4, 5, false] }],
          [{ color: [] }, { background: [] }],
          ['link', 'image', 'clean'],
        ],
        handlers: {
          image: showImageUploadModal,
          link: showLinkModal,
        },
      },
      imageResize: {
        modules: ['DisplaySize'],
        handleStyles: {
          backgroundColor: 'transparent',
          border: 'none',
        },
        resizeWidth: false,
      },
    }),
    [cos]
  );

  const formats = [
    'font',
    'header',
    'size',
    'bold',
    'italic',
    'underline',
    'strike',
    'list',
    'bullet',
    'link',
    'customImage', // 使用自定义的 customImage
    'width',
    'height',
    'color',
    'background',
  ];

  if (!isCosReady) {
    return <div>加载中...</div>;
  }

  return (
    <>
      <ReactQuill
        ref={quillRef}
        value={editorValue}
        onChange={handleEditorChange}
        modules={modules}
        formats={formats}
      />
      <Modal
        title="插入图片"
        visible={isModalVisible}
        onCancel={handleCancel}
        footer={null}
      >
        <Tabs defaultActiveKey="local" onChange={(key) => setUploadMode(key as 'local' | 'url')}>
          <Tabs.TabPane tab="本地图片" key="local">
            <Upload beforeUpload={beforeUpload} showUploadList={false}>
              <Button icon={<UploadOutlined />}>选择图片</Button>
            </Upload>
            {previewUrl && (
              <div
                style={{
                  display: 'flex',
                  justifyContent: 'center',
                  alignItems: 'center',
                  marginTop: 10,
                  width: 150,
                  height: 150,
                  overflow: 'hidden',
                  border: '1px solid #e8e8e8',
                }}
              >
                <img src={previewUrl} alt="预览" style={{ width: 150, maxHeight: '100%' }} />
              </div>
            )}
          </Tabs.TabPane>
          <Tabs.TabPane tab="链接图片" key="url">
            <Input
              placeholder="图片链接"
              value={imageUrl}
              onChange={(e) => setImageUrl(e.target.value)}
              onBlur={() => {
                const img = new Image();
                img.onload = () => {
                  setOriginalWidth(img.naturalWidth);
                  setOriginalHeight(img.naturalHeight);
                  setWidth(img.naturalWidth.toString());
                  setHeight(img.naturalHeight.toString());
                  setPreviewUrl(imageUrl);
                  setIsImageValid(true); // 图片有效
                };
                img.onerror = (error) => {
                  console.error('图像加载失败:', error);
                  setPreviewUrl(null);
                  setIsImageValid(false); // 图片无效
                };
                img.src = imageUrl;
              }}
            />
            {previewUrl && (
              <div
                style={{
                  display: 'flex',
                  justifyContent: 'center',
                  alignItems: 'center',
                  marginTop: 10,
                  width: 150,
                  height: 150,
                  overflow: 'hidden',
                  border: '1px solid #e8e8e8',
                }}
              >
                <img src={previewUrl} alt="预览" style={{ width: 150, maxHeight: '100%' }} />
              </div>
            )}
          </Tabs.TabPane>
        </Tabs>
        <Input
          placeholder="设置宽度"
          value={width}
          onChange={(e) => setWidth(e.target.value)}
          onBlur={handleWidthBlur}
          style={{ marginTop: 10 }}
        />
        <Input
          placeholder="设置高度"
          value={height}
          onChange={(e) => setHeight(e.target.value)}
          onBlur={handleHeightBlur}
          style={{ marginTop: 10 }}
        />
        <div style={{ marginTop: 10, textAlign: 'right' }}>
          <Button
            type="primary"
            onClick={handleOk}
            disabled={
              uploadMode === 'local'
                ? !currentFile
                : !imageUrl || !isImageValid // 当图片无效时禁用按钮
            }
          >
            确认
          </Button>
          <Button onClick={handleCancel} style={{ marginLeft: 10 }}>
            取消
          </Button>
        </div>
      </Modal>

      <Modal
        title="添加链接"
        visible={isLinkModalVisible}
        onCancel={handleLinkCancel}
        onOk={handleLinkOk}
      >
        {urlError && <Alert message={urlError} type="error" />}
        <Input
          placeholder="链接地址"
          value={linkUrl}
          onChange={handleLinkUrlChange}
          style={{ marginBottom: 10 }}
        />
        <Input
          placeholder="备注"
          value={linkText}
          onChange={(e) => setLinkText(e.target.value)}
        />
      </Modal>
    </>
  );
});

export default RichEditor;
  • quillEditor.css
/* 字体风格 */
/* 处理下拉字体选择器中选项的文本溢出并显示省略号 */
.ql-snow .ql-picker.ql-font .ql-picker-label::before {
  width: 88px; /* 设置下拉选项宽度,可以根据需要调整 */
  white-space: nowrap; /* 不换行显示 */
  overflow: hidden; /* 隐藏溢出部分 */
  text-overflow: ellipsis; /* 使用省略号显示溢出文本 */
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="SimSun"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="SimSun"]::before {
  content: "宋体";
  font-family: "SimSun";
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="SimHei"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="SimHei"]::before {
  content: "黑体";
  font-family: "SimHei";
}

.ql-snow
.ql-picker.ql-font
.ql-picker-label[data-value="Microsoft-YaHei"]::before,
.ql-snow
.ql-picker.ql-font
.ql-picker-item[data-value="Microsoft-YaHei"]::before {
  content: "微软雅黑";
  font-family: "Microsoft YaHei";
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="KaiTi"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="KaiTi"]::before {
  content: "楷体";
  font-family: "KaiTi";
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="FangSong"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="FangSong"]::before {
  content: "仿宋";
  font-family: "FangSong";
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="Arial"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="Arial"]::before {
  content: "Arial";
  font-family: "Arial";
}

.ql-snow
.ql-picker.ql-font
.ql-picker-label[data-value="Times-New-Roman"]::before,
.ql-snow
.ql-picker.ql-font
.ql-picker-item[data-value="Times-New-Roman"]::before {
  content: "Times New Roman";
  font-family: "Times New Roman";
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="sans-serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="sans-serif"]::before {
  content: "sans-serif";
  font-family: "sans-serif";
}

.ql-font-SimSun { font-family: "SimSun"; }
.ql-font-SimHei { font-family: "SimHei"; }
.ql-font-Microsoft-YaHei { font-family: "Microsoft YaHei"; }
.ql-font-KaiTi { font-family: "KaiTi"; }
.ql-font-FangSong { font-family: "FangSong"; }
.ql-font-Arial { font-family: "Arial"; }
.ql-font-Times-New-Roman { font-family: "Times New Roman"; }
.ql-font-sans-serif { font-family: "sans-serif"; }

/* 字体大小 */
.ql-snow .ql-picker.ql-size .ql-picker-label::before { content: "字体大小"; }
.ql-snow .ql-picker.ql-size .ql-picker-item::before { content: "常规"; }
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="14px"]::before{
  content: "14px";
  font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before{
  content: "16px";
  font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before{
  content: "18px";
  font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before{
  content: "20px";
  font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="22px"]::before{
  content: "22px";
  font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="26px"]::before{
  content: "26px";
  font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="30px"]::before {
  content: "30px";
  font-size: 14px;
}

.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="14px"]::before {
  content: "14px";
  font-size: 14px;
}

.ql-size-14px { font-size: 14px; }

/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="16px"]::before {
  content: "16px";
  font-size: 16px;
}

.ql-size-16px { font-size: 16px; }

/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="18px"]::before {
  content: "18px";
  font-size: 18px;
}

.ql-size-18px { font-size: 18px; }

/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before {
  content: "20px";
  font-size: 20px;
}

.ql-size-20px { font-size: 20px; }

/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="22px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="22px"]::before {
  content: "22px";
  font-size: 22px;
}

.ql-size-22px { font-size: 22px; }

/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="26px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="26px"]::before {
  content: "26px";
  font-size: 26px;
}

.ql-size-26px { font-size: 26px; }

/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="28px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="28px"]::before {
  content: "28px";
  font-size: 28px;
}

.ql-size-28px { font-size: 28px; }

/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="30px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="30px"]::before {
  content: "30px";
  font-size: 30px;
}

.ql-size-30px { font-size: 30px; }

/* 段落大小 */
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
  content: "标题1";
}

.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
  content: "标题2";
}

.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
  content: "标题3";
}

.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
  content: "标题4";
}

.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
  content: "标题5";
}

.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
  content: "标题6";
}

.ql-snow .ql-picker.ql-header .ql-picker-item::before {
  content: "常规";
}

/* .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, */
.ql-snow .ql-picker.ql-header .ql-picker-label::before {
  content: "标题大小";
}

/* 默认设置 */
.ql-snow .ql-editor { font-size: 14px; }
/* 查看样式 */
.view-editor .ql-toolbar { display: none; }
.view-editor .ql-container.ql-snow { border: 0; }
.view-editor .ql-container.ql-snow .ql-editor { padding: 0; }
/* 编辑样式 */
.edit-editor .ql-toolbar { display: block; }
.edit-editor .ql-container.ql-snow {
  border: 1px solid #ccc;
  min-height: inherit;
}

  • golang后端接口,获取
    TxyCosConf:
      SecretId: 'xxxxx'
      SecretKey: 'xxxxx'
      Bucket: 'xxxxx'
      Region: 'xxxx'
import {request} from "@@/plugin-request/request";
export function getTxyCosConf() {
  return request('/api/v1/xxxx/getTxyCosConf', {
    method: 'get',
  })
    .then(response => {
      return response;
    })
    .catch(error => {
      console.error('Error get data:', error);
      throw error;
    });
}

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

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

相关文章

关于扫描模型 拓扑 和 传递贴图工作流笔记

关于MAYA拓扑和传递贴图的操作笔记 一、拓扑低模: 1、拓扑工作区位置: 1、准备出 目标 高模。 (高模的状态如上 ↑ )。 2、打开顶点吸附,和建模工具区,选择四边形绘制. 2、拓扑快捷键使…

【Rust自学】11.9. 单元测试

喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 11.9.1. 测试的分类 Rust把测试分为两类&#xff0c;一个是单元测试&#xff0c;一个是集成…

【竞技宝】CS2:HLTV2024选手排名TOP4-NiKo

北京时间2025年1月11日,HLTV年度选手排名正在持续公布中,今日凌晨正式公布了今年的TOP4选手为G2(目前已转为至Falcons)战队的NiKo。 选手简介 NiKo是一名来自波黑的CS职业选手,现年26岁。作为DOTA2饱负盛名的职业选手,NiKo在CS1.6时代就已经开始征战职业赛场。2012年,年仅15岁…

rom定制系列------小米max3安卓12 miui14批量线刷 默认开启usb功能选项 插电自启等

小米Max3是小米公司于2018年7月19日发布的机型。此机型后在没有max新型号。采用全金属一体机身设计&#xff0c;配备6.9英寸全面屏.八核处理器骁龙636&#xff0c;后置双摄像头1200万500万像素&#xff0c;前置800万像素.机型代码 &#xff1a;nitrogen.官方最终版为稳定版12.5…

Linux第一课:c语言 学习记录day06

四、数组 冒泡排序 两两比较&#xff0c;第 j 个和 j1 个比较 int a[5] {5, 4, 3, 2, 1}; 第一轮&#xff1a;i 0 n&#xff1a;n个数&#xff0c;比较 n-1-i 次 4 5 3 2 1 // 第一次比较 j 0 4 3 5 2 1 // 第二次比较 j 1 4 3 2 5 1 // 第三次比较 j 2 4 3 2 1 5 // …

Cline(原Claude Dev)开源的IDE AI插件,如何搭配OpenRouter实现cursor功能,Cline怎么使用

Cline&#xff08;原Claude Dev&#xff09;是一个开源的IDE AI插件&#xff0c;可以使用你的命令行界面和编辑器的人工智能助手。 你可以直接在VS Code编辑器进行安装。如果你使用过Cursor AI IDE的话&#xff0c;可以尝试最新发布的Cline3.1版本。 在OpenRouter上&#xff0…

arcgis的合并、相交、融合、裁剪、联合、标识操作的区别和使用

1、相交 需要输入两个面要素&#xff0c;最终得到的是两个输入面要素相交部分的结果面要素。 2、合并 合并能将两个单独存放的两个要素类的内容&#xff0c;汇集到一个要素类里面。 3、融合 融合能将一个要素类内的所有元素融合成一个整体。 4、裁剪 裁剪需要输入两个面要…

【Rust自学】11.7. 按测试的名称运行测试

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 11.7.1. 按名称运行测试的子集 如果想要选择运行的测试&#xff0c;就将测试的名称&#xff08;一个或多个&#xff09;作为cargo test的…

利用Java爬虫获取义乌购店铺所有商品列表:技术探索与实践

在当今数字化时代&#xff0c;数据的重要性不言而喻。对于采购商和市场分析师而言&#xff0c;能够快速获取并分析供应商店铺内的所有商品信息&#xff0c;是制定有效采购策略和市场分析的关键。义乌购作为国内知名的在线批发平台&#xff0c;拥有海量的商品数据。本文将介绍如…

An FPGA-based SoC System——RISC-V On PYNQ项目复现

本文参考&#xff1a; &#x1f449; 1️⃣ 原始工程 &#x1f449; 2️⃣ 原始工程复现教程 &#x1f449; 3️⃣ RISCV工具链安装教程 1.准备工作 &#x1f447;下面以LOCATION代表本地源存储库的安装目录&#xff0c;以home/xilinx代表在PYNQ-Z2开发板上的目录 ❗ 下载Vivad…

Photoshop PS批处理操作教程(批量修改图片尺寸、参数等)

前言 ‌Photoshop批处理的主要作用‌是通过自动化处理一系列相似的操作来同时应用于多张图片&#xff0c;从而节省时间和精力&#xff0c;提高工作效率。批处理功能特别适用于需要批量处理的任务&#xff0c;如图像尺寸调整、颜色校正、水印添加等‌。 操作步骤 1.创建动作 …

互联网架构变迁:从 TCP/IP “呼叫” 到 NDN “内容分发” 的逐浪之旅

本文将给出关于互联网架构演进的一个不同视角。回顾一下互联网的核心理论基础产生的背景&#xff1a; 左边是典型的集中控制通信网络&#xff0c;很容易被摧毁&#xff0c;而右边的网络则没有单点问题&#xff0c;换句话说它很难被全部摧毁&#xff0c;与此同时&#xff0c;分…

nvim 打造成可用的IDE(2)

上一个 文章写的太长了&#xff0c; 后来再写东西 就一卡一卡的&#xff0c;所以新开一个。 主要是关于 bufferline的。 之前我的界面是这样的。 这个图标很不舒服有。 后来发现是在这里进行配置。 我也不知道&#xff0c;这个配置 我是从哪 抄过来的。 测试结果&#xff1…

drawDB docker部属

docker pull xinsodev/drawdb docker run --name some-drawdb -p 3000:80 -d xinsodev/drawdb浏览器访问&#xff1a;http://192.168.31.135:3000/

Redis--20--大Key问题解析

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 大Key问题1.什么是 Redis 大 Key&#xff1f;在 Redis 中&#xff0c;大 Key 是指单个键值对的数据量非常大&#xff0c;可能包含大量数据。 2. Redis大Key的危害3.…

自动驾驶---E2E架构演进

1 背景 模型最早应用的自动驾驶模块就是感知层面&#xff0c;随着技术的发展&#xff0c;逐渐开始应用到决策规划等其它模块。端到端自动驾驶架构是一种基于深层神经网络模型和方法的自动驾驶技术模式。目前一段式端到端系统只有在英伟达早期所做的demo中实现&#xff0c;再者就…

如何优雅地绘制时序图

说到时序图&#xff0c;相信所有从事嵌入式开发的伙伴都非常熟悉&#xff0c;在各种元器件手册以及处理器说明书中&#xff0c;但凡涉及到通信、接口、交互等内容&#xff0c;都会涉及到时序图。时序图可以非常详细且明确地描述硬件及软件接口中各个信号的时序关系&#xff0c;…

SpringCloud系列教程:微服务的未来(十一)服务注册、服务发现、OpenFeign快速入门

本篇博客将通过实例演示如何在 Spring Cloud 中使用 Nacos 实现服务注册与发现&#xff0c;并使用 OpenFeign 进行服务间调用。你将学到如何搭建一个完整的微服务通信框架&#xff0c;帮助你快速开发可扩展、高效的分布式系统。 目录 前言 服务注册和发现 服务注册 ​编辑 …

WebGIS在应急灾害中对村庄、风景区、机场的影响范围应用-以日喀则市定日县地震为例

目录 前言 一、关于影响范围 1、震中距离5公里 2、震中20公里范围 3、20到80公里范围 二、空间查询知识 1、相关数据介绍 2、空间数据查询 三、前后端数据查询以及web可视化实现 1、后台API实现 2、WebGIS前端实现 四、Web成果展示 1、空间位置分析 2、包含风景区…

使用网页版Jupyter Notebook和VScode打开.ipynb文件

目录 正文 1、网页版Jupyter Notebook查看 2、VScode查看 因为总是忘记查看文件的网址&#xff0c;收藏了但分类众多每次都找不到……当个记录吧&#xff08;/捂脸哭&#xff09;&#xff01; 正文 此处以gitub中的某个仓库为例&#xff1a; https://github.com/INM-6/mu…