august-23th项目总结


项目总结

前端复制插件

import copy from 'copy-to-clipboard';

// 复制事件 放入copy函数即可复制成功
handleCopy = text => {
    if (copy(text)) {
        message.success({
            content: '复制成功',
            duration: 1
        });
    }
};
<span
    title='复制'
    className={styles.servicesUrlCopy}
    onClick={this.handleCopy.bind(this, text)}
>
      <CopyOutlined />
</span>

react 项目中实现一键复制

import {FC, useCallback} from 'react';
import {Toast} from 'antd-mobile';
import {CopyToClipboard} from 'react-copy-to-clipboard';
import c from './index.less';

interface Props {
    style?: any;
    value: string | number;
}

const Copy: FC<Props> = ({value, style}) => {
    const onCopy = useCallback(
        () => {
            Toast.show({icon: 'success', content: '复制成功'});
        },
        []
    );

    return (
        <CopyToClipboard
            text={value}
            onCopy={onCopy}
        >
            <span style={style} className={c.copy}></span>
        </CopyToClipboard>
    );
};

export default Copy;

移动端 @vw

// 使用自适应单位vw 除以 设备单位 获得相对于当前设备的等比例单位

// 生成的 @vw 单位是一种单位,就像px 一样,

// 因此可以用于移动端的 width 和 height,

// 区别在于 @vw是等比例的

// 会自动调节宽高

// 值得注意的是,一般会给定宽度,高度不给定。

@device-width: 1242;
@vw: (100vw/@device-width);

文件暴露方法

// getRegions.ts

export const getRegions = (nums: number) => {
  ...
  ..
};
// index.ts

export * from './getRegions';
export * from './getCode';
// component use

import {getRegions} from '@/utils';

监听 useForm 的值

/**
* Antd useForm 是自动监听表单值的变化的,(但目前谷歌浏览器似乎并不支持)
* 
* 变化后打印的确能看到最新值,
*
* 但如果希望 用  form.getFieldValue(FormKeys.result) 的方式,以这个值,
*
* 在节点中以此判断dom的显示隐藏,
*
* 会发现 表单域 值变化时,
*
* render函数中 没有观察到这个值的变化,
*
* 也就是说表单域管控的值,在变化时,无法引起视图更新!
*
* 因此以这种方式动态控制节点的显示隐藏,并不可行!
*/

// antd useWatch 官方介绍: 允许你监听字段变化,同时仅当该字段变化时重新渲染。

const radioValue = Form.useWatch(FormKeys.result, form)

// form.useWatch 可以在表单值变化时,监听到值的变化,改字段变化也会导致react重新渲染。

// 利用这一机制,我们能在表单值发生变化时,因为重新渲染的缘故,

// 将这个值更新到视图,即render函数。

// 这样就解决了我们上面遇到的问题 (表单域管控的值,在变化时,无法引起视图更新!)。

git reset –soft

在push代码时,如果包含多个commit,这个时候提交就会有问题,

提示不能提交多个。

那我们可以使用此命令删除commit, 
  
不用担心会删除已有的代码,

它只会删除commit记录

mac里git项目删除.DS_Store文件

在项目的 .gitignore 文件下,

写入

*/.DS_Store
.DS_Store


// 删除 stage 中的 .DS_Store 文件

git rm --cached filename

git commit -m "delete file"

git push origin

参考资料: http://t.zoukankan.com/zourong-p-5962240.html

formData 和 json 传参

// formDaTa

/**
*  formData传参,
*
* 直接将formData
*/
const createFunc = useCallback(
        async () => {
                const nowDate = (new Date()).valueOf();
                const unixSecond = nowDate.toString().substring(0, 10);

                const params: CreateOrder = {
                    timestamp: unixSecond,
                    act_id: actId,
                    asset_id: assetId,
                    return_url: 'https://www.karlfranz.com',
                };
                const formData = new FormData();
                Object.keys(params).forEach(key => {
                    formData.append(key, params[key as CreateOrderType]);
                });

                const res = await service1.getBuyUrl(formData);
                location.href = (res as any)?.data?.order_url;
            },
        [actId, assetId]
    );
// json传参

直接以对象形式传入

// formData 和图片的结合使用。

debugger

想要确定代码的执行顺序?

不用分好几个地方打印,

在你想确定顺序的几个地方,

都写下debugger,

然后打开控制台

就能一步一步点小三角确定代码的执行顺序了。

确定代码逻辑的执行顺序,

有时能解决很多bug,

也能少走弯路,

能以更清晰简便的方式,

解决问题。

antd 单项校验

// 注意 定义的 validator 的校验会先执行,validateFields 的 catch 会后走

this.formModalRef.current.form.validateFields(['bus']);

// 相关问题

