React低代码项目:问卷编辑器 II

吐司问卷:问卷编辑器 II

Date: February 26, 2025


Log

**软件设计的可拓展性:**对修改封闭,对拓展开放




工具栏

删除组件

需求:

2025-02-27 00.04.23.gif

要点:

  • 实现删除选中组件
    • 思路:重新计算 selectedId,优先选择下一个,没有下一个则选择上一个
    • 以上通过componentReducer工具函数utils

componentReducer/index.ts

removeSelectedComponent: (draft: ComponentsStateType) => {
  const { selectedId: removeId, componentList } = draft
  // 重新计算 selectedId, 优先选择下一个,没有下一个则选择上一个
  const nextSelectedId = getNextSelectedId(removeId, componentList)
  draft.selectedId = nextSelectedId
  // 删除组件
  const index = componentList.findIndex(c => c.fe_id === removeId)
  componentList.splice(index, 1)
},

componentReducer/utils.ts

import { ComponentInfoType } from './index'

/**
 * 获取下一个选中的组件 id
 * @param fe_id 当前选中的组件 id
 * @param componentList 组件列表
 * @returns 下一个选中的组件 id
 */
export function getNextSelectedId(
  fe_id: string,
  componentList: ComponentInfoType[]
) {
  const index = componentList.findIndex(c => c.fe_id === fe_id)
  if (index < 0) return ''
  if (index === componentList.length - 1) {
    return componentList[index - 1].fe_id
  } else {
    return componentList[index + 1].fe_id
  }
}


隐藏/显示组件

需求:

2025-02-27 01.01.39.gif

要点:

  • 定义属性 isHidden(Mock + Redux store)
  • Redux中changeComponentHidden修改 isHidden,实现 显示/隐藏 功能
    • componentList更新后过滤掉隐藏的组件
  • 修复潜在问题:隐藏组件属性暴露

思路:

componentReducer 中先定义属性 isHidden ,Redux 中实现 changeComponentHidden

用于修改 isHidden,从而实现 显示/隐藏功能。不过,记得页面的 componentList 需要过滤掉隐藏的信息,并且在处理组件对应属性面板的时候,也得先过滤掉隐藏的信息,再做选中下个组件逻辑。

潜在问题:

当组件A上面有隐藏组件B时,隐藏组件A,右侧的组件属性面板会显示B的属性。

2025-02-27 00.53.00.gif

参考服务端的 Mock 数据如下:

{
  fe_id: Random.id(),
  type: 'questionInput',
  title: '这是一个输入框组件',
  isHidden: false,
  props: {
    title: '你的电话',
    placeholder: '请输入内容'
  }
},
{
  fe_id: Random.id(),
  type: 'questionInput',
  title: '这是一个输入框组件',
  isHidden: true,
  props: {
    title: '隐藏咯!!!',
    placeholder: '请输入内容'
  }
},
{
  fe_id: Random.id(),
  type: 'questionInput',
  title: '这是一个输入框组件',
  isHidden: false,
  props: {
    title: '上面有一个隐藏元素',
    placeholder: '请输入内容'
  }
}

