低代码平台开发 - 编辑器拓展

news2024/9/20 20:51:43

设计器(编辑器)这边内容比较杂,我们这次挑两个讲,一个是自定义出码,一个是新版本引擎中 array-setter 存在的问题
这期和之前的文章关联性不大,可以直接在阿里的低代码引擎初始化的目录下进行,如何搭建阿里低代码引擎平台可以参考我之前的文章

阿里低代码引擎使用 - 项目启动 & 本地物料开发

题外话:无法使用 antd 组件 / antd 组件样式不生效

我们在设计器当中开发时想引入 antd 的组件,会发现 antd 的组件无法使用,或者样式不生效。目前我的解决办法是将 antd 包升到 5.x 版本。我本地使用的是 5.7.1

自定义出码

引擎自带的出码是基于 React 的,组件是类式组件,也就是用的 class。很多场景都无法满足。
不过本文的重点还是专注于熟悉如何使用低代码引擎,不会对出码功能本身做太详细的展开,这次就带着做一个非常简单的 HTML 结构的出码功能,非组件式的

在页面上添加自己的按钮

第一步,我们需要在设计器页面当中添加自己的按钮,也就是入口
官网中的介绍如下

插件拓展 - 面板拓展

我的想法是添加一个按钮,按钮单独打开一个面板,将一部分功能集合全塞在这里,所以我选择新建一个 layout 目录,然后创建我们的按钮

import { IPublicModelPluginContext } from '@alilc/lowcode-types';
import CustomerPanel from './customerPanel';	//	这是一个自定义的组件

// 保存功能示例
const CustomerLayout = (ctx: IPublicModelPluginContext) => {
  return {
    async init() {
      const { skeleton } = ctx;
      skeleton.add({
        name: 'CustomerLayout',
        area: 'leftArea',
        type: 'PanelDock',
        props: {
          align: 'right',
          icon: "wenjian",
          description: "自定义功能集合",
        },
        content: <CustomerPanel ctx={ctx} />,
        panelProps: {
          floatable: true, // 是否可浮动
          height: 300,
          hideTitleBar: false,
          maxHeight: 800,
          maxWidth: 1200,
          title: "自定义功能集合",
          width: 600,
        },
      });
    },
  };
}
CustomerLayout.pluginName = 'CustomerLayout';
CustomerLayout.meta = {
  dependencies: ['EditorInitPlugin'],
};
export default CustomerLayout;

这是我的目录结构
image.png
并不一定非要取名 layout,这个是完全取决于自己的,引擎并没有约定的目录结构。customerPanel 是自定义的组件,大家自由发挥,这个和写普通组件是完全一样的。
找到 src 目录下的 index,导入我们的按钮并且注册

···	// 省略部分代码
import CustomerLayout from './layout';
···	// 省略部分代码
await plugins.register(CustomerLayout);
···	// 省略部分代码

刷新页面,我们就能够看到我们添加的按钮,在左侧的导航栏中
image.png
点击会弹出一个面板,而面板当中的内容,就取决于你刚刚自定义的组件内容,这里我添加了一个按钮,并设置点击时会再弹出一个面板,而我们自定义显示的出码代码就会在这个面板当中,这个部分完全自由发挥,请大家自行完成。
image.png

确认出码逻辑

这一步我们确认出码逻辑,先了解一下官方自带的出码逻辑

出码模块设计

简单来讲,官方的出码是基于 schema 树的,这也是正确的做法,后续我们也会做相关的解析。这次暂时不做这么复杂的,我们做最简单的出码功能,不基于 schema,我们只把页面的 dom 树扒下来
我们随意拖几个组件进设计器,然后打开控制台,观察页面元素
image.png
image.png
可以看到,引擎把内容都放进了一个 iframe 中,在一个 id 为 app 的 dom 中
我们可以通过代码直接拿到 dom 树

