前端反卷计划-组件库-05-Menu组件开发

简介: 前端反卷计划-组件库-05-Menu组件开发

Hi, 大家好!我是程序员库里。

今天开始分享如何从0搭建UI组件库。这也是前端反卷计划中的一项。

在接下来的日子,我会持续分享前端反卷计划中的每个知识点。

以下是前端反卷计划的内容:

image.png

image.png

目前这些内容持续更新到了我的 学习文档 中。感兴趣的欢迎一起学习!

Menu

5.1 需求分析

  1. 水平菜单

image.png

  1. 垂直菜单

image.png

5.2 Demo

<Menu defaultIndex='0' onSelect={(index) => {action(`clicked ${index} item`)}} >
    <MenuItem>
      cool link
    </MenuItem>
    <MenuItem disabled>
      disabled
    </MenuItem> 
    <MenuItem>
      cool link 2
    </MenuItem> 
  </Menu>
  
  
  <Menu defaultIndex='0' defaultOpenSubMenus={["2"]} onSelect={e => alert(e)}>
      <MenuItem>aaa</MenuItem>
      <MenuItem>bbb</MenuItem>
      <SubMenu title='aaa'>
        <MenuItem>ccc</MenuItem>
        <MenuItem>ddd</MenuItem>
      </SubMenu>
</Menu>

5.3 API

  1. Menu
参数 说明 类型 默认值
defaultIndex 第几项处于选中状态 number 0,第一个
mode 水平还是垂直 'horizontal' 'vertical' horizontal
onSelect 选中事件 (selectedIndex: number) => void; -
defaultOpenSubMenus 设置子菜单的默认打开 只在纵向模式下生效
  1. MenuItem
参数 说明 类型 默认值
index 索引 number
disabled 是否是disabled状态 boolean FALSE
  1. SubMenu
参数 说明 类型 默认值
title 名称 string

5.4 开发

5.4.1 定义Menu Props

type MenuMode = 'horizontal' | 'vertical'
export interface MenuProps {
  /**默认 active 的菜单项的索引值 */
  defaultIndex?: string;
  className?: string;
  /**菜单类型 横向或者纵向 */
  mode?: MenuMode;
  style?: CSSProperties;
  /**点击菜单项触发的回掉函数 */
  onSelect?: (selectedIndex: string) => void;
  /**设置子菜单的默认打开 只在纵向模式下生效 */
  defaultOpenSubMenus?: string[];
  children?: React.ReactNode;
}

5.4.2 自定义style和水平、垂直菜单

const classes = classNames('curry-menu', className, {
    'menu-vertical': mode === 'vertical',
    'menu-horizontal': mode !== 'vertical',
  })
  
 return (
        <ul className={classes} style={style}>
            {children}
        </ul>
    )

5.4.3 定义MenuItem Props

export interface MenuItemProps {
  index?: string;
  disabled?: boolean;
  className?: string;
  style?: React.CSSProperties;
  children: React.ReactNode
}

const { index, disabled, className, style, children } = props
const classes = classNames('menu-item', className, {
    'is-disabled': disabled,  // 是否可点击
    'is-active': context.index === index // 是否选中
  })
  
return (
    <li className={classes} style={style} onClick={handleClick}>
      {children}
    </li>
  )

5.4.4 定义context

因为Menu组件的一些属性,需要在MenuItem组件中使用,所以这里使用context来传递props
interface IMenuContext {
  index: string;
  onSelect?: (selectedIndex: string) => void;
  mode?: MenuMode;
  defaultOpenSubMenus?: string[];
}

export const MenuContext = createContext<IMenuContext>({ index: '0' })

5.4.5 高亮逻辑

点击哪个item,哪个就高亮
// menu.tsx
export const MenuContext = createContext<IMenuContext>({ index: '0' })

const [currentActive, setActive] = useState(defaultIndex)


// 当点击某一项的时候,将当前的index和点击事件传到MenuItem中,这里同样使用context
const handleClick = (index: string) => {
    setActive(index)
    if (onSelect) {
      onSelect(index)
    }
  }
  // 传递给 menu item
  const passedContext: IMenuContext = {
    index: currentActive ? currentActive : '0',
    onSelect: handleClick,
    mode,
    defaultOpenSubMenus,
  }
  
  return (
    <ul className={classes} style={style} data-testid="test-menu">
      <MenuContext.Provider value={passedContext}>
        {chilren}
      </MenuContext.Provider>
    </ul>
  )