/**
* 我们提交表单数据到接口,
*
* 后端接口返回一个重复码 1110,
* 
* 表示字段重复,
* 
* 前端从接口获得这个码,
*
* 并根据这个码存一个状态。
*
* 在自定义校验中以这个状态为基准,判断是否赋予这条校验规则。
*/

if (res.code === 1110) {
    this.setState({isShow: true});
}

/**
* 但是我们的校验,或者说form item 配置是一个数组,且在 class 组件的 state中,
* 
* 并且经过 debugger 查看执行顺序,表单校验会先走,
* 
* 接口后走,
*
* 那么调完接口,表单校验已经走过了,
*
* 那么我们没法在接口走完再根据 那个状态 校验表单,
* 
* 而我们希望调完接口再校验表单。
*
* 办法是,
*
* 再校验一次,
*
* 那么就是,在调接口前后我们都进行了校验,
* 
* 而我们需要的是后一次。
* 
* 这样就完成了我们所希望的。
*/

if (res.code === 1110) {
    this.setState({isShow: true});
    this.formModalRef.current.form.validateFields(['bus']);
}

vue 自动刷新

// 自动刷新逻辑

// main.js

/**
*
* @param {roterkey} roterkey 当前路由包含的唯一路径
*
* fn 组件中传入的,需要轮询的函数体
*/

Vue.prototype.autoChangeMethods = function(roterkey, fn) {
  if (location.pathname.includes(roterkey)) {// 如果当前url包含组件路由传入的路径key, 执行此判断
    setTimeout(() => {                       // 即 : 位于当前页面。
      fn()                                   // 确保在切换到另一个需要轮询页面时,
    }, 5000); // 统一配置的轮询时间             // 停止上一个页面的轮询,
  }                                          // 开启当前页面的轮询。
  return;                                    // 避免额外的性能开销。
};

// 组件中使用

/**
* 
* 定义被轮询函数体,
*
* 这个函数体需要的参数可能是多种多样的,
*
* 函数名autoChange
*/

autoChange () {
  this.autoChangeMethods(
    'apply',
    () =>{
      this.getTableList() // 进入此函数即刷新列表一次,获取tabel 列表的函数
      const flag = this.tableData.filter(item => {
        return this.status.includes(item.state);
      });
      
      if (flag[0]) {// 存在表明存在中间状态
        this.autoChange(); // 继续递归
        return;     // 此时轮询仍在继续,watch的监听函数不能被再次触发!不执行下面的修改语句
      }
      this.isRunning = false; // 将已经调用的状态置为 false
    }
  )
}

watch: { // 起初次调用的效果,tableData 数据变化时触发
  tableData: { // 监听列表数据
    handel(now) {
      const isHaveMiddleStatus = Array.isArray(now)
      && now.filter(item => {
        return this.status.includes(item.state);
      });
      if (isHaveMiddleStatus[0] && !this.isRunning) { // 存在中间状态且
        this.autoChange();                            // 是第一次调用
        this.isRunning = true; //避免多次触发           // 改为 true 表明 第一次已调用
        return;
      }
    }
  }
}

react renderList 事件委托

/**
*
* 这种方式不用给每一个子元素绑定一个事件,
*
* 只需给父元素绑定一个事件,
*
* 处理子元素不同的数据或是下标,
* 
* 给子节点都注册事件更加耗费性能,因此这也是一种性能优化的手段。
*/

/**
* 以下不用map来更新数组的方式更加节省性能。
*
* 都可以引发重渲。
*/

import React, {
    useState,
    MouseEvent,
} from "react";

interface ListType {
  name: string;
  key: number;
}

const list = [
    {
        name: "王之哈默",
        key: 1,
    },
    {
        name: "光之惩戒",
        key: 2,
    },
    {
        name: "圣灵谱尼",
        key: 3,
    },
    {
        name: "启灵元神",
        key: 4,
    },
];

const APP = () => {
  
  const [saier, setSaier] = useState<ListType[]>();

  const handeleClick = (
    e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>
  ) => {
    const key = Object.keys(e.target)[1]; // 取得target 对象的第二个key
    const changeObj = (e.target as any)[key]; // 取得这个key 所对应的值
    
    // 为什么要这样取? 因为第二个key的 键 是不断变化的
    const { style, ..._other } = changeObj;

    console.log(
        "other data-item",
        _other["data-item"], // 我们自定义属性所传的 item
        "index",
        _other["data-index"] // 我们自定义属性所传的 index
    );

    const newList = [...(saier as any)];
    const newItem = { key: _other["data-item"]["key"], name: "圣霆雷伊" };

    // type one 改变数组空间地址
    // newList[_other['data-index']] = newItem;
    // setSaier(newList);

    // type two 不改变空间地址,同样使页面刷新了,??,不是新对象的原因!
    
    // 直接 _other['data-item']['name'] = '圣霆雷伊' 不set也能触发更新?为何
    
    setSaier((state) => {
        (state as any)[_other["data-index"]] = newItem;
        return state;
    });
  };
  
  return (
        <div style={{ height: 100 }} onClick={handeleClick}>
          {Array.isArray(saier) &&
              saier.map((ele, index) => (
                  <p
                      style={{ height: 10 }}
                      key={ele.key}
                      data-item={ele}
                      data-index={index}
                  >
                      {ele.name}
                  </p>
              ))}
        </div>
    )
};