const iframe = document.getElementsByClassName('lc-simulator-content-frame')[0]
// @ts-ignore
const domTree = iframe.contentWindow.document.getElementById('app');
const pageDom = domTree?.children[0];
const contentDom = (pageDom.cloneNode(true).children as Element[]);
const container = document.createElement('div');
container.append(...contentDom);
const domTreeHtml = container.innerHTML;

最后的 domTreeHtml 就是 dom 树了,要注意如果是容器组件的话,在容器组件中没有其他组件时,引擎会在容器组件中插入一个提示信息,就是下面这样
image.png
它的 dom 节点是这样的
image.png
因此我们直接拿 dom 树的话需要处理这个问题,直接遍历 dom 树清除就好了,可以参考下面的代码

const clearContainerDom = (elems: Element[]) => {
  Array.from(elems)
  for (const elem of Array.from(elems)) {
    if (elem.classList.contains('lc-container-placeholder')) {
      elem.parentElement?.removeChild(elem);
      continue;
    }
    if (elem.children.length > 0) {
      clearContainerDom((elem.children as unknown as Element[]));
    }
  }
}

然后在刚刚拿 dom 树的地方调用这个函数就行了

···	// 其他代码
clearContainerDom(contentDom);
const container = document.createElement('div');
container.append(...contentDom);

渲染 dom 代码

最后,展示我们的代码,这个网上的方案有很多,大家可以自行完成。我这边也从网上看到一个方案,大家可以参考
首先格式化 dom 代码的内容就不展示了,大家自行在网上查找解决方案
推荐大家使用 highlight.js 作为染色方案

import hljs from 'highlight.js/lib/common';

const target = hljs.highlightAuto(格式化后的dom代码, ['html']).value;

在 React 中,我们可以通过下面的方式将我们的 dom 放进元素当中

import React, { useRef, useState } from 'react';

···
const contentDom = useRef<HTMLPreElement>(null!);
···

const target = hljs.highlightAuto(text, ['html']).value;	// 这是刚才的代码
contentDom.current.innerHTML = target;

···
<pre className='customer-code-pre'>
  <code className='customer-code-block hljs language-html' ref={contentDom}></code>
</pre>
···

我们可以直接在样式中指定对应 class 的颜色

.hljs-tag,
.hljs-keyword,
.hljs-selector-tag,
.hljs-attr,
.hljs-literal,
.hljs-strong,
.hljs-name {
  color: #f92672;
}

.hljs-string,
.hljs-bullet,
.hljs-subst,
.hljs-title,
.hljs-section,
.hljs-emphasis,
.hljs-type,
.hljs-built_in,
.hljs-builtin-name,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-addition,
.hljs-variable,
.hljs-template-tag,
.hljs-template-variable {
  color: #a6e22e;
}

最后简单展示下效果
image.png
渲染代码和引擎基本没啥关系了,所以过的很快,如果有疑问可以留言

解决 array-setter 的问题

再补充一点目前引擎存在的问题,要讲这个需要结合物料库的内容(什么是物料库可以参考官方文档,或者我之前的文章)

问题描述

当我们设置一个组件的某个属性是数组类型时,我们可以在设计器当中添加这个数组的元素
比如下面这个组件

export interface AntdSelectProps {
  /**
   * 选项
   * @componentName NewArraySetter
   */
  options: {label: string, value: string}[]
}

const AntdSelect: React.FC<AntdSelectProps> = ({
  options = []
}) => {
  return (
  <select>
    <option value="">请选择</option>
    {
      options?.length > 0 &&
      options.map((item, index) => {
        return <option key={index} value={item.value}>{item.label}</option>
      })
    }
  </select>
  )
}

这是一个下拉框组件,它的下拉选项由传入的 options 属性决定
它在设计器当中是这样的
image.png
注意右侧的添加属性
我们点击后可以添加下拉选项
image.png
这是正常的效果,但是在最近几个版本当中,会出现问题
image.png
当我们添加一个选项之后再次点击,就会报错并且无法再次添加内容
我使用的引擎版本