MenuItem
import React, { useContext } from "react";


import { MenuContext } from './menu'
const { index, disabled, className, style, children } = props
const context = useContext(MenuContext)

const classes = classnames(className, 'menu-item', {
        'is-disabled': disabled,
        'is-active': context.index === index // 根据index判断哪个高亮
})

// item 点击事件
const handleClick = () => {
    if (context.onSelect && !disabled && (typeof index === 'string')) {
      context.onSelect(index)
    }
  }
  return (
    <li className={classes} style={style} onClick={handleClick}>
      {children}
    </li>
  )

经过上面代码,我们可以使用这样来编写。这里需要给menu item添加index

  <Menu defaultIndex={0}>
        <MenuItem index={0}>aaa</MenuItem>
        <MenuItem index={1}>bbb</MenuItem>
      </Menu>

5.5.5 添加样式

  1. 在src/styles/_variables.scss添加样式变量
// menu
$menu-border-width:            $border-width !default;
$menu-border-color:            $border-color !default;
$menu-box-shadow:              inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default;
$menu-transition:              color .15s ease-in-out, border-color .15s ease-in-out !default;

// menu-item
$menu-item-padding-y:          .5rem !default;
$menu-item-padding-x:          1rem !default;
$menu-item-active-color:       $primary !default;
$menu-item-active-border-width: 2px !default;
$menu-item-disabled-color:     $gray-600 !default;
  1. 导入到样式入口文件
// menu
@import "../components/Menu/style";
  1. 编写menu、menu item样式
.curry-menu {
  display: flex;
  flex-wrap: wrap;
  padding-left: 0;
  margin-bottom: 30px;
  list-style: none;
  border-bottom: $menu-border-width solid $menu-border-color;
  box-shadow: $menu-box-shadow;
  >.menu-item {
    padding: $menu-item-padding-y $menu-item-padding-x;
    cursor: pointer;
    transition: $menu-transition;
    &:hover, &:focus {
      text-decoration: none;
    }
    &.is-disabled {
      color: $menu-item-disabled-color;
      pointer-events: none;
      cursor: default;
    }
    &.is-active, &:hover {
      color: $menu-item-active-color;
      border-bottom: $menu-item-active-border-width solid $menu-item-active-color;
    }
  }
  .submenu-item {
    position: relative;
    .submenu-title {
      display: flex;
      align-items: center;
    }
    .arrow-icon {
      transition: transform .25s ease-in-out;
      margin-left: 3px;
    }
    &:hover {
      .arrow-icon {
        transform: rotate(180deg);
      }
    }
  }
  .is-vertical {
    .arrow-icon {
      transform: rotate(0deg) !important;
    }
  }
  .is-vertical.is-opened {
    .arrow-icon {
      transform: rotate(180deg) !important;
    }
  }
  .curry-submenu {
    //display: none;
    list-style:none;
    padding-left: 0;
    white-space: nowrap;
    //transition: $menu-transition;
    .menu-item {
      padding: $menu-item-padding-y $menu-item-padding-x;
      cursor: pointer;
      transition: $menu-transition;
      color: $body-color;
      &.is-active, &:hover {
        color: $menu-item-active-color !important;
      }
    }
  }
  .curry-submenu.menu-opened {
    //display: block;
  }
}
.menu-horizontal {
  >.menu-item {
    border-bottom: $menu-item-active-border-width solid transparent;
  }
  .curry-submenu {
    position: absolute;
    background: $white;
    z-index: 100;
    top: calc(100% + 8px);
    left: 0;
    border: $menu-border-width solid $menu-border-color;
    box-shadow: $submenu-box-shadow;
  }
}
.menu-vertical {
  flex-direction: column;
  border-bottom: 0px;
  margin: 10px 20px;
  border-right: $menu-border-width solid $menu-border-color;
  >.menu-item {
    border-left: $menu-item-active-border-width solid transparent;
    &.is-active, &:hover {
      border-bottom: 0px;
      border-left: $menu-item-active-border-width solid $menu-item-active-color;
    }
  }
}