export default App;

气泡省略组件

// 作用: 当文本内容过多,hover上去会出现省略效果

/* eslint-disable comma-dangle */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/prefer-for-of */

/**
 * Ellipsis Popover
 * 
 */

import {Popover} from 'antd';
import {FC, useRef, useState} from 'react';

interface PopoverProps {
    text: string | React.ReactNode;
}

let currBubbleCallback = (state: boolean) => {};
const showCurrBubble = () => currBubbleCallback(true);
const hideCurrBubble = () => currBubbleCallback(false);

const EllipsisPopover: FC<PopoverProps> = ({text}) => {
    const [isVisible, setIsVisible] = useState(false);
    const eleRef = useRef<HTMLDivElement>(null);
    const timeRef = useRef<any>(undefined); // timeoutID

    const renderBubble = (state: boolean) => {
        // 有上一个泡时
        if (timeRef.current !== undefined) {
            clearTimeout(timeRef.current);
            timeRef.current = undefined;
        }
        setIsVisible(state);
    };

    const hasEllipsisFn = () => {
        const ele = eleRef.current;
        return ele && ele.scrollWidth > ele.offsetWidth;
    };

    const handleMouseEnter = () => {
        // 现在的泡可以用上一个泡的renderBubble来隐藏上一个泡
        currBubbleCallback(false); // 隐藏上一个泡
        currBubbleCallback = renderBubble; // 把现在泡的renderBubble给下一个
        if (hasEllipsisFn()) {
            setIsVisible(true);
        }
    };

    const handleMouseLeave = () => {
        const popDom = document.getElementsByClassName('ant-popover-inner');
        for (let i = 0; i < popDom.length; ++i) {
            const dom = popDom[i];
            // 给每个泡监听器
            if (dom.getAttribute('listener') !== 'true' && dom?.children?.length < 2) {
                dom.addEventListener('mouseenter', showCurrBubble);
                dom.addEventListener('mouseleave', hideCurrBubble);
                dom.setAttribute('listener', 'true');
            }
        }

        timeRef.current = setTimeout(() => {
            renderBubble(false);
        }, 500);
    };

    return (
        <Popover
            content={
                <div
                    style={{
                        overflow: 'auto',
                        maxHeight: '132px',
                        maxWidth: '310px',
                        lineHeight: '26px'
                    }}
                >
                    {text}
                </div>
            }
            visible={isVisible}
        >
            <div
                ref={eleRef}
                onMouseEnter={handleMouseEnter}
                onMouseLeave={handleMouseLeave}
                style={{
                    display: 'block',
                    overflow: 'hidden',
                    textOverflow: 'ellipsis',
                    whiteSpace: 'nowrap'
                }}
            >
                {text}
            </div>
        </Popover>
    );
};

export default EllipsisPopover;


// 组件中使用 用于表格展示

import EllipsisPopover from '@/components/EllipsisPopover';

const columns = [
    {
        width: '20%',
        ellipsis: true,
        title: '审核时间',
        dataIndex: 'modifiedTime',
        render: (text: string) => <EllipsisPopover text={sendForamt(text)} />,
    },
    {
        width: '10%',
        ellipsis: true,
        title: '审核结果',
        dataIndex: 'status',
        render: (text: string) => <EllipsisPopover text={text} />,
    },
  ...
  ..
  ];

引入antd 组类型

import {FormProps} from 'rc-field-form/lib/Form';

antd 使用useForm值判断dom隐显

Antd useForm 是自动监听表单值的变化的,

变化后打印的确能看到最新值,

但如果希望 用  form.getFieldValue(FormKeys.result) 的方式以这个值,

在节点中以此判断dom的显示隐藏,

会发现 form.getFieldValue(FormKeys.result) 值变化时,

发现render函数中 没有观察到这个值的变化,

也就是说表单域管控的值,在变化时,无法引起视图更新!

因此以这种方式动态控制节点的显示隐藏,并不可行!

echarts 版本和 event-source-polyfill 版本不兼容

echarts 4x 和  event-source-polyfill 配合使用,

5x 和 3x 配合使用,

项目里是 4x 配合 3x, 不兼容报错了,修改  event-source-polyfill 版本 至2x。

重新安装 event-source-polyfill 版本依然报错,

删除整个依赖重装,发现就好了。

history.svg?react 有多余title?