"@alilc/lowcode-engine": "1.2.3",
"@alilc/lowcode-engine-ext": "1.0.6-beta.19",

这个问题查官方的 git 下面提了不少

解决办法

如果你本地跑起物料库的服务时,会发现在物料库自带的设计器当中并不存在这个问题,那么我的解决办法也非常简单,添加一个新的设置器替代现版本的 array-setter 设置器,而代码就来自物料库当中的 array-setter 代码
有点绕,我们看具体怎么做就行,回到我们设计器的目录下,新建一个 setters 目录,添加一个 new-array-setter.tsx
我们把物料库的服务跑起来,找到 array-setter 的代码位置:

webpack://AliLowCodeEngineExt/src/setter/array-setter/index.tsx

我们拷贝所有代码复制当我们刚刚的 new-array-setter.tsx,解决掉部分依赖和路径问题,最后修改成下面的代码

import * as React from 'react';
import { Component, Fragment } from 'react';
import { common } from '@alilc/lowcode-engine';
import { Button, Message } from '@alifd/next';
import { IPublicModelSettingField, IPublicTypeSetterType, IPublicTypeFieldConfig, IPublicTypeSetterConfig } from '@alilc/lowcode-types';
import CustomIcon from '@alilc/lowcode-engine-ext/es/components/custom-icon';
import Sortable from '@alilc/lowcode-engine-ext/es/setter/array-setter/sortable';
// import './style.less';
const { editorCabin, skeletonCabin } = common;
const { Title } = editorCabin;
const { createSettingFieldView, PopupContext } = skeletonCabin;

interface ArraySetterState {
  items: IPublicModelSettingField[];
}

/**
 * onItemChange 用于 ArraySetter 的单个 index 下的数据发生变化,
 * 因此 target.path 的数据格式必定为 [propName1, propName2, arrayIndex, key?]。
 *
 * @param target
 * @param value
 */
function onItemChange (target: IPublicModelSettingField, items: IPublicModelSettingField[], props: ArraySetterProps) {
  const targetPath: Array<string | number> = target?.path;
  if (!targetPath || targetPath.length < 2) {
    console.warn(
      `[ArraySetter] onItemChange 接收的 target.path <${
        targetPath || 'undefined'
      }> 格式非法需为 [propName, arrayIndex, key?]`,
    );
    return;
  }
  const { field, value: fieldValue } = props;
  // const { items } = this.state;
  const { path } = field;
  if (path[0] !== targetPath[0]) {
    console.warn(
      `[ArraySetter] field.path[0] !== target.path[0] <${path[0]} !== ${targetPath[0]}>`,
    );
    return;
  }
  try {
    const index = +targetPath[targetPath.length - 2];
    if (typeof index === 'number' && !isNaN(index)) {
      fieldValue[index] = items[index].getValue();
      field?.extraProps?.setValue?.call(field, field, fieldValue);
    }
  } catch (e) {
    console.warn('[ArraySetter] extraProps.setValue failed :', e);
  }
};

interface ArraySetterProps {
  value: any[];
  field: IPublicModelSettingField;
  itemSetter?: IPublicTypeSetterType;
  columns?: IPublicTypeFieldConfig[];
  multiValue?: boolean;
  hideDescription?: boolean;
  onChange?: Function;
  extraProps: {renderFooter?: (options: ArraySetterProps & {onAdd: (val?: {}) => any}) => any}
}

export class ListSetter extends Component<ArraySetterProps, ArraySetterState> {
  state: ArraySetterState = {
    items: [],
  };

  private scrollToLast = false;

  constructor(props: ArraySetterProps) {
    super(props);
  }