效果如下:

image.png

5.5.6 改造children

children目前只能是MenuItem,如果是其他的,就报错
  1. 在MenuItem上加上displayName
MenuItem.displayName = 'MenuItem'
  1. 写一个renderChildren方法,使用React.Children来遍历传进来的children,根据displayName是否是 MenuItem来判断,如果是则渲染children,否则报错
import { MenuItemProps } from './menuItem'

const renderChildren = () => {
        return React.Children.map(children, (child, index) => {
            const childElement = child as React.FunctionComponentElement<MenuItemProps>;
            const { displayName } = childElement.type;
            if (displayName === 'MenuItem') {
                return child;
            } else {
                console.error('Warning: Menu has a child which is not a MenuItem component')
            }
        })
    }

 return (
        <ul className={classes} style={style} data-testid='test-menu'>
            <MenuContext.Provider value={passedContext}>
                {renderChildren()}
            </MenuContext.Provider>
        </ul>
    )

5.5.7 改造index

上面需要给每个menu item传入index,这里改成不需要传index

在渲染childrend的时候,使用React.cloneElement将index克隆到child上

const renderChildren = () => {
        return React.Children.map(children, (child, index) => {
            const childElement = child as React.FunctionComponentElement<MenuItemProps>;
            const { displayName } = childElement.type;
            if (displayName === 'MenuItem') {
               // 这里使用React.cloneElement
                return React.cloneElement(childElement, {
                    index
                })
            } else {
                console.error('Warning: Menu has a child which is not a MenuItem component')
            }
        })
    }

这样就不用在menu item上传index了

<Menu>
  <MenuItem>1</MenuItem>
  <MenuItem>12</MenuItem>
</Menu>

5.5.8 SubMenu基础开发

原理和MenuItem一样,不再赘述
import React, { useContext, FunctionComponentElement } from "react";
import classnames from 'classnames';
import { MenuItemProps } from './menuItem'
import { MenuContext } from './menu'

export interface SubMenuProps {
    index?: number;
    title: string;
    className?: string;
    children: React.ReactNode;
    style?: React.CSSProperties;
}

const SubMenu: React.FC<SubMenuProps> = ({ index, style, title, className, children }) => {
    const context = useContext(MenuContext);

    const classes = classnames('menu-item submenu-item', className, {
        'is-active': context.index === index
    })

    const renderChildren = () => {
        const childrenComponent = React.Children.map(children, (child, index) => {
            const childElement = child as FunctionComponentElement<MenuItemProps>
            if (childElement.type.displayName === 'MenuItem') {
                return childElement
            } else {
                console.error('Warning: SubMenu has a child which is not a MenuItem component')
            }
        })
        return (
            <ul className="curry-submenu">
                {childrenComponent}
            </ul>
        )
    }
    return (
        <li key={index} className={classes} style={style}>
            <div className="submenu-title">
                {title}
            </div>
            {renderChildren()}
        </li>
    )
}

SubMenu.displayName = 'SubMenu'

export default SubMenu;

5.5.9 添加SubMenu样式