在我们使用 svg 图标时,图标确实可以正常显示,

但hover上去发现有不该显示的内容,

这时一审查元素发现svg图也是一个标签,

在它的内部的title正好是hover上去不该显示的内容。

这时,我们将这个svg以备忘录以typora打开,

删去哪一行title,

就发现项目上的图标hover上去不会再显示无关内容了。
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>4.图标元件/7.通用/4.电话/电话-线备份 5</title> /** 删除这一行**/
    <g id="页面说明" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="待审核名单" transform="translate(-1329.000000, -2117.000000)">
            <g id="编组-23" transform="translate(1329.000000, 2115.000000)">
                <g id="4.图标元件/7.通用/4.电话/电话-线备份-5" transform="translate(0.000000, 2.000000)">
                    <rect id="矩形" stroke="#2468F2" fill="#2468F2" opacity="0" x="0.5" y="0.5" width="15" height="15"></rect>
                    <path d="M11,9 C9.34329471,9 8,10.343604 8,12 C8,13.6569792 9.34302082,15 11,15 C12.6569792,15 14,13.6569792 14,12 C14,10.343604 12.6567053,9 11,9 Z M11,10 C12.1043892,10 13,10.895817 13,12 C13,13.1046861 12.1046861,14 11,14 C9.8953139,14 9,13.1046861 9,12 C9,10.895817 9.89561076,10 11,10 Z" id="Stroke-3" fill="#2468F2" fill-rule="nonzero"></path>
                    <path d="M10.1,12.5 L10.1,12 L10.8,12 L10.8,11 L11.3,11 L11.3,12.5 L10.1,12.5 Z" id="形状结合" fill="#2468F2" transform="translate(10.700000, 11.750000) scale(-1, -1) rotate(-180.000000) translate(-10.700000, -11.750000) "></path>
                    <g id="编组" transform="translate(3.000000, 1.500000)">
                        <polyline id="Stroke-1" stroke="#2468F2" points="4.60462219 13.0987 0 13.0987 0 0.0987 7 0.0987 10 3.0987 10 7.0987"></polyline>
                        <polygon id="Fill-3" fill="#2468F2" points="6.1983 4.001 7.1983 4.001 7.1983 0 6.1983 0"></polygon>
                        <polygon id="Fill-4" fill="#2468F2" points="6.1983 4.001 10.1993 4.001 10.1993 3 6.1983 3"></polygon>
                        <polygon id="Fill-5" fill="#2468F2" points="2 6.588 8 6.588 8 5.588 2 5.588"></polygon>
                        <polygon id="Fill-6" fill="#2468F2" points="2 9 4.5 9 4.5 8 2 8"></polygon>
                    </g>
                </g>
            </g>
        </g>
    </g>
</svg>

ReactSVG 插件

import React from 'react';
import { ReactSVG } from 'react-svg';

// 这种引入 、使用方式不行!
// import { historyIcon, waitExamineIcon } from 'assets/name';

// 第二种可以
import history from 'assets/name/history.svg';
import waitexamine from 'assets/name/waitexamine.svg';

interface SvgIconProps {
  [propsName: string]: any;
}

class SvgIcon extends React.Component<SvgIconProps> {
  render(): React.ReactNode {
    return (
      <div>
        {/* <p>{historyIcon()}历史</p> */}
        {/* <p>{waitExamineIcon()}记录</p> */}

        <ReactSVG src={history} />
        <ReactSVG src={waitexamine} />
      </div>
    );
  }
}

export default SvgIcon;
// index.less 
// 改变svg大小
.wrapper {
    width: 200px;
    height: 200px;
}

.wrapper path {
    // 改变颜色
    fill: red;
    height: 190px;
    width: 190px;
}

统一定制化 formModal

/**
* 定义一个数组结构,这个结构表明了将渲染的组件有多少。
*
* 这个结构可以非常灵活,文本、校验,甚至自定义校验都可,定义好参数就好。
*
* 将ref传给form,并在from内部使用 useImperativeHandle 暴露 useform 生成的实例,
*
* 将这个暴露的 form 实例传递给 定制化组件使用 (这个组件的样式、逻辑是多变的),
*
* 同时记得这个定制化组件也是form的item。
*
* 他在fromModal内部只是被展示出来了,
* 
* 不参与form中顶部组件的循环渲染。
*
* 但因为他也是form的item,
* 
* 所以表单域同样可以拿到他所有的值。
*/