  static getDerivedStateFromProps(props: ArraySetterProps, state: ArraySetterState) {
    const items: IPublicModelSettingField[] = [];
    const { value, field } = props;
    const valueLength = value && Array.isArray(value) ? value.length : 0;

    for (let i = 0; i < valueLength; i++) {
      let item = state.items[i];
      if (!item) {
        item = field.createField({
          name: i.toString(),
          setter: props.itemSetter,
          forceInline: 1,
          type: 'field',
          extraProps: {
            defaultValue: value[i],
            setValue: (target: IPublicModelSettingField) => {
              onItemChange(target, items, props);
            },
          },
        });
      }
      items.push(item);
    }

    return {
      items,
    };
  }

  onSort(sortedIds: Array<string | number>) {
    const { onChange, value: oldValues } = this.props;
    const { items } = this.state;
    const values: any[] = [];
    const newItems: IPublicModelSettingField[] = [];
    sortedIds.map((id, index) => {
      const item = items[+id];
      item.setKey(index);
      values[index] = oldValues[id as number];
      newItems[index] = item;
      return id;
    });
    this.setState({
      items: newItems,
    });
    onChange?.(values);
  }

  onAdd(newValue?: {[key: string]: any}) {
    const { itemSetter, field, onChange, value = [] } = this.props;
    const values = value || [];
    const initialValue = (itemSetter as any)?.initialValue;
    const defaultValue = newValue ? newValue : (typeof initialValue === 'function' ? initialValue(field) : initialValue);
    values.push(defaultValue);
    this.scrollToLast = true;
    onChange?.(values);
  }

  onRemove(removed: IPublicModelSettingField) {
    const { onChange, value } = this.props;
    const { items } = this.state;
    const values = value || [];
    let i = items.indexOf(removed);
    items.splice(i, 1);
    values.splice(i, 1);
    const l = items.length;
    while (i < l) {
      items[i].setKey(i);
      i++;
    }
    removed.remove();
    const pureValues = values.map((item: any) => typeof(item) === 'object' ? Object.assign({}, item):item);
    onChange?.(pureValues);
  }

  componentWillUnmount() {
    this.state.items.forEach((field) => {
      field.purge();
    });
  }

  render() {
    const { hideDescription, extraProps = {} } = this.props;
    const { renderFooter } = extraProps;
    let columns: any = null;
    const { items } = this.state;
    const { scrollToLast } = this;
    this.scrollToLast = false;
    if (this.props.columns) {
      columns = this.props.columns.map((column) => (
        <Title key={column.name} title={column.title || (column.name as string)} />
      ));
    }

    const lastIndex = items.length - 1;

    const content =
      items.length > 0 ? (
        <div className="lc-setter-list-scroll-body">
          <Sortable itemClassName="lc-setter-list-card" onSort={this.onSort.bind(this)}>
            {items.map((field, index) => (
              <ArrayItem
                key={index}
                scrollIntoView={scrollToLast && index === lastIndex}
                field={field}
                onRemove={this.onRemove.bind(this, field)}
              />
            ))}
          </Sortable>
        </div>
      ) : (
        <div className="lc-setter-list-notice">
          {this.props.multiValue ? (
            <Message type="warning">当前选择了多个节点,且值不一致,修改会覆盖所有值</Message>
          ) : (
            <Message type="notice" size="medium" shape="inline">
              暂时还没有添加内容
            </Message>
          )}
        </div>
      );

    return (
      <div className="lc-setter-list lc-block-setter">
        {!hideDescription && columns && items.length > 0 ? (
          <div className="lc-setter-list-columns">{columns}</div>
        ) : null}
        {content}
        <div className="lc-setter-list-add">
          {
            !renderFooter ? (
              <Button text type="primary" onClick={() => {
                this.onAdd()
              }}>
                <span>添加一项 +</span>
              </Button>
            ) : renderFooter({...this.props, onAdd: this.onAdd.bind(this),})
          }
        </div>
      </div>
    );
  }
}
class ArrayItem extends Component<{
  field: IPublicModelSettingField;
  onRemove: () => void;
  scrollIntoView: boolean;
}> {
  private shell?: HTMLDivElement | null;

  componentDidMount() {
    if (this.props.scrollIntoView && this.shell) {
      this.shell.parentElement!.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }
  }

  render() {
    const { onRemove, field } = this.props;
    return (
      <div
        className="lc-listitem"
        ref={(ref) => {
          this.shell = ref;
        }}
      >
        <div className="lc-listitem-body">{createSettingFieldView(field, field.parent)}</div>
        <div className="lc-listitem-actions">
          <Button size="small" ghost="light" onClick={onRemove} className="lc-listitem-action">
            <CustomIcon type="icon-ic_delete" />
          </Button>
          <Button draggable size="small" ghost="light" className="lc-listitem-handler">
            <CustomIcon type="icon-ic_drag" />
          </Button>
        </div>
      </div>
    );
  }
}