.curry-menu {
  display: flex;
  flex-wrap: wrap;
  padding-left: 0;
  margin-bottom: 30px;
  list-style: none;
  border-bottom: $menu-border-width solid $menu-border-color;
  box-shadow: $menu-box-shadow;
  >.menu-item {
    padding: $menu-item-padding-y $menu-item-padding-x;
    cursor: pointer;
    transition: $menu-transition;
    &:hover, &:focus {
      text-decoration: none;
    }
    &.is-disabled {
      color: $menu-item-disabled-color;
      pointer-events: none;
      cursor: default;
    }
    &.is-active, &:hover {
      color: $menu-item-active-color;
      border-bottom: $menu-item-active-border-width solid $menu-item-active-color;
    }
  }
  .submenu-item {
    position: relative;
    .submenu-title {
      display: flex;
      align-items: center;
    }
    .arrow-icon {
      transition: transform .25s ease-in-out;
      margin-left: 3px;
    }
    &:hover {
      .arrow-icon {
        transform: rotate(180deg);
      }
    }
  }
  .is-vertical {
    .arrow-icon {
      transform: rotate(0deg) !important;
    }
  }
  .is-vertical.is-opened {
    .arrow-icon {
      transform: rotate(180deg) !important;
    }
  }
  .curry-submenu {
    //display: none;
    list-style:none;
    padding-left: 0;
    white-space: nowrap;
    //transition: $menu-transition;
    .menu-item {
      padding: $menu-item-padding-y $menu-item-padding-x;
      cursor: pointer;
      transition: $menu-transition;
      color: $body-color;
      &.is-active, &:hover {
        color: $menu-item-active-color !important;
      }
    }
  }
  .curry-submenu.menu-opened {
    //display: block;
  }
}
.menu-horizontal {
  >.menu-item {
    border-bottom: $menu-item-active-border-width solid transparent;
  }
  .curry-submenu {
    position: absolute;
    background: $white;
    z-index: 100;
    top: calc(100% + 8px);
    left: 0;
    border: $menu-border-width solid $menu-border-color;
    box-shadow: $submenu-box-shadow;
  }
}
.menu-vertical {
  flex-direction: column;
  border-bottom: 0px;
  margin: 10px 20px;
  border-right: $menu-border-width solid $menu-border-color;
  >.menu-item {
    border-left: $menu-item-active-border-width solid transparent;
    &.is-active, &:hover {
      border-bottom: 0px;
      border-left: $menu-item-active-border-width solid $menu-item-active-color;
    }
  }
}

5.5.10 在Menu里添加SubMenu

const renderChildren = () => {
        return React.Children.map(children, (child, index) => {
            const childElement = child as React.FunctionComponentElement<MenuItemProps>;
            const { displayName } = childElement.type;
            // 增加这个逻辑:displayName === 'SubMenu' 
            if (displayName === 'MenuItem' || displayName === 'SubMenu') {
                return React.cloneElement(childElement, {
                    index
                })
            } else {
                console.error('Warning: Menu has a child which is not a MenuItem component')
            }
        })
    }

目前效果如下:

水平方向:

image.png

垂直方向:

image.png

5.5.11 SubMenu显示隐藏

  1. css方面通过display控制显示隐藏
// 通过display控制显示隐藏
  .curry-submenu {
    display: none; // 开始隐藏
  }
 .curry-submenu.menu-opened {
    display: block; // 显示
  }
  1. 逻辑方面,通过state控制
const context = useContext(MenuContext);
const openedSubMenus = context.defaultOpenSubMenus as Array<string>;
const isOpend = index && context.mode === "vertical" ? openedSubMenus.includes(index) : false;
const [menuOpen, setOpen] = useState(isOpend);

const classes = classNames("menu-item submenu-item", className, {
    "is-active": context.index === index,
    "is-opened": menuOpen,  // 
    "is-vertical": context.mode === "vertical",
});
  1. 判断mode的值,当是水平菜单的时候,改成当鼠标移入移出来控制显示隐藏。当是垂直菜单的时候,通过点击来控制
const handleClick = (e: React.MouseEvent) => {
    e.preventDefault();
    setOpen(!menuOpen);
  };
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  let timer: any;
  const handleMouse = (e: React.MouseEvent, toggle: boolean) => {
    clearTimeout(timer);
    e.preventDefault();
    timer = setTimeout(() => {
      setOpen(toggle);
    }, 300);
  };
  const clickEvents =
    context.mode === "vertical"
      ? {
          onClick: handleClick,
        }
      : {};
  const hoverEvents =
    context.mode !== "vertical"
      ? {
          onMouseEnter: (e: React.MouseEvent) => {
            handleMouse(e, true);
          },
          onMouseLeave: (e: React.MouseEvent) => {
            handleMouse(e, false);
          },
        }
      : {};

目前效果:

水平菜单:

1.默认是隐藏的

image.png

2.当鼠标移动上去后,显示菜单

image.png

3.当鼠标移出后,隐藏菜单

image.png

垂直菜单:

1.默认菜单是隐藏的

image.png

2.当点击的时候,显示出来

image.png

3.当再次点击的时候,隐藏菜单

image.png

5.5.12 将index改造成树形结构