this.state = {
    formList: [
      {
          label: '用户名称',
          name: 'username',
          require: true,
          rules: [
              {
                  min: 4,
                  max: 16,
                  message: '请输入4~16个字符'
              },
              {
                  pattern: new RegExp(/^[\u4e00-\u9fa5a-zA-Z]+$/, 'g'),
                  message: '只允许输入中文或英文字母'
              }
          ],
          props: {
              placeholder: '请输入业务方名称'
          }
      },
      {
          label: '用户账户',
          name: 'account',
          require: true,
          rules: [
              {
                  min: 6,
                  max: 20,
                  message: '请输入6~20个字符'
              },
              {
                  pattern: new RegExp(/^[A-Za-z]*$/, 'g'),
                  message: '只允许输入英文字母'
              }
          ],
          props: {
              placeholder: '创建后不允许更改',
              message: '请输入用户账户'
          }
      },
      {
          label: '用户密码',
          name: 'password',
          require: true,
          formType: 'password',
          rules: [
              // {
              //     // eslint-disable-next-line max-len
              //     pattern: new RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@!%*?&])[A-Za-z\d$@!%*?&]/, 'g'),
              //     message: '需要大小写字母、数字、特殊符号至少各一个(特殊符号仅包含以下几种:$@!%*?&)'
              // },
              {
                  min: 8,
                  max: 32,
                  message: '请输入8~32个字符'
              }
          ],
          props: {
              placeholder: '请设置密码',
              onClick: this.setPasswordVisibility.bind(this, true),
              autoComplete: 'new-password'
          }
      }
  ],
}


this.formModalRef = createRef();
this.addPermissionRef = createRef();

const defaultFormModalProps = {
  width: 490,
  cRef: this.formModalRef,
  labelCol: 5,
  wrapperCol: 19,
  formList,
  onFinish: this.handleSubmit,
  onCancel: () => {
      this.addPermissionRef.current.clearSelect();
  }
};

<FormModal {...defaultFormModalProps}>
  <p className={styles.modalDesc}>说明:.......</p>
  <Divider className={styles.modalDivider} />
  <AddPermissions
      cRef={this.addPermissionRef}
      form={this.formModalRef?.current?.form}
      selectList={serverList}
  />
</FormModal>
// AddPermissions

import React, {useState, useEffect, useImperativeHandle} from 'react';
import {Form, Select, Button, Input} from 'antd';
import {CloseCircleFilled, PlusOutlined} from '@ant-design/icons';
import styles from './index.less';

const AddPermissions = ({cRef, selectList, form}) => {
const [list, setList] = useState([]);
const [selected, setSelected] = useState();
useEffect(() => {
    setList(selectList.map(item => ({
        label: item.name,
        value: JSON.stringify({value: item.code, type: item.type}),
        disabled: item.disabled || false
    })));
}, [selectList]);
// 将选中的元素清空
const clearSelect = () => {
    setSelected([]);
    setList(selectList.map(item => ({
        label: item.name,
        value: JSON.stringify({value: item.code, type: item.type}),
        disabled: false
    })));
};
let addFn;
// 选中元素后禁用,并放置末尾
const handleClickDisabled = () => {
    setTimeout(() => {
        if (form) {
            let res = form.getFieldValue('permission');
            setSelected(res);
            let disabledAry = [];
            res.map(item => {
                if (item?.serviceCode === undefined) {
                    return;
                }
                let index = list.findIndex(attr => attr.value === item.serviceCode);
                if (index > -1) {
                    disabledAry.push(index);
                }
            });
            let _list = list.map((item, index) => {
                item.disabled = disabledAry.findIndex(attr => attr === index) > -1;
                return item;
            });
            _list = _list.sort((a, b) => a.disabled - b.disabled);
            setList(_list);
        }
    });
};

// 根据select选择的内容,设置右侧的内容
const setContent = (key, need) => {
    // need:content 文本  unit 单位
    const contentObj = {
        HOUR: '每日最大处理量',
        QPS: '瞬时最大并发量',
        CONNECTION: '瞬时最大并发量'
    };
    const unitObj = {
        HOUR: '小时',
        QPS: '并发',
        CONNECTION: '并发'
    };
    if (!selected) {
        return need === 'unit' ? unitObj.HOUR : contentObj.HOUR;
    }
    if (!selected[key]) {
        return need === 'unit' ? unitObj.HOUR : contentObj.HOUR;
    }
    const res = JSON.parse(selected[key].serviceCode).type;
    return (need === 'unit' ? unitObj : contentObj)[res];
};
useImperativeHandle(cRef, () => ({
    addFn: addFn,
    clearSelect,
    handleClickDisabled
}));
return (<div>
    <Form.List
        name="permission"
        labelCol={{span: 0}}
        wrapperCol={{span: 24}}
    >
        {(fields, {add, remove}) => {
            addFn = add;
            return (
                <div>
                    <Form.Item>
                        <Button
                            disabled={fields.length >= list.length}
                            onClick={() => {
                                add();
                            }}
                            type='primary'
                            icon={<PlusOutlined/>}
                        >添加服务权限</Button>
                    </Form.Item>
                    {fields.map(field => (
                        <Form.Item
                            key={field.key}
                            style={{marginBottom: 0}}
                            labelCol={{span: 0}}
                            wrapperCol={{span: 24}}
                        >
                            <div className={styles.addIcon}>
                                <div className={styles.addIcon}>
                                    <Form.Item
                                        {...field}
                                        key={field.key + '1'}
                                        name={[field.name, 'serviceCode']}
                                        fieldKey={[field.fieldKey, 'serviceCode']}
                                        rules={[
                                            {
                                                required: true,
                                                message: '请选择服务名称'
                                            }
                                        ]}
                                        className={styles.addIcon}
                                    >
                                        <Select
                                            onChange={handleClickDisabled}
                                            className={styles.addIcon}
                                            placeholder='请选择服务名称'
                                            options={list}
                                        />
                                    </Form.Item>
                                    <span className={styles.addIcon}>
                                        {setContent(field.fieldKey)}
                                    </span>
                                    <Form.Item
                                        {...field}
                                        key={field.key + '2'}
                                        name={[field.name, 'value']}
                                        fieldKey={[field.fieldKey, 'value']}
                                        rules={[
                                            {
                                                required: true,
                                                message: '请输入数字'
                                            },
                                            {
                                                validator: (rule, value) => {
                                                    if (value === undefined || value === '') {
                                                        return Promise.resolve();
                                                    }
                                                    const reg = /^-?\d*(\.\d*)?$/;
                                                    if ((!isNaN(value) && reg.test(value))) {
                                                        if (/-/.test(`${value}`)) {
                                                            return Promise.reject('请输入正数');
                                                        }
                                                        return Promise.resolve();
                                                    }
                                                    return Promise.reject('请输入数字');
                                                }
                                            }
                                        ]}
                                        className={styles.addIcon}
                                    >
                                        <Input
                                            placeholder='请输入'
                                            className={styles.addIcon}
                                        />
                                    </Form.Item>
                                    <span className={styles.addIcon}>
                                        {setContent(field.fieldKey, 'unit')}
                                    </span>
                                </div>
                                {fields.length > 1 ? (
                                    <CloseCircleFilled
                                        className={styles.addIcon}
                                        onClick={() => {
                                            remove(field.name);
                                            handleClickDisabled();
                                        }}
                                    />
                                ) : <span className={styles.addIcon}/>}
                            </div>
                        </Form.Item>
                    ))}
                </div>
            );
        }}
    </Form.List>
</div>);
};
export default AddPermissions;
// FormModal