class TableSetter extends ListSetter {
  // todo:
  // forceInline = 1
  // has more actions
}

export default class ArraySetter extends Component<{
  value: any[];
  field: IPublicModelSettingField;
  itemSetter?: IPublicTypeSetterType;
  mode?: 'popup' | 'list';
  forceInline?: boolean;
  multiValue?: boolean;
}> {
  static contextType = PopupContext;

  private pipe: any;

  render() {
    const { mode, forceInline, ...props } = this.props;
    const { field, itemSetter } = props;
    let columns: IPublicTypeFieldConfig[] | undefined;
    if ((itemSetter as IPublicTypeSetterConfig)?.componentName === 'ObjectSetter') {
      const items: IPublicTypeFieldConfig[] = (itemSetter as any).props?.config?.items;
      if (items && Array.isArray(items)) {
        columns = items.filter(
          (item) => item.isRequired || item.important || (item.setter as any)?.isRequired,
        );
        if (columns.length > 4) {
          columns = columns.slice(0, 4);
        }
      }
    }

    if (mode === 'popup' || forceInline) {
      const title = (
        <Fragment>
          编辑:
          <Title title={field.title} />
        </Fragment>
      );
      if (!this.pipe) {
        let width = 360;
        if (columns) {
          if (columns.length === 3) {
            width = 480;
          } else if (columns.length > 3) {
            width = 600;
          }
        }
        this.pipe = this.context.create({ width });
      }

      this.pipe.send(<TableSetter key={field.id} {...props} columns={columns} />, title);
      return (
        <Button
          type={forceInline ? 'normal' : 'primary'}
          onClick={(e) => {
            this.pipe.show((e as any).target, field.id);
          }}
        >
          <CustomIcon type="icon-bianji" size="small" />
          {forceInline ? title : '编辑数组'}
        </Button>
      );
    } else {
      return <ListSetter {...props} columns={columns?.slice(0, 4)} />;
    }
  }
}

然后就是注册这个设置器,我们找到 src\plugins\plugin-custom-setter-sample\index.tsx

import { IPublicModelPluginContext } from '@alilc/lowcode-types';
import NewArraySetter from 'src/setters/new-array-setter';

// 保存功能示例
const CustomSetterSamplePlugin = (ctx: IPublicModelPluginContext) => {
  return {
    async init() {
      const { setters } = ctx;

      setters.registerSetter('NewArraySetter', NewArraySetter);
    },
  };
}
CustomSetterSamplePlugin.pluginName = 'CustomSetterSamplePlugin';
export default CustomSetterSamplePlugin;

这里清除了没用的代码,如果拿不准不删除也是完全没问题的
最后就是怎样使用我们新建的设置器,回到物料库,找到对应组件的描述文件:lowcode/xxx/meta.ts
image.png
在此处指名要使用到的设置器即可

如果是跟着我物料库的文章做下来的,那更简单,直接在对应组件属性的注释当中声明即可
image.png
需要注意,之前的逻辑下必须要在 inject.config.js 配置了对应的组件才行,可以简单配置 group 或者 category 就能看到效果

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

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

相关文章

实现文本 内容展开 / 收起