submenu和menuitem目前都是通过index来索引的,所以submenu的点击没有效果。解决方案是:去掉index,改成类似:1-1,1-2这种方案。
  1. 修改menu组件的index的类型
// 首先修改menu组件的defaultIndex的类型,由数字改成字符串
export interface MenuProps {
    defaultIndex?: string; // 由number 改成string
    className?: string;
    mode?: MenuMode;
    style?: React.CSSProperties;
    onSelect?: SelectCallback;
    children?: React.ReactNode
}

// 修改IMenuContext下的index类型
interface IMenuContext {
    index: string; // number 改成string
    onSelect?: SelectCallback;
    mode?: MenuMode;
}

// 修改默认值,
export const MenuContext = createContext<IMenuContext>({
    index: '0', // 0 改成 '0'
})

// index从number改成string
const handleClick = (index: string) => {
        setActive(index)
        onSelect && onSelect(index)
    }
// selectedIndex从number改成string
type SelectCallback = (selectedIndex: string) => void;

// 0 改成 '0'
   const passedContext: IMenuContext = {
        index: currentActive || '0',
        onSelect: handleClick,
        mode
    }
// 0 改成 '0'
Menu.defaultProps = {
    defaultIndex: '0',
    mode: 'horizontal'
}


//   index: index.toString()
    const renderChildren = () => {
        return React.Children.map(children, (child, index) => {
            const childElement = child as React.FunctionComponentElement<MenuItemProps>;
            const { displayName } = childElement.type;
            if (displayName === 'MenuItem' || displayName === 'SubMenu') {
                return React.cloneElement(childElement, {
                    index: index.toString() // 修改
                })
            } else {
                console.error('Warning: Menu has a child which is not a MenuItem component')
            }
        })
    }
  1. 修改MenuItem的index类型

export interface MenuItemProps {
    index?: string; // number 改成  string
    disabled?: boolean;
    className?: string;
    style?: React.CSSProperties;
    children?: React.ReactNode;
}

// number 改成 string
 const handleClick = () => {
        if (context.onSelect && !disabled && (typeof index === 'string')) {
            context.onSelect(index)
        }
    }
  1. 修改submenu的index类型
export interface SubMenuProps {
    index?: string; // number to  string
    title: string;
    className?: string;
    children: React.ReactNode;
    style?: React.CSSProperties;
}
  1. 以上类型就修完了。然后根据上面的改成1-1这种形式
  const renderChildren = () => {
        const subMenuClasses = classnames('curry-submenu', {
            'menu-opened': menuOpen
        })
        const childrenComponent = React.Children.map(children, (child, i) => {
            const childElement = child as FunctionComponentElement<MenuItemProps>
            if (childElement.type.displayName === 'MenuItem') {
              // 改成如下代码
                return React.cloneElement(childElement, {
                    index: `${index}-${i}`
                })
            } else {
                console.error('Warning: SubMenu has a child which is not a MenuItem component')
            }
        })
        return (
            <ul className={subMenuClasses}>
                {childrenComponent}
            </ul>
        )
    }

效果如下:

image.png

image.png

5.5.13 垂直菜单默认展开

  1. 增加defaultOpenSubMenus属性表示哪些是默认展开
export interface MenuProps {
    defaultIndex?: string;
    className?: string;
    mode?: MenuMode;
    style?: React.CSSProperties;
    onSelect?: SelectCallback;
    children?: React.ReactNode;
    defaultOpenSubMenus?: string[]; // 新增,控制菜单默认展开
}

Menu.defaultProps = {
    defaultIndex: '0',
    mode: 'horizontal',
    defaultOpenSubMenus: [] // 默认值
}
  1. 通过context来将defaultOpenSubMenus传到submenu组件