import React, {useState, useImperativeHandle, useEffect} from 'react';
import {Modal, Form} from 'antd';
import renderForm from '@/utils/renderForm';
import styles from './index.less';

const FormModal = ({cRef, formList, title: _title, width, wrapperCol, labelCol, children, onCancel, onFinish, key, ...props}) => {
    const [visible, setVisible] = useState(false);
    const [title, setTitle] = useState(_title || '通知');
    const [form] = Form.useForm();
    const handleOk = e => {
        form.submit();
    };
    useEffect(() => {
        if (!visible) {
            if (form) {
                form.resetFields();
            }
        }
    }, [visible]);
    useImperativeHandle(cRef, () => ({
        handleOk,
        setVisible,
        form: form,
        init: ({title} = {}) => {
            setTitle(title);
        }
    }));
    const defaultModalProps = {
        closable: false,
        title: title,
        width: width || 520,
        maskClosable: false,
        forceRender: true,
        wrapClassName: styles.formModal,
        onOk: handleOk,
        onCancel: () => {
            setVisible(false);
            onCancel && onCancel();
        }
    };
    const defaultFormProps = {
        form: form,
        colon: false,
        labelCol: {span: labelCol || 4},
        wrapperCol: {span: wrapperCol || 20},
        labelAlign: 'left',
        onFinish: onFinish
    };
    const FormContent = formList.map(item => renderForm(item));
    return (
        <Modal {...defaultModalProps} visible={visible}>
            <Form {...defaultFormProps}>
                {FormContent}
                {children}
            </Form>
        </Modal>
    );
};
export default FormModal;
// renderForm

/*
   格式说明
   label,
   name,
   require:Boolean,
   formType, form表单元素小写
   props:{} 表单的属性
 */
import {Input, Radio, Form, Select} from 'antd';
import InputPassword from '@/components/InputPassword';