EditCanvas.tsx 组件列表更新后去除隐藏组件

  }
  return (
    <div className={styles.canvas}>
      {componentList
        .filter(c => !c.isHidden)
        .map(c => {
          const { fe_id } = c
          // 拼接 class name
          const wrapperDefaultClassName = styles['component-wrapper']

componentReducer/index.ts Redux实现隐藏组件

export type ComponentInfoType = {
  fe_id: string
  type: string
  title: string
  isHidden?: boolean
  props: ComponentPropsType
}

changeComponentHidden: (
  draft: ComponentsStateType,
  action: PayloadAction<{ fe_id: string; isHidden: boolean }>
) => {
  const { componentList } = draft
  const { fe_id, isHidden } = action.payload
  const component = draft.componentList.find(c => c.fe_id === fe_id)
  // 重新计算 selectedId, 优先选择下一个,没有下一个则选择上一个
  let newSelectedId = ''
  if (isHidden) {
    newSelectedId = getNextSelectedId(fe_id, componentList)
  } else {
    newSelectedId = fe_id
  }
  draft.selectedId = newSelectedId
  if (component) {
    component.isHidden = isHidden
  }
},

componentReducer/utils.ts 重新计算 selected 时需要过滤隐藏元素

import { ComponentInfoType } from './index'

/**
 * 获取下一个选中的组件 id
 * @param fe_id 当前选中的组件 id
 * @param componentList 组件列表
 * @returns 下一个选中的组件 id
 */
export function getNextSelectedId(
  fe_id: string,
  componentList: ComponentInfoType[]
) {
	// 重新计算 selected 时需要过滤隐藏元素
  const visibleComponentList = componentList.filter(c => !c.isHidden)
  const index = visibleComponentList.findIndex(c => c.fe_id === fe_id)
  if (index < 0) return ''
  if (index === visibleComponentList.length - 1) {
    return visibleComponentList[index - 1].fe_id
  } else {
    return visibleComponentList[index + 1].fe_id
  }
}



锁定/解锁组件

需求:

2025-02-27 13.56.02.gif

思路:

分析需求:

当点击锁定按钮时,可能需要传递锁定这个参数,因此,先从数据层面入手:

数据层面:先为组件参数定义 isLocked 属性,并在 Redux 中设计 锁定逻辑

逻辑层面:定位到顶部的工具栏,获取 Redux 中的锁定函数,并绑定到对应组件。

样式层面:当点击实现锁定效果

另外,当点击对应组件,属性面板组件也需要锁定,这一块也得需要分析:

先从数据层面入手

数据层面:表单锁定,根据 AntD,可能需要 disable 的属性,因此我们需要为属性面板的组件添加 disabled 的参数设定。

逻辑层面:点击画布中组件时,传递 isHidden 到属性组件中,也就是属性面板,如果画布中组件是锁定的,那么我们就传递 disable 给组件对应的属性面板。

样式层面:给表单添加 disabled 属性即可。

要点:

  • 数据:
    • 定义属性 isLocked(Mock + Redux store)
  • 变化:
    • 面板组件锁定:定义 Redux 中 toggleComponentLock 处理锁定
    • 组件属性面板锁定:属性面板,组件锁定则禁用 form
  • 样式:
    • 画布:增加 locked 样式
    • 属性面板组件锁定

Code:

componentReducer/index.ts

toggleComponentLock: (
  draft: ComponentsStateType,
  action: PayloadAction<{ fe_id: string }>
) => {
  const { fe_id } = action.payload
  const component = draft.componentList.find(c => c.fe_id === fe_id)
  if (component) {
    component.isLocked = !component.isLocked
  }
},

样式:

EditCanvas.tsx

<div className={styles.canvas}>
  {componentList
    .filter(c => !c.isHidden)
    .map(c => {
      const { fe_id, isLocked } = c
      // 样式处理
      const wrapperDefaultClassName = styles['component-wrapper']
      const selectedClassName = styles.selected
      const locked = styles.locked
      const wrapperClassName = classNames({
        [wrapperDefaultClassName]: true,
        [selectedClassName]: fe_id === selectedId,
        [locked]: isLocked,
      })

      return (
        <div
          key={fe_id}
          className={wrapperClassName}
          onClick={e => handleClick(e, fe_id || '')}
        >
          <div className={styles.component}>{getComponent(c)}</div>
        </div>
      )
    })}
</div>

EditCanvas.module.scss

.locked {
  opacity: 0.5;
  cursor: not-allowed;
}

属性面板,组件锁定则禁用 form

componentProp.tsx

<PropComponent
  {...props}
  disabled={isLocked || isHidden}
  onChange={changeProps}
/>


复制/粘贴组件

需求:

2025-02-27 15.57.08.gif

要点:

  • 在 Redux store 中存储复制的内容 copiedComponent
  • 粘贴按钮,判断是否 disabled
  • 公共代码抽离 insertNewComponent :新增组件逻辑

思路:

需求:点击组件,然后点击复制按钮,再选择位置,后点击粘贴,将拷贝的组件插入对应位置。

数据层面:

  • 组件状态需要新增 copiedComponent 状态,用于处理粘贴。

逻辑层面:

  • 选中组件,再点击复制按钮,将 selected 传递到 redux 中
  • Redux中设定 拷贝和粘贴 函数,根据 selectedId 深度拷贝对应组件,然后生成具有新的id的深拷贝组件,最后插入到对应位置即可。

utils.ts

/**
 * 插入新组件
 * @param draft 组件状态
 * @param newCompontent 新组件
 * @returns
 */
export const insertNewComponent = (
  draft: ComponentsStateType,
  newCompontent: ComponentInfoType
) => {
  const { selectedId, componentList } = draft
  const index = componentList.findIndex(c => c.fe_id === selectedId)
  if (index < 0) {
    draft.componentList.push(newCompontent)
  } else {
    draft.componentList.splice(index + 1, 0, newCompontent)
  }
  draft.selectedId = newCompontent.fe_id
}

componentReducer/index.ts

export type ComponentsStateType = {
  selectedId: string
  componentList: Array<ComponentInfoType>
  copiedComponent: ComponentInfoType | null
}

const INIT_STATE: ComponentsStateType = {
  selectedId: '',
  componentList: [],
  copiedComponent: null,
}

------

copySelectedComponent: (draft: ComponentsStateType) => {
  const { selectedId, componentList } = draft
  const selectedComponent = componentList.find(c => c.fe_id === selectedId)
  if (selectedComponent) {
    draft.copiedComponent = clonedeep(selectedComponent)
  }
},
pasteCopiedComponent: (draft: ComponentsStateType) => {
  const { copiedComponent } = draft
  if (!copiedComponent) return
  const newCopiedComponent = clonedeep(copiedComponent)
  newCopiedComponent.fe_id = nanoid()
  insertNewComponent(draft, newCopiedComponent)
},


画布增加快捷键

需求:

2025-02-27 17.43.54.gif

要点:

  • 删除、复制、粘贴、上下选中功能
  • 处理潜在问题:属性面板进行 backspace 时,会删除画布组件

**潜在问题:**属性面板进行 backspace 时,会删除画布组件

2025-02-27 16.45.31.gif

解决方案:

点击input组件显示的时候 <input … />,点击其他组件,比如画布组件会显示

根据以上这点,来处理删除快捷键问题。

function isActiveElementValid() {
  const activeElement = document.activeElement
  // 光标没有 focus 到 ipnut 上
  if (activeElement === document.body) {
    return true
  }
  return false
}

useBindCanvasKeyPress.tsx

import { useDispatch } from 'react-redux'
import {
  removeSelectedComponent,
  copySelectedComponent,
  pasteCopiedComponent,
  selectPrevComponent,
  selectNextComponent,
} from '../store/componentReducer'
import { useKeyPress } from 'ahooks'

/**
 * 判断光标是否在 input 上
 * @returns
 *  true: 光标在 input 上
 *  false: 光标不在 input 上
 *
 */
function isActiveElementValid() {
  const activeElement = document.activeElement
  // 光标没有 focus 到 ipnut 上
  if (activeElement === document.body) {
    return true
  }
  return false
}

const useBindCanvasKeyPress = () => {
  const dispatch = useDispatch()
  // 删除选中的组件
  useKeyPress(['Delete', 'backspace'], () => {
    if (!isActiveElementValid()) return
    dispatch(removeSelectedComponent())
  })
  // 复制选中的组件
  useKeyPress(['ctrl.c', 'meta.c'], () => {
    if (!isActiveElementValid()) return
    dispatch(copySelectedComponent())
  })
  // 粘贴复制的组件
  useKeyPress(['ctrl.v', 'meta.v'], () => {
    if (!isActiveElementValid()) return
    dispatch(pasteCopiedComponent())
  })
  // 选中上一个组件
  useKeyPress(['uparrow'], () => {
    if (!isActiveElementValid()) return
    dispatch(selectPrevComponent())
  })
  // 选中下一个组件
  useKeyPress(['downarrow'], () => {
    if (!isActiveElementValid()) return
    dispatch(selectNextComponent())
  })
}

export default useBindCanvasKeyPress

componentReducer.tsx

  selectPrevComponent: (draft: ComponentsStateType) => {
    const { selectedId, componentList } = draft
    const index = componentList.findIndex(c => c.fe_id === selectedId)
    // 如果是第一个组件,不做任何操作
    if (index <= 0) return
    const prevComponent = componentList[index - 1]
    if (prevComponent) {
      draft.selectedId = prevComponent.fe_id
    }
  },
  selectNextComponent: (draft: ComponentsStateType) => {
    const { selectedId, componentList } = draft
    const index = componentList.findIndex(c => c.fe_id === selectedId)
    if (index <= 0) return
    if (index === componentList.length - 1) return
    const nextComponent = componentList[index + 1]
    if (nextComponent) {
      draft.selectedId = nextComponent.fe_id
    }
  },



组件库拓展设计

扩展性:

  • 从最简单的组件开始
  • 定义好规则,跑通流程
  • 增加其他组件,不改变编辑器的规则

**软件开发规则:**对拓展开放,对修改封闭



段落组件

需求:

2025-02-28 00.56.19.gif

要点:

  • 段落组件类型、接口、组件、属性组件实现
  • 潜在问题:段落换行处理

文件树:

│   │   ├── QuestionComponents
│   │   │   ├── QuestionParagraph
│   │   │   │   ├── Component.tsx
│   │   │   │   ├── PropComponent.tsx
│   │   │   │   ├── index.ts
│   │   │   │   └── interface.ts
│   │   │   └── index.ts

潜在问题:段落换行处理

尽量不要使用 dangerouslySetInnerHTML 来渲染 html,会有 xss 攻击风险。

如下可以选用 map 对组件列表进行渲染

const textList = text.split('\n')
<Paragraph
  style={{ textAlign: isCenter ? 'center' : 'start', marginBottom: 0 }}
>
  {/* <span dangerouslySetInnerHTML={{ __html: t }}></span> */}
  {textList.map((item, index) => (
    <span key={index}>
      {index === 0 ? '' : <br />}
      {item}
    </span>
  ))}
</Paragraph>

Component.tsx

import React, { FC } from 'react'
import {
  QuestionParagraphPropsType,
  QuestionParagraphDefaultProps,
} from './interface'
import { Typography } from 'antd'

const { Paragraph } = Typography

const Component: FC<QuestionParagraphPropsType> = (
  props: QuestionParagraphPropsType
) => {
  const { text = '', isCenter = false } = {
    ...QuestionParagraphDefaultProps,
    ...props,
  }
  // 尽量不要使用 dangerouslySetInnerHTML 来渲染 html,会有 xss 攻击风险
  // const t = text.replace('\n', '<br/>')
  const textList = text.split('\n')
  return (
    <Paragraph
      style={{ textAlign: isCenter ? 'center' : 'start', marginBottom: 0 }}
    >
      {/* <span dangerouslySetInnerHTML={{ __html: t }}></span> */}
      {textList.map((item, index) => (
        <span key={index}>
          {index === 0 ? '' : <br />}
          {item}
        </span>
      ))}
    </Paragraph>
  )
}

export default Component

index.ts

/**
 *  @description 段落组件
 */
import Component from './Component'
import { QuestionParagraphDefaultProps } from './interface'
import PropComponent from './PropComponent'
export * from './interface'
// paragraph 组件配置
export default {
  title: '段落',
  type: 'questionPragraph',
  Component: Component,
  PropComponent: PropComponent,
  defaultProps: QuestionParagraphDefaultProps,
}

interface.ts

export type QuestionParagraphPropsType = {
  text?: string
  isCenter?: boolean
  onChange?: (newProps: QuestionParagraphPropsType) => void
  disabled?: boolean
}

export const QuestionParagraphDefaultProps: QuestionParagraphPropsType = {
  text: '一行段落',
  isCenter: false,
}

PropComponent.tsx

import React, { FC } from 'react'
import { useEffect } from 'react'
import { Form, Input, Checkbox } from 'antd'
import { QuestionParagraphPropsType } from './interface'

const { TextArea } = Input
const PropComponent: FC<QuestionParagraphPropsType> = (
  props: QuestionParagraphPropsType
) => {
  const { text, isCenter, onChange, disabled } = props
  const [form] = Form.useForm()
  useEffect(() => {
    form.setFieldsValue({ text, isCenter })
  }, [text, isCenter])
  function handleValuesChange() {
    if (onChange) {
      onChange(form.getFieldsValue())
    }
  }
  return (
    <Form
      layout="vertical"
      initialValues={{ text, isCenter }}
      form={form}
      onChange={handleValuesChange}
      disabled={disabled}
    >
      <Form.Item
        label="段落内容"
        name="text"
        rules={[{ required: true, message: '请输入段落内容' }]}
      >
        <TextArea cols={5} />
      </Form.Item>
      <Form.Item label="是否居中" name="isCenter" valuePropName="checked">
        <Checkbox />
      </Form.Item>
    </Form>
  )
}

export default PropComponent



单选框组件

多选框同理处理

需求:

2025-03-02 21.10.26.gif

要点:

  • 组件属性面板:表单标题、默认选中、竖向编辑
  • 动态增减嵌套字段

Component.tsx

import React from 'react'
import { Radio, Typography } from 'antd'
import { QuestionRadioPropsType, QuestionRadioDefaultProps } from './interface'

const { Paragraph } = Typography
const QuestionRadio: React.FC<QuestionRadioPropsType> = (
  props: QuestionRadioPropsType
) => {
  const { title, isVertical, options, value } = {
    ...QuestionRadioDefaultProps,
    ...props,
  }

  const radioStyle: React.CSSProperties = isVertical
    ? { display: 'flex', flexDirection: 'column' }
    : {}

  return (
    <div>
      <Paragraph strong>{title}</Paragraph>
      <Radio.Group
        value={value}
        style={radioStyle}
        options={options?.map(option => ({
          value: option.value,
          label: option.text,
        }))}
      />
    </div>
  )
}
export default QuestionRadio

PropCompnent.tsx

import React, { FC } from 'react'
import { useEffect } from 'react'
import { Checkbox, Form, Input, Button, Space, Select } from 'antd'
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'
import { QuestionRadioPropsType, OptionType } from './interface'
import { nanoid } from '@reduxjs/toolkit'

const PropComponent: FC<QuestionRadioPropsType> = (
  props: QuestionRadioPropsType
) => {
  const { title, isVertical, options, value, disabled, onChange } = props
  const [form] = Form.useForm()

  useEffect(() => {
    form.setFieldsValue({ title, isVertical, options, value })
  }, [title, isVertical, options, value])

  function handleValuesChange() {
    const values = form.getFieldsValue()
    const { options } = values
    // 生成唯一的value
    if (options && options.length > 0) {
      options.forEach((opt: OptionType) => {
        if (!opt.value) {
          opt.value = nanoid(5)
        }
      })
    }
    if (onChange) {
      onChange(form.getFieldsValue())
    }
  }
  return (
    <Form
      layout="vertical"
      initialValues={{ title, isVertical, options, value }}
      onValuesChange={handleValuesChange}
      form={form}
      disabled={disabled}
    >
      <Form.Item
        label="标题"
        name="title"
        rules={[{ required: true, message: '请输入标题' }]}
      >
        <Input />
      </Form.Item>
      <Form.Item label="选项" shouldUpdate>
        <Form.List name="options">
          {(fields, { add, remove }) => (
            <>
              {fields.map(({ key, name }) => (
                <Space key={key} align="baseline">
                  <Form.Item
                    name={[name, 'text']}
                    rules={[
                      { required: true, message: '请输入选项文字' },
                      {
                        validator: (_, value) => {
                          const optionTexts = form
                            .getFieldValue('options')
                            .map((opt: OptionType) => opt.text)
                          if (
                            optionTexts.filter((text: string) => text === value)
                              .length > 1
                          ) {
                            return Promise.reject(new Error('选项重复!'))
                          }
                          return Promise.resolve()
                        },
                      },
                    ]}
                  >
                    <Input placeholder="选项文字" />
                  </Form.Item>
                  <MinusCircleOutlined onClick={() => remove(name)} />
                </Space>
              ))}
              <Form.Item>
                <Button
                  type="dashed"
                  onClick={() => add({ text: '', value: '' })}
                  block
                  icon={<PlusOutlined />}
                >
                  添加选项
                </Button>
              </Form.Item>
            </>
          )}
        </Form.List>
      </Form.Item>
      <Form.Item label="默认选中" name="value">
        <Select
          options={options?.map(({ text, value }) => ({
            label: text,
            value: value,
          }))}
          allowClear
          placeholder="请选择默认选项"
        />
      </Form.Item>
      <Form.Item label="竖向排列" name="isVertical" valuePropName="checked">
        <Checkbox />
      </Form.Item>
    </Form>
  )
}
export default PropComponent


表单细节:getFieldValue

在这两个文件中,getFieldValue 的使用方式不同是因为它们获取表单字段值的方式不同。

文件 PropComponent.tsx

const optionTexts = form
.getFieldsValue()
.list.map((opt: OptionType) => opt.text)

在这个文件中,getFieldsValue 被用来获取整个表单的所有字段值,然后通过链式调用获取 list 字段的值。list 是一个数组,其中包含了所有选项的对象。

文件 PropComponent.tsx-1

const optionTexts = form
.getFieldValue('options')
.map((opt: OptionType) => opt.text)

在这个文件中,getFieldValue 被用来直接获取 options 字段的值。options 是一个数组,其中包含了所有选项的对象。

总结

  • getFieldsValue 返回整个表单的所有字段值作为一个对象。
  • getFieldValue 需要一个参数,返回指定字段的值。

这两种方法的选择取决于你需要获取的字段值的范围。如果你只需要一个特定字段的值,使用 getFieldValue 更加直接和高效。如果你需要多个字段的值,使用 getFieldsValue 会更方便。


fix: 重复选项提示处理

需求:

image.png

注释代码运行时候,当用户添加选项,输入选项值时后,哪怕值与之前选项不重复,它会保持报“选项重复!

<Form.Item
  name={[name, 'text']}
  rules={[
    { required: true, message: '请输入选项文字' },
    {
      validator: (_, value) => {
        const optionTexts = form
          .getFieldValue('list')
          .map((opt: OptionType) => opt.text)
        // if (optionTexts.indexOf(value) !== -1) {
        //   return Promise.reject('选项文字不能重复')
        // }
        if (
          optionTexts.filter((text: string) => text === value)
            .length > 1
        ) {
          return Promise.reject(new Error('选项重复!'))
        }
        return Promise.resolve()
      },
    },
  ]}
>
  <Input />
</Form.Item>

问题原因:

  • optionTexts 包含当前正在编辑选项的旧值

  • 当用户开始输入新值时,表单立即更新导致:

    旧值仍然存在于数组中,新值会被重复校验,即使输入唯一值,旧值的存在也会触发校验失败

解决方案:

if (
  optionTexts.filter((text: string) => text === value)
    .length > 1
) {
  return Promise.reject(new Error('选项重复!'))
}

校验逻辑解析

  1. filter 会遍历所有选项文字(包含当前正在编辑的选项)
  2. 当相同文字出现 超过1次 时才触发错误
  3. 这意味着:
    • 允许当前编辑项自身存在一次
    • 只有当其他选项存在相同文字时才会报错
    • 空值场景:多个空选项会触发错误(因为 "" === ""
// 原错误逻辑(任意重复即报错,包含自身)
if (optionTexts.indexOf(value) !== -1) { ... }
// 当前逻辑(允许自身存在一次)
if (重复次数 > 1) { ... }

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

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

相关文章

图像处理之图像边缘检测算法

目录 1 图像边缘检测算法简介 2 Sobel边缘检测 3 经典的Canny边缘检测算法 4 演示Demo 4.1 开发环境 4.2 功能介绍 4.3 下载地址 参考 1 图像边缘检测算法简介 图像边缘检测是计算机视觉和图像处理中的基本问题&#xff0c;主要目的是提取图像中明暗变化明显的边缘细节…

数据结构(初阶)(八)----排序

排序 概念 排序&#xff1a;所谓排序&#xff0c;就是使⼀串记录&#xff0c;按照其中的某个或某些关键字的⼤⼩&#xff0c;递增或递减的排列起来的 操作。 比较排序 插入排序 直接插入排序 直接插⼊排序是⼀种简单的插⼊排序法&#xff0c;其基本思想是&#xff1a;把待…

计算机毕业设计SpringBoot+Vue.js基于JAVA语言的在线考试与学习交流网页平台(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

聊一聊 IM 如何优化数据库

IM 系列 im doc 实时通讯文档仓库 聊一聊 IM 是什么&#xff1f; IM 即时通讯系统概览 聊一聊 IM 要如何设计&#xff1f; 聊一聊 IM 要如何设计功能模块&#xff1f; 聊一聊 IM 要如何进行架构设计&#xff1f; 聊一聊 IM 要如何进行技术选型&#xff1f; 聊一聊 IM 要…

人工智能AI在汽车设计领域的应用探索

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…

DeepSeek-R1 大模型实战:腾讯云 HAI 平台 3 分钟极速部署指南

引言&#xff1a;为什么选择 DeepSeek-R1&#xff1f; 近期&#xff0c;国产大模型 DeepSeek-R1 因其低成本、高性能的特点在全球 AI 领域引发热议。根据 Sensor Tower 数据&#xff0c;其发布仅 18 天便斩获 1600 万次下载量&#xff0c;远超 ChatGPT 同期表现。而腾讯云推出…

[SWPUCTF 2022 新生赛]1z_unserialize

题目描述&#xff1a;是很简单的反序列化噢 代码审计看注释 <?phpclass lyh{ //定义一个类为lyhpublic $url NSSCTF.com;//公共属性&#xff0c;初始值为NSSCTF.compublic $lt; //公共属性&#xff0c;没有初始值public $lly; //公共属性&…

三支一扶入职体检不合格项目全解析

“三支一扶” 计划为高校毕业生提供了到基层服务的宝贵机会&#xff0c;通过层层选拔后&#xff0c;入职体检也是其中关键的一环。了解哪些项目可能导致体检不合格&#xff0c;能让大家提前做好准备&#xff0c;避免在这一步出现意外。接下来&#xff0c;就为大家详细介绍三支一…

专题一四数之和

1.题目 题目分析&#xff1a; 给一个数组&#xff0c;在里面找到四个数字&#xff0c;满足四个数字之和等于给的特定值&#xff0c;四数之和可以拆分成三数之和&#xff0c;再继续拆分成二数之和&#xff0c;由简化繁。 2.算法原理 通过排序加双指针 1.依次固定一个数 2.在…

如何在docker中的mysql容器内执行命令与执行SQL文件

通过 docker ps -a 查询当前运行的容器&#xff0c;找到想执行命令的容器名称。 docker ps -a若想执行sql文件&#xff0c;则将sql文件放入当前文件夹下后将项目内的 SQL 文件拷贝到 mysql 容器内部的 root下。 sudo docker cp /root/enterprise.sql mysql:/root/然后进入 my…

Linux线程同步与互斥应用/生产者消费者模型

一&#xff0c;理论讲解 我们拿工厂&#xff0c;超市和消费者直接的关系来做讲解&#xff0c;首先人去超市买东西的过程就不用多说&#xff0c;但是超市本身是不能生产商品的&#xff0c;他们需要从各个不同的工厂进货商品&#xff0c;然后再给消费者买&#xff0c;以计算机的…

基于YOLO11深度学习的遥感视角农田检测与分割系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标分割、人工智能

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

RabbitMQ面试题及原理

RabbitMQ使用场景&#xff1a; 异步发送&#xff08;验证码、短信、邮件…&#xff09;MYSQL和Redis, ES之间的数据同步分布式事务削峰填谷 1. 消息可靠性&#xff08;不丢失&#xff09; 消息丢失场景&#xff1a; RabbitMQ-如何保证消息不丟失&#xff1f; 开启生产者确…

Python每日一练:学习指南进行汇总

Python&#xff0c;一种“优雅”、“明确”、“简单”的编程语言&#xff0c;凭借其低学习曲线、强大的开源生态系统、卓越的平台可移植性以及面向对象和函数式编程的支持&#xff0c;成为了众多开发者首选。 01 Python 应用领域和就业形势分析 Python&#xff0c;一种“优雅…

商米科技前端工程师(base上海)内推

1.根据原型或高保真设计&#xff0c;开发web、H5、小程序等类型的前端应用&#xff1b; 2.在指导下&#xff0c;高质量完成功能模块的开发&#xff0c;并负责各功能模块接口设计工作&#xff1b; 3.负责产品及相关支撑系统的开发及维护工作&#xff0c;不断的优化升级&#x…

八. Spring Boot2 整合连接 Redis(超详细剖析)

八. Spring Boot2 整合连接 Redis(超详细剖析) 文章目录 八. Spring Boot2 整合连接 Redis(超详细剖析)2. 注意事项和细节3. 最后&#xff1a; 在 springboot 中 , 整合 redis 可以通过 RedisTemplate 完成对 redis 的操作, 包括设置数据/获取数据 比如添加和读取数据 具体…

【漫话机器学习系列】113.逻辑回归(Logistic Regression) VS 线性回归(Linear Regression)

逻辑回归 vs 线性回归&#xff1a;详解对比 在机器学习和统计学中&#xff0c;逻辑回归&#xff08;Logistic Regression&#xff09; 和 线性回归&#xff08;Linear Regression&#xff09; 都是非常常见的模型。尽管它们的数学表达式有一定的相似性&#xff0c;但它们的应用…

构建智能 SQL 查询代理agent,把整个查询过程模块化,既能自动判断使用哪些表,又能自动生成 SQL 语句,最终返回查询结果

示例代码&#xff1a; import os import getpass from dotenv import load_dotenv from pyprojroot import here from typing import List from pprint import pprint from pydantic import BaseModel from langchain_core.tools import tool from langchain_core.runnables i…

fastapi中的patch请求

目录 示例测试使用 curl 访问&#xff1a;使用 requests 访问&#xff1a;预期返回&#xff1a; 浏览器访问 示例 下面是一个使用 app.patch("") 的 FastAPI 示例&#xff0c;该示例实现了一个简单的用户信息更新 API。我们使用 pydantic 定义数据模型&#xff0c;并…

【文献阅读】Collective Decision for Open Set Recognition

基本信息 文献名称&#xff1a;Collective Decision for Open Set Recognition 出版期刊&#xff1a;IEEE TRANSACTIONS ON KNOWLEDGE AND DATA ENGINEERING 发表日期&#xff1a;04 March 2020 作者&#xff1a;Chuanxing Geng and Songcan Chen 摘要 在开集识别&#xff0…