interface IMenuContext {
    index: string;
    onSelect?: SelectCallback;
    mode?: MenuMode;
    defaultOpenSubMenus?: string[]; // 新增
}

 const passedContext: IMenuContext = {
        index: currentActive || '0',
        onSelect: handleClick,
        mode,
        defaultOpenSubMenus // 新增
    }
  1. 在submenu组件中通过context获取defaultOpenSubMenus。定义一个isOpened变量,来控制是否默认展开,这个逻辑是:当index存在并且是垂直菜单的时候,看defaultOpenSubMenus是否包含index,是的话返回true,否则false。然后将isOpened当成默认值传给menuOpen的初始值。
 const openedSubMenus = context.defaultOpenSubMenus as Array<string>;
 const isOpened = (index && context.mode === 'vertical') ? openedSubMenus.includes(index) : false;

 const [menuOpen, setOpen] = useState(isOpened)
  1. 看下效果
  <Menu defaultIndex='0' mode="vertical" defaultOpenSubMenus={["2"]} onSelect={e => alert(e)}>
        <MenuItem>aaa</MenuItem>
        <MenuItem>bbb</MenuItem>
        <SubMenu title='aaa'>
          <MenuItem>ccc</MenuItem>
          <MenuItem>ddd</MenuItem>
        </SubMenu>
      </Menu>

image.png

系列篇

前端反卷计划-组件库-01-环境搭建

前端反卷计划-组件库-02-storybook

前端反卷计划-组件库-03-组件样式

[前端反卷计划-组件库-04-Button组件开发]

持续更新

目前这些内容持续更新到了我的 学习文档 中。感兴趣的欢迎一起学习!

相关文章
|
11天前
|
前端开发 JavaScript
除了 jsPDF,还有哪些前端库可以用于生成 PDF?
【10月更文挑战第21天】这些前端库都有各自的特点和优势,你可以根据具体的项目需求、技术栈以及对功能的要求来选择合适的库。不同的库在使用方法、性能表现以及功能支持上可能会有所差异,需要根据实际情况进行评估和选择。
|
10天前
|
前端开发 JavaScript 开发者
揭秘前端高手的秘密武器:深度解析递归组件与动态组件的奥妙,让你代码效率翻倍!
【10月更文挑战第23天】在Web开发中,组件化已成为主流。本文深入探讨了递归组件与动态组件的概念、应用及实现方式。递归组件通过在组件内部调用自身,适用于处理层级结构数据,如菜单和树形控件。动态组件则根据数据变化动态切换组件显示,适用于不同业务逻辑下的组件展示。通过示例,展示了这两种组件的实现方法及其在实际开发中的应用价值。
19 1
|
14天前
|
缓存 前端开发 JavaScript
前端serverless探索之组件单独部署时,利用rxjs实现业务状态与vue-react-angular等框架的响应式状态映射
本文深入探讨了如何将RxJS与Vue、React、Angular三大前端框架进行集成,通过抽象出辅助方法`useRx`和`pushPipe`,实现跨框架的状态管理。具体介绍了各框架的响应式机制,展示了如何将RxJS的Observable对象转化为框架的响应式数据,并通过示例代码演示了使用方法。此外,还讨论了全局状态源与WebComponent的部署优化,以及一些实践中的改进点。这些方法不仅简化了异步编程,还提升了代码的可读性和可维护性。
|
21天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
21天前
|
前端开发 JavaScript
CSS样式穿透技巧:利用scoped与deep实现前端组件样式隔离与穿透
CSS样式穿透技巧:利用scoped与deep实现前端组件样式隔离与穿透
110 1
|
22天前
|
存储 前端开发 JavaScript
🚀 10 个 GitHub 存储库,助你成为前端巨匠✨
本文介绍了10个极具价值的GitHub存储库,旨在帮助各级JavaScript开发人员提升技能。这些资源涵盖了从基本概念到高级算法、编码风格指南、面试准备等各个方面,包括经典书籍、实用工具和面试手册。无论您是刚入门的新手还是有经验的开发者,这些存储库都能为您提供丰富的学习资源,助您在JavaScript领域更进一步。探索这些资源,开启您的学习之旅吧!
47 0
🚀 10 个 GitHub 存储库,助你成为前端巨匠✨
|
24天前
|
前端开发 JavaScript 开发者
Web组件:一种新的前端开发范式
【10月更文挑战第9天】Web组件:一种新的前端开发范式
30 2
|
1月前
|
前端开发 JavaScript Go
前端开发趋势:从响应式设计到Web组件的探索
【10月更文挑战第1天】前端开发趋势:从响应式设计到Web组件的探索
35 3
|
21天前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
|
21天前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
34 0