<template><el-table :data"tableData" style"width: 100%" height"250"><el-table-columnfixedprop"date"label"日期"width"150"></el-table-column><el-table-columnprop"name…

geemap学习笔记040:GEE中样本点选择操作流程

前言 geemap中目前有一个bug&#xff0c;就是在选择样本点的时候不合理&#xff0c;选完一类样本之后&#xff0c;没法继续选择下一类&#xff0c;并且没法在线进行编辑和修改。因此目前就只能结合在线版的GEE进行样本选择&#xff0c;本节就详细的介绍一下GEE中样本点的选择过…

HCIA-Datacom题库(自己整理分类的)——其他网络协议【完】

&#xff08;一&#xff09;单选 下列属于链路状态协议的是? Direct static FTP OSPF 解析&#xff1a; FTP&#xff1a;文件传输协议 OSPF&#xff1a;链路状态路由协议 如下图所示的网络主机A通过Telnet登录到路由器A然后在远程的界面通过FTP获取路由器的配置文件&…

multipath 内核接口及框架介绍

文章目录 1 云主机使用网络存储 io 流程2 multipath 介绍 1 云主机使用网络存储 io 流程 对于一个云服务环境&#xff0c;大致会有网络节点&#xff0c;存储节点&#xff0c;计算节点&#xff0c;控制节点&#xff0c;其中虚拟云主机在计算节点工作&#xff0c;而虚拟云主机&a…

数据结构——二叉树四种遍历的实现

目录 一、树的概念 1、树的定义 1&#xff09;树 2&#xff09;空树 3&#xff09;子树 2、结点的定义 1&#xff09;根结点 2&#xff09;叶子结点 3&#xff09;内部结点 3、结点间关系 1&#xff09;孩子结点 2&#xff09;父结点 3&#xff09;兄弟结点 4、树…

人工智能在金融领域的应用存在的4大挑战

金融服务供应商应该有计划地应对AI面临的难题 金融行业投资人工智能热潮带来有关数据安全和透明度的新问题。由于数据管理实践随着新的 AI 解决方案的引入而不断发展&#xff0c;应对这些新问题以及金融服务领域 AI 面临的其他挑战尤为重要。各组织必须认识到可能面临以下挑战…

网络安全—IPSec安全策略

文章目录 网络拓扑添加策略ESP添加筛选器添加筛选器的操作另一台主机设置 AH 使用Windows Server 2003系统 网络拓扑 client1 IP 192.168.17.105client2 IP 192.168.17.106 只要保证两个主机在同一网段接口&#xff0c;即互相ping通即可完成策略的实现 下面的所有通讯都只是…

Vue 模板编译原理解析

Vue 模板编译原理解析 模板编译整体流程 首先我们看一下什么是编译&#xff1f; 所谓编译&#xff08;Compile&#xff09;&#xff0c;指的是将语言 A 翻译成语言 B&#xff0c;语言 A 就被称之为源码&#xff08;source code&#xff09;&#xff0c;语言 B 就被称之为目标…

半导体设备系列:半导体制造产能扩张,设备零部件需求旺盛

近年来国内半导体制造产能不断扩张&#xff0c;半导体设备厂商加速成长。我们认为下游发展将拉动上游本地化配套需求&#xff0c;半导体设备零部件迎来高增长阶段。 摘要 半导体设备零部件包含密封圈、EFEM、射频电源、静电吸盘、硅电极、真空泵、气体流量计、喷淋头等产品&a…

实时数据处理概述与Spark Streaming简介

实时数据处理已经成为当今大数据时代的一个重要领域&#xff0c;它使组织能够及时分析和采取行动&#xff0c;以应对不断变化的数据。Spark Streaming是Apache Spark生态系统中的一个模块&#xff0c;专门用于实时数据处理。本文将深入探讨实时数据处理的概念&#xff0c;并介绍…

leetcode贪心(单调递增的数字、监控二叉树)