const formItemObj = {
    'input': Input,
    'radio': Radio.Group,
    'select': Select,
    'password': InputPassword,
    'textarea': Input.TextArea
};
// const formChildItemObj = {
//     'select': Select.Option
// };
const msgItem = {
    'input': '请输入',
    'radio': '请选择',
    'select': '请选择',
    'password': '请输入',
    'textarea': '请输入'
};
const renderForm = obj => {
    const {
        formType, label, name, require = false, list = [], rules = [], props = {}, ..._formProps
    }
        = obj;
    const {placeholder, message, extendElement, ..._props} = props;
    const Tag = formItemObj[formType || 'input'];
    const msg = placeholder || msgItem[formType || 'input'] + label;
    const defaultProps = {
        placeholder: msg,
        ..._props
    };
    // 使用options渲染
    // const tagChild = () => {
    //     if (props.options) {
    //         return;
    //     }
    //     if (formChildItemObj[formType] && list) {
    //         const TagChild = formChildItemObj[formType];
    //         return list.map(item => (<TagChild key={item.value} value={item.value}>{item.label}</TagChild>));
    //     }
    // };
    return (<Form.Item
        key={name}
        label={label}
        name={name}
        rules={[
            {
                required: require,
                message: message || msg
            },
            ...rules
        ]}
        {..._formProps}
    >
        <Tag {...defaultProps} />
    </Form.Item>);
};
export default renderForm;

antd modal 统一定制化弹窗 (可以只有一个确定按钮)

import React, {useState, useEffect, useImperativeHandle} from 'react';
import {Modal, Button} from 'antd';
import styles from './index.less';

const SystemModal = ({cRef, title: _title, content: _content, width, className}) => {
    const [visible, setVisible] = useState(false);
    const [onlyOk, setOnlyOk] = useState(false);
    const [title, setTitle] = useState(_title || '系统通知');
    const [content, setContent] = useState(_content || '第三方服务删除后不可恢复,请确认是否删除?');
    const [okText, setOkText] = useState('确认');
    const [okButtonProps, setOkButtonProps] = useState({});
    const [handleOkCallback, setHandleOkCallback] = useState(undefined);
    const handleOk = () => {
        if (typeof handleOkCallback === 'function') {
            handleOkCallback();
        }
        else {
            setVisible(false);
        }

    };
    useEffect(() => {
        if (!visible) {
            setOnlyOk(false);
            setContent(undefined);
            setTitle('系统通知');
            setHandleOkCallback(undefined);
        }
    }, [visible]);
    useImperativeHandle(cRef, () => ({
        handleOk,
        setVisible,
        setOkButtonProps,
        init: ({title, content, onlyOk = false, handleOkCallback, okButtonProps, callback, okText} = {}) => {
            setTitle(title);
            setContent(content);
            setOnlyOk(onlyOk);
            setHandleOkCallback(() => handleOkCallback);
            okText && setOkText(okText);
            okButtonProps && setOkButtonProps(okButtonProps);
            callback && callback();
        }
    }));
    const ButtonOk = <Button type='primary' key='ok' onClick={handleOk}>{okText}</Button>;
    const defaultModalProps = {
        closable: false,
        title: title,
        zIndex: 9999,
        width: width || 520,
        destroyOnClose: true,
        maskClosable: false,
        wrapClassName: `${styles.systemModal} ${className ? className : ''}`,
        footer: onlyOk ? [ButtonOk] : undefined,
        okText: okText,
        onOk: handleOk,
        okButtonProps,
        cancelText: '取消',
        onCancel: () => {
            setVisible(false);
        }
    };
    return (<Modal {...defaultModalProps} visible={visible}>{content}</Modal>);
};
export default SystemModal;
// index.less

.system-modal {
  :global {
    .ant-modal-header {
      background: #3A7EF9;
      padding: 10px 24px;
      text-align: center;

      .ant-modal-title {
        color: white;
      }
    }

    .ant-modal-body {
      padding: 25px 35px 10px;
      text-align: center;
      font-weight: 500;
    }

    .ant-modal-footer {
      text-align: center;
      margin: 0 35px;
      padding: 20px 0;
      border: 0;

      .ant-btn {
        padding: 0 29px;

        &:first-child {
          margin-right: 33px;
        }

        &:last-child {
          margin-right: 0 !important;
        }
      }
    }

  }
}
// use

this.systemModalRef.current.setVisible(true); // 关闭

this.systemModalRef.current.init({
  title: '系统通知',
  content: action === 'add' ? '新增成功!' : '修改成功!',
  onlyOk: true, // 只有确定按钮
  callback: () => {
      this.formModalRef.current.setVisible(false);
      this.getList();
  }
});

// 不传 onlyOk 确认取消按钮都有

this.systemModalRef.current.init({
    title: '系统通知',
    content: '删除后将导致.......,请谨慎操作!是否确认删除?',
    handleOkCallback: this.deleteServer
});

为什么不用json.parse 、stringify 深拷贝

1. 对象中的时间类型会被拷贝成字符串。

2. 当对象中的属性值为undefined或function时,拷贝会直接丢失。

3. 当对象中有 NaNInfinity-Infinity时,拷贝的值会变成null4. 当对象循环引用的时候会报错。

new操作符做了哪些事情

1. 创建一个新对象。

2. 将构造函数的作用域赋予这个新对象 (因此this也指向了这个新对象)3. 执行构造函数中的代码(为新对象添加属性)4. 返回这个新对象。

5. 将构造函数的prototype属性与实例的__proto__属性关联。

重绘与回流

重绘一般指节点的样式方式改变而不会影响布局的。

如: outline, visibility, color、background-color

回流一般指页面的布局发生改变。

获取位置的方法会触发回流与重绘,我们应该避免频繁使用这些属性。

offsetTop、offsetWidth、offsetheight、scrollTop、clientTop、width、heigth,
  
getComputedStyle()

getBoundingClientRect()

rebase 和merge 的区别

场景: 开发中一班都是先在自己的分支开发,然后将自己的分支合并到主分支。

  一般有两种操作 (merge 和 rebase), 那他们有什么区别呢。
  
  merge 会让两个分支的 commit 提交按时间排序,并且会把两个最新的commit合并成一个新的commit,

  最后分支树呈现非线形结构。

  rebase 之后 master 不会在多出一个commit, 并且整个master分支的commit记录呈线性结构。

  rebase 后 dev 分支 的那几次提交记录的哈希值都发生了变化,

  但提交的内容全部都被保留了。
  
  参考资料: https://juejin.cn/post/7123826435357147166#comment

二分法

① 二分查找的数组是有序的

② 二分查找是从数组中间开始查找,如果找到,则结束查找并返回其在数组中的下标。

否则将数组中间值(midValue)和要找的值(findValue)进行比较,

如果 findValue > midValue , 则将数组中 midValue 右侧的所有值再次一分为二,

从中间开始查找,以此递归。

反之如果 findValue < midValue ,

则将midValue左侧的所有值一分为二进行递归查找。若递归完成后未找到,则返回-1。
let arr = [1, 2, 3, 4, 5 ,6 ,7];

// type one 逐次递归

const twoDiviSionOne = (arr, target) => {
    return search(arr, target, 0 , arr.length - 1);
    function search(arr, target, from, to) {
        if(from > to ) {
            return - 1;
        }

        const mid = Math.floor((from + to) / 2);
        if(arr[mid] > target) {// 中间值大于目标值, 从中间值左半边再开始找
            return search(arr, target, from , mid - 1);
        }
        else if(arr[mid] < target) {// 中间值小于目标值, 从中间值右半边再开始找
            return search(arr, target, mid + 1, to);
        }
        else {
            return mid;
        }
    }
}

console.warn('twoDiviSionOne(arr, 4);',twoDiviSionOne(arr, 7));

// 非递归 

// 使用了循环,其实两种方式相差不大

function BinarySearch2 (arr, target) {
    let from = 0;
    let to = arr.length - 1;
    let mid = Math.floor((from + to) / 2);
    while (from <= to) {
        mid = Math.floor((from + to) / 2);
        if (arr[mid] > target) {
            to = mid - 1;
        } else if (arr[mid] < target) {
            from = mid + 1;
        } else {
            return mid;
        }
    }

    return - 1;
}

// 数组中最大元素的二分法

let a = [4, 6, 7, 8];

function solution(a,l,r){
   let mid = Math.floor((l+r)/2),max1,max2
   if (l < r){
       max1 = solution(a,l,mid)
       max2 = solution(a,mid+1,r)
       return (max1 > max2) ? max1 : max2;
   }
  else return a[l];
}

console.log(solution(a, 0, 3));

// 参考资料

https://juejin.cn/post/6964566234864025613#comment

https://juejin.cn/post/6844904121380667399

https://blog.csdn.net/qq_45859670/article/details/122219423

对象方法

var o = {
    val: '小姐姐',
    set a(value) {
        this.val = `${value}` + this.val;
    },
    get a() {
        return this.val;
    }
};

o.a  // '小姐姐'; // 触发get

o.a = '我爱'; // 触发set

o.a   // '我爱小姐姐'  // 触发get

cra 使用 scss

// 安装 sass

cnpm i sass --save

// 定义index.module.scss

// 组件引入

import style from './index.module.scss';

// 再看 view 发现 scss 定义的样式生效了

<div className={style.wrap}></div>

amis使用心得

如果想法起接口

在对应的api写好请求地址

data定义后端需要数据的字段

接口调用成功即可!

requestAdaptor 可以重写请求配置。

至于组件状态,

amis会帮你维护,

因此,

接口就只关心地址和data的参数,

json配置最后的fetch

则可以看做是类似axios的东西。

如果说想在一个操作中,

显示隐藏另一个amis组件,

请查看amis联动。

关闭、取消 和主题色在fetch后面配置即可!

文章作者: KarlFranz
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 reprint policy. If reproduced, please indicate source KarlFranz !
评论
  目录