738.单调递增的数字 给定一个非负整数 N&#xff0c;找出小于或等于 N 的最大的整数&#xff0c;同时这个整数需要满足其各个位数上的数字是单调递增。 &#xff08;当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。&#xff…

一文教会git如何配置多SSH Key(Github+Gitlab)

一文教会git如何配置多SSH Key&#xff08;GithubGitlab&#xff09; 对于工作了的伙伴来说&#xff0c;大家经常会配置多个SSH Key&#xff1a; SSH拉取的仓库不会反复的要求输入github用户名密码来验证自己的身份需要将公司的Gitlab和个人的Github区分开 我这里以Mac电脑为例…

次梯度算法介绍

系列文章目录 最优化笔记&#xff0c;主要参考资料为《最优化&#xff1a;建模、算法与理论》 文章目录 系列文章目录一、次梯度1 定义2 存在性 二、次梯度的计算1 按定义计算2 常用计算规则 三、最优性条件1 无约束优化问题2 约束优化问题 四、次梯度算法1 迭代格式2 收敛性 参…

sql关键字——with 子查询,row_number()排名函数,lag()函数用法举例

题目&#xff1a; 查询所有选修"英语"的学生成绩与前一名的分数差距&#xff0c;按照成绩降序排序。 针对以上需求&#xff0c;有两种做法 1.使用lag函数 lag()函数&#xff0c;取当前行的上一列&#xff0c;用法是lag(列&#xff0c;往上取的行数&#xff0c;填充…

使用Python做个可视化的“剪刀石头布”小游戏

目录 一、引言 二、环境准备与基础知识 三、游戏界面制作 四、游戏逻辑实现 五、代码示例 六、游戏测试与优化 七、扩展与改进 八、总结 一、引言 “剪刀石头布”是一种古老的手势游戏&#xff0c;它简单易懂&#xff0c;趣味性强&#xff0c;适合各个年龄段的人参与。…

Excel·VBA二维数组组合函数的应用实例之概率计算

看到一个视频《李永乐老师的抖音 - 骰子概率问题》&#xff0c;计算投出6个骰子恰好出现1、2、3、4、5、6这6个点数的概率 李永乐老师的计算方法是&#xff0c;第1个概率为1即6/6&#xff0c;第2个不与之前相同的概率为5/6&#xff0c;第3个同理概率为4/6&#xff0c;因此该问…

深度学习:大规模模型分布式训练框架DeepSpeed

深度学习&#xff1a;大规模模型分布式训练框架DeepSpeed DeepSpeed简介DeepSpeed核心特点DeepSpeed如何工作&#xff1f;DeepSpeed如何使用&#xff1f;参考文献 DeepSpeed简介 随着机器学习模型变得越来越复杂和庞大&#xff0c;训练这些模型所需的计算资源也在不断增加。特别…

【已解决】Invalid bound statement (not found)

报错讯息 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.casey.mapper.SysRoleMapper.getUserRoleCode at org.apache.ibatis.binding.MapperMethod S q l C o m m a n d . < i n i t > ( M a p p e r M e t h o d . j a v a :…

数据库MYSQL no.2

1.加法 加法&#xff1a;在java中有运算和拼接的功能&#xff0c;但是数据库中加号只做运算。 拼接是concat&#xff08;...&#xff0c;...&#xff09; 2. IFNULL ifnull(字段&#xff0c;0) 为null就返回逗号后面的东西0. 3.条件查询&#xff1a; 1.条件表达式 &#…

第84讲:基于各种场景使用mysqldump逻辑备份数据库

文章目录 1.mysqldump备份工具的语法格式2.使用mysqldump进行全库备份3.备份单个库或者多个库的数据4.备份某个库下的单表或者多表的数据5.mysqldump备份数据库时必加的一些参数5.1.基本参数5.2.核心参数 6.mysqldump备份数据库时的一些其他参数 1.mysqldump备份工具的语法格式…