vue-element-plus-admin 第7期|主题实战:主题定制与国际化

1. 主题系统设计与动态切换

在现代前端应用中,主题定制功能已成为提升用户体验的重要部分。vue-element-plus-admin 项目实现了一套完善的主题系统,支持系统主题、菜单主题和头部主题的独立配置,以及暗黑模式的切换。

1.1 主题变量设计

项目采用 CSS 变量实现主题定制,核心样式变量定义在 src/styles/var.css 中:

css 复制代码
:root {
  --login-bg-color: #293146;

  /* left menu start */
  --left-menu-max-width: 200px;
  --left-menu-min-width: 64px;
  --left-menu-bg-color: #001529;
  --left-menu-bg-light-color: #0f2438;
  --left-menu-bg-active-color: var(--el-color-primary);
  --left-menu-text-color: #bfcbd9;
  --left-menu-text-active-color: #fff;
  --left-menu-collapse-bg-active-color: var(--el-color-primary);
  /* left menu end */

  /* logo start */
  --logo-height: 50px;
  --logo-title-text-color: #fff;
  /* logo end */

  /* header start */
  --top-header-bg-color: '#fff';
  --top-header-text-color: 'inherit';
  --top-header-hover-color: #f6f6f6;
  --top-tool-height: var(--logo-height);
  --top-tool-p-x: 0;
  --tags-view-height: 35px;
  /* header start */

  /* tab menu start */
  --tab-menu-max-width: 80px;
  --tab-menu-min-width: 30px;
  --tab-menu-collapse-height: 36px;
  /* tab menu end */

  --app-content-padding: 20px;
  --app-content-bg-color: #f5f7f9;
  --app-footer-height: 50px;
  --transition-time-02: 0.2s;
}

.dark {
  --app-content-bg-color: var(--el-bg-color);
}

这些 CSS 变量形成了一套完整的主题体系,通过修改这些变量可以轻松实现主题的切换。

1.2 主题状态管理

项目使用 Pinia 进行主题状态的集中管理,主题相关的状态定义在 src/store/modules/app.ts 中:

typescript 复制代码
// 主题相关的状态
theme: {
  // 主题色
  elColorPrimary: '#409eff',
  // 左侧菜单边框颜色
  leftMenuBorderColor: 'inherit',
  // 左侧菜单背景颜色
  leftMenuBgColor: '#001529',
  // 左侧菜单浅色背景颜色
  leftMenuBgLightColor: '#0f2438',
  // 左侧菜单选中背景颜色
  leftMenuBgActiveColor: 'var(--el-color-primary)',
  // 左侧菜单收起选中背景颜色
  leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',
  // 左侧菜单字体颜色
  leftMenuTextColor: '#bfcbd9',
  // 左侧菜单选中字体颜色
  leftMenuTextActiveColor: '#fff',
  // logo字体颜色
  logoTitleTextColor: '#fff',
  // logo边框颜色
  logoBorderColor: 'inherit',
  // 头部背景颜色
  topHeaderBgColor: '#fff',
  // 头部字体颜色
  topHeaderTextColor: 'inherit',
  // 头部悬停颜色
  topHeaderHoverColor: '#f6f6f6',
  // 头部边框颜色
  topToolBorderColor: '#eee'
}

这些状态可以通过 Pinia store 的 actions 进行修改,实现主题的动态切换:

typescript 复制代码
// 设置系统主题
setTheme(theme: ThemeTypes) {
  this.theme = Object.assign(this.theme, theme)
},

// 设置 CSS 变量
setCssVarTheme() {
  for (const key in this.theme) {
    setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
  }
  this.setPrimaryLight()
},

// 设置菜单主题
setMenuTheme(color: string) {
  const primaryColor = useCssVar('--el-color-primary', document.documentElement)
  const isDarkColor = colorIsDark(color)
  const theme: Recordable = {
    // 左侧菜单边框颜色
    leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
    // 左侧菜单背景颜色
    leftMenuBgColor: color,
    // ... 其他菜单相关样式
  }
  this.setTheme(theme)
  this.setCssVarTheme()
},

// 设置头部主题
setHeaderTheme(color: string) {
  const isDarkColor = colorIsDark(color)
  const textColor = isDarkColor ? '#fff' : 'inherit'
  const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
  const topToolBorderColor = isDarkColor ? color : '#eee'
  setCssVar('--top-header-bg-color', color)
  setCssVar('--top-header-text-color', textColor)
  setCssVar('--top-header-hover-color', textHoverColor)
  // ... 设置其他头部相关样式
}

1.3 主题色处理工具

项目中实现了一系列颜色处理工具函数,位于 src/utils/color.ts,用于辅助主题色的计算和转换:

typescript 复制代码
// 判断颜色是否是深色
export const colorIsDark = (color: string) => {
  if (!isHexColor(color)) return
  const [r, g, b] = hexToRGB(color)
    .replace(/(?:\(|\)|rgb|RGB)*/g, '')
    .split(',')
    .map((item) => Number(item))
  return r * 0.299 + g * 0.578 + b * 0.114 < 192
}

// 减淡颜色
export const lighten = (color: string, amount: number) => {
  color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color
  amount = Math.trunc((255 * amount) / 100)
  return `#${addLight(color.substring(0, 2), amount)}${addLight(
    color.substring(2, 4),
    amount
  )}${addLight(color.substring(4, 6), amount)}`
}

// 混合两种颜色
export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
  let color = '#'
  for (let i = 0; i <= 2; i++) {
    const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16)
    const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16)
    const c = Math.round(c1 * weight + c2 * (1 - weight))
    color += c.toString(16).padStart(2, '0')
  }
  return color
}

这些工具函数使得主题色的处理更加灵活和精确,特别是在自动计算颜色的明暗度和生成色阶时非常有用。

1.4 主题设置组件

项目实现了一个专门的设置面板组件(src/components/Setting/src/Setting.vue),用于用户交互式地修改主题:

vue 复制代码
<template>
  <ElDrawer v-model="drawer" title="项目配置" size="300px">
    <!-- 主题色设置 -->
    <ElDivider>{{ t('setting.systemTheme') }}</ElDivider>
    <ColorRadioPicker
      v-model="systemTheme"
      @change="setSystemTheme"
      :colorList="defaultSystemThemes"
    />

    <!-- 头部主题设置 -->
    <ElDivider>{{ t('setting.headerTheme') }}</ElDivider>
    <ColorRadioPicker
      v-model="headerTheme"
      @change="setHeaderTheme"
      :colorList="defaultHeaderThemes"
    />

    <!-- 菜单主题设置 -->
    <ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
    <ColorRadioPicker
      v-model="menuTheme"
      @change="setMenuTheme"
      :colorList="defaultMenuThemes"
    />

    <!-- 其他设置... -->
  </ElDrawer>
</template>

<script setup>
// 设置系统主题色
const setSystemTheme = (color: string) => {
  setCssVar('--el-color-primary', color)
  appStore.setTheme({ elColorPrimary: color })
  // 更新菜单样式,确保颜色协调
  const leftMenuBgColor = useCssVar('--left-menu-bg-color', document.documentElement)
  setMenuTheme(trim(unref(leftMenuBgColor) as string))
}

// 设置头部主题
const setHeaderTheme = (color: string) => {
  appStore.setHeaderTheme(color)
}

// 设置菜单主题
const setMenuTheme = (color: string) => {
  appStore.setMenuTheme(color)
}
</script>

通过这个设置面板,用户可以轻松地自定义系统的主题色、头部主题和菜单主题。

2. 暗黑模式实现

暗黑模式是现代应用的标准功能,vue-element-plus-admin 项目通过结合 Element Plus 的暗黑模式支持和自定义 CSS 变量实现了完整的暗黑模式切换功能。

2.1 暗黑模式的状态管理

暗黑模式的状态同样存储在 app store 中:

typescript 复制代码
// src/store/modules/app.ts
interface AppState {
  // ... 其他状态
  isDark: boolean
  // ... 其他状态
}

// state 初始值
isDark: false

// actions
setIsDark(isDark: boolean) {
  this.isDark = isDark
  if (this.isDark) {
    document.documentElement.classList.add('dark')
    document.documentElement.classList.remove('light')
  } else {
    document.documentElement.classList.add('light')
    document.documentElement.classList.remove('dark')
  }
  this.setPrimaryLight()
}

2.2 主题色明暗度调整

暗黑模式下,需要调整主题色的明暗度,以确保良好的对比度和视觉效果:

typescript 复制代码
// 设置主题色的不同明暗度
setPrimaryLight() {
  if (this.theme.elColorPrimary) {
    const elColorPrimary = this.theme.elColorPrimary
    const color = this.isDark ? '#000000' : '#ffffff'
    const lightList = [3, 5, 7, 8, 9]
    lightList.forEach((v) => {
      setCssVar(`--el-color-primary-light-${v}`, mix(color, elColorPrimary, v / 10))
    })
    setCssVar(`--el-color-primary-dark-2`, mix(color, elColorPrimary, 0.2))
  }
}

2.3 暗黑模式切换组件

项目实现了一个专门的暗黑模式切换组件(src/components/ThemeSwitch/src/ThemeSwitch.vue):

vue 复制代码
<script setup lang="ts">
import { computed } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { ElSwitch } from 'element-plus'
import { useIcon } from '@/hooks/web/useIcon'
import { getCssVar } from '@/utils'

const emit = defineEmits(['change'])

const Sun = useIcon({ icon: 'vi-emojione-monotone:sun', color: '#fde047' })
const CrescentMoon = useIcon({ icon: 'vi-emojione-monotone:crescent-moon', color: '#fde047' })

const appStore = useAppStore()

// 初始化获取是否是暗黑主题
const isDark = computed({
  get() {
    return appStore.getIsDark
  },
  set(val: boolean) {
    appStore.setIsDark(val)
    const color = getCssVar('--el-bg-color')
    appStore.setMenuTheme(color)
    appStore.setHeaderTheme(color)
    emit('change', val)
  }
})
</script>

<template>
  <ElSwitch
    v-model="isDark"
    inline-prompt
    :border-color="blackColor"
    :inactive-color="blackColor"
    :active-color="blackColor"
    :active-icon="Sun"
    :inactive-icon="CrescentMoon"
  />
</template>

这个组件使用 Element Plus 的 Switch 组件,配合太阳和月亮图标,提供了一个直观的暗黑模式切换控件。

2.4 暗黑模式样式适配

暗黑模式需要调整各个组件和页面的样式,项目在 var.css 中定义了暗黑模式特定的样式变量:

css 复制代码
.dark {
  --app-content-bg-color: var(--el-bg-color);
  /* 其他暗黑模式特定的变量 */
}

项目还通过 VueUse 库的 useDark hook 实现了系统主题的自动检测:

typescript 复制代码
// 初始化主题模式
initTheme() {
  const isDark = useDark({
    valueDark: 'dark',
    valueLight: 'light'
  })
  isDark.value = this.getIsDark
  // ... 其他初始化逻辑
}

3. 国际化解决方案

vue-element-plus-admin 项目采用了 vue-i18n 实现多语言支持,覆盖了界面文本、错误提示、表单验证等各个方面。

3.1 国际化架构设计

项目的国际化架构主要包括以下部分:

  1. 语言文件src/locales 目录下的语言文件
  2. 语言状态管理src/store/modules/locale.ts 中的语言状态
  3. i18n 插件集成src/plugins/vueI18n 目录下的配置和辅助函数
  4. 国际化 Hooksrc/hooks/web/useLocale.ts 提供的便捷函数
  5. 语言切换组件src/components/LocaleDropdown 提供的语言切换下拉菜单

3.2 语言文件设计

项目定义了标准化的语言文件结构,以 zh-CN.tsen.ts 为例:

typescript 复制代码
// src/locales/zh-CN.ts
export default {
  common: {
    inputText: '请输入',
    selectText: '请选择',
    // ... 其他通用文本
  },
  login: {
    welcome: '欢迎使用本系统',
    message: '开箱即用的中后台管理系统',
    // ... 登录相关文本
  },
  router: {
    login: '登录',
    dashboard: '首页',
    // ... 路由名称文本
  },
  // ... 其他模块文本
}

// src/locales/en.ts
export default {
  common: {
    inputText: 'Please input',
    selectText: 'Please select',
    // ... 其他通用文本
  },
  // ... 其他模块文本
}

这种模块化的语言文件设计使得多语言内容维护更加清晰。

3.3 语言状态管理

项目使用 Pinia 管理语言状态,定义在 src/store/modules/locale.ts 中:

typescript 复制代码
interface LocaleState {
  currentLocale: LocaleDropdownType
  localeMap: LocaleDropdownType[]
}

export const useLocaleStore = defineStore('locales', {
  state: (): LocaleState => {
    return {
      currentLocale: {
        lang: getStorage('lang') || 'zh-CN',
        elLocale: elLocaleMap[getStorage('lang') || 'zh-CN']
      },
      // 多语言列表
      localeMap: [
        {
          lang: 'zh-CN',
          name: '简体中文'
        },
        {
          lang: 'en',
          name: 'English'
        }
      ]
    }
  },
  // getters 和 actions...
})

通过这个 store,应用可以全局管理当前语言和支持的语言列表。

3.4 i18n 插件配置

项目在 src/plugins/vueI18n/index.ts 中配置了 vue-i18n 插件:

typescript 复制代码
export const setupI18n = async (app: App<Element>) => {
  const options = await createI18nOptions()
  i18n = createI18n(options) as I18n
  app.use(i18n)
}

const createI18nOptions = async (): Promise<I18nOptions> => {
  const localeStore = useLocaleStoreWithOut()
  const locale = localeStore.getCurrentLocale
  const localeMap = localeStore.getLocaleMap
  const defaultLocal = await import(`../../locales/${locale.lang}.ts`)
  const message = defaultLocal.default ?? {}

  setHtmlPageLang(locale.lang)

  localeStore.setCurrentLocale({
    lang: locale.lang
  })

  return {
    legacy: false,
    locale: locale.lang,
    fallbackLocale: locale.lang,
    messages: {
      [locale.lang]: message
    },
    availableLocales: localeMap.map((v) => v.lang),
    sync: true,
    silentTranslationWarn: true,
    missingWarn: false,
    silentFallbackWarn: true
  }
}

这个配置实现了:

  1. 动态加载当前语言文件
  2. 设置文档的 lang 属性
  3. 配置 i18n 的行为选项

3.5 useLocale Hook

项目封装了 useLocale hook 简化国际化操作:

typescript 复制代码
// src/hooks/web/useLocale.ts
export const useLocale = () => {
  // 切换语言
  const changeLocale = async (locale: LocaleType) => {
    const globalI18n = i18n.global

    // 动态加载语言文件
    const langModule = await import(`../../locales/${locale}.ts`)
    globalI18n.setLocaleMessage(locale, langModule.default)

    setI18nLanguage(locale)
  }

  return {
    changeLocale
  }
}

// 设置 i18n 语言
const setI18nLanguage = (locale: LocaleType) => {
  const localeStore = useLocaleStoreWithOut()

  if (i18n.mode === 'legacy') {
    i18n.global.locale = locale
  } else {
    (i18n.global.locale as any).value = locale
  }
  
  localeStore.setCurrentLocale({
    lang: locale
  })
  
  setHtmlPageLang(locale)
}

这个 hook 提供了动态切换语言的功能,包括加载语言文件和更新语言设置。

3.6 语言切换组件

项目实现了一个多语言切换下拉菜单组件(src/components/LocaleDropdown/src/LocaleDropdown.vue):

vue 复制代码
<script setup lang="ts">
import { computed, unref } from 'vue'
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
import { useLocaleStore } from '@/store/modules/locale'
import { useLocale } from '@/hooks/web/useLocale'

const localeStore = useLocaleStore()
const langMap = computed(() => localeStore.getLocaleMap)
const currentLang = computed(() => localeStore.getCurrentLocale)

const setLang = (lang: LocaleType) => {
  if (lang === unref(currentLang).lang) return
  // 需要重新加载页面让整个语言多初始化
  window.location.reload()
  localeStore.setCurrentLocale({ lang })
  const { changeLocale } = useLocale()
  changeLocale(lang)
}
</script>

<template>
  <ElDropdown trigger="click" @command="setLang">
    <Icon
      :size="18"
      icon="vi-ion:language-sharp"
      class="cursor-pointer !p-0"
      :class="$attrs.class"
      :color="color"
    />
    <template #dropdown>
      <ElDropdownMenu>
        <ElDropdownItem v-for="item in langMap" :key="item.lang" :command="item.lang">
          {{ item.name }}
        </ElDropdownItem>
      </ElDropdownMenu>
    </template>
  </ElDropdown>
</template>

这个组件提供了一个简洁的界面,让用户可以轻松切换系统语言。

4. CSS 变量与样式模块化

vue-element-plus-admin 项目采用了基于 CSS 变量的样式系统,实现了高度可定制和模块化的样式管理。

4.1 CSS 变量系统

如前所述,项目在 src/styles/var.css 中定义了全局 CSS 变量,这些变量覆盖了布局、颜色、尺寸等各个方面。

项目还使用了 Element Plus 的 CSS 变量系统,与自定义 CSS 变量结合,形成了完整的主题变量系统。

4.2 Less 模块化样式

除了 CSS 变量外,项目还使用了 Less 预处理器和样式模块化技术:

vue 复制代码
<style lang="less" scoped>
@prefix-cls: ~'@{adminNamespace}-icon';

.@{prefix-cls} {
  // 样式定义
  :deep(svg) {
    &:hover {
      color: v-bind(hoverColor) !important;
    }
  }
}
</style>

这种方式的优点:

  1. 命名空间隔离:通过前缀避免样式冲突
  2. 变量复用:Less 变量可以在样式中复用
  3. 作用域限制:scoped 样式确保样式只影响当前组件
  4. 深度选择器 :使用 :deep 选择器修改子组件样式

4.3 样式工具函数

项目封装了一系列样式辅助函数,用于动态操作 CSS 变量:

typescript 复制代码
// src/utils/index.ts
/**
 * 设置 css 变量
 * @param prop 变量名
 * @param val 变量值
 * @param dom 元素
 */
export const setCssVar = (prop: string, val: any, dom = document.documentElement) => {
  dom.style.setProperty(prop, val)
}

/**
 * 获取 css 变量
 * @param prop 变量名
 * @param dom 元素
 */
export const getCssVar = (prop: string, dom = document.documentElement) => {
  return getComputedStyle(dom).getPropertyValue(prop)
}

这些工具函数使得在 JavaScript 中操作 CSS 变量变得简单直观。

4.4 useDesign Hook

项目提供了 useDesign hook,用于在组件中生成带有前缀的类名:

typescript 复制代码
// src/hooks/web/useDesign.ts
export const useDesign = () => {
  const getPrefixCls = (scope: string) => {
    return `${adminNamespace}-${scope}`
  }

  return {
    getPrefixCls
  }
}

在组件中使用:

typescript 复制代码
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('icon')

这种方式确保了样式类名的一致性和可维护性。

5. 自定义指令与插件开发

vue-element-plus-admin 项目开发了多个自定义指令和插件,扩展了 Vue 的基础功能。

5.1 权限指令

项目实现了权限控制指令 v-hasPermi,定义在 src/directives/permission/hasPermi.ts 中:

typescript 复制代码
import { useUserStoreWithOut } from '@/store/modules/user'
import { DirectiveBinding } from 'vue'

const hasPermission = (value: string | string[]): boolean => {
  if (!value) {
    return false
  }
  const userStore = useUserStoreWithOut()
  const permissions = userStore.getPermissions
  
  if (!permissions || !permissions.length) {
    return false
  }
  
  if (permissions.includes('*:*:*')) {
    return true
  }
  
  if (Array.isArray(value)) {
    return permissions.some((item) => value.includes(item))
  }
  
  return permissions.includes(value)
}

export const hasPermi = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const { value } = binding
    const hasAuth = hasPermission(value)
    
    if (!hasAuth) {
      el.parentNode?.removeChild(el)
    }
  }
}

这个指令可以在模板中直接使用:

vue 复制代码
<button v-hasPermi="'system:user:add'">添加用户</button>

当用户没有相应权限时,按钮会被自动移除。

5.2 可调整大小的弹窗指令

项目为可调整大小的弹窗实现了 v-resize 指令:

typescript 复制代码
// src/components/Dialog/src/ResizeDialog.vue
const vResize = {
  mounted(el) {
    const observer = new MutationObserver(() => {
      const elDialog = el.querySelector('.el-dialog')
      if (elDialog) {
        setupDrag(elDialog, el)
      }
    })
    observer.observe(el, { childList: true, subtree: true })
  }
}

// 注册指令
const initDirective = () => {
  const directives = instance?.appContext?.app._context?.directives
  if (!directives || !directives['resize']) {
    instance?.appContext?.app.directive('resize', vResize)
  }
}

这个指令使得 Dialog 组件可以通过拖拽边缘调整大小,提升了用户体验。

5.3 插件集成与扩展

项目在 src/plugins 目录下实现了多个插件,包括:

  1. animate.css:提供动画效果
  2. echarts:集成图表库
  3. elementPlus:Element Plus 组件库配置
  4. svgIcon:SVG 图标支持
  5. unocss:集成原子化 CSS
  6. vueI18n:国际化插件

以 Element Plus 插件为例:

typescript 复制代码
// src/plugins/elementPlus/index.ts
import { App, Component } from 'vue'
import {
  ElTag,
  ElButton,
  ElInput,
  // ... 其他组件
} from 'element-plus'
import 'element-plus/theme-chalk/index.css'

const components = [
  ElTag,
  ElButton,
  ElInput,
  // ... 其他组件
]

const plugins = [
  ElLoading,
  ElMessage,
  ElMessageBox,
  ElNotification
]

export const setupElementPlus = (app: App<Component>) => {
  components.forEach((component) => {
    app.component(component.name, component)
  })

  plugins.forEach((plugin) => {
    app.use(plugin)
  })
}

这种方式可以按需注册和配置第三方库,提高应用性能和灵活性。

6. 总结与最佳实践

通过对 vue-element-plus-admin 项目主题定制与国际化功能的分析,我们可以总结出以下最佳实践:

6.1 主题设计最佳实践

  1. CSS 变量为基础:使用 CSS 变量构建主题系统,方便动态切换和扩展
  2. 集中状态管理:使用 Pinia store 集中管理主题状态
  3. 颜色计算工具:实现颜色处理函数,自动计算色阶和明暗度
  4. 组件化设计:将主题切换功能封装为独立组件
  5. 适配暗黑模式:考虑明亮模式和暗黑模式的样式差异

6.2 国际化最佳实践

  1. 模块化语言文件:按功能模块组织语言文件内容
  2. 统一的语言键名:保持语言键名的一致性,避免遗漏翻译
  3. 运行时语言切换:支持无需刷新页面的语言切换
  4. 类型安全:使用 TypeScript 提供类型安全的国际化支持
  5. HTML 语言属性:切换语言时同步设置 HTML 的 lang 属性

6.3 样式模块化最佳实践

  1. 命名空间隔离:使用前缀避免样式冲突
  2. Scoped 样式:使用 Vue 的 scoped 样式限制作用域
  3. 辅助函数封装:封装样式相关的辅助函数,简化操作
  4. 响应式设计:确保在不同设备和主题下的样式一致性
  5. 样式复用:使用混合(mixins)和变量提高样式复用性

vue-element-plus-admin 项目的主题定制和国际化实现展现了现代前端应用的最佳实践,通过组件化、模块化和工具函数的合理使用,打造了一个高度可定制、用户友好的管理系统框架。这些实践对于构建大型企业级应用具有重要的参考价值。

相关推荐
前端小咸鱼一条2 小时前
React的介绍和特点
前端·react.js·前端框架
野区小女王6 小时前
react调用接口渲染数据时,这些表格里的数据是被禁选的
前端·react.js·前端框架
濮水大叔7 小时前
Prisma不能优雅的支持DTO,试试Vona ORM吧
前端框架·node.js·orm
用户151865304138411 小时前
从传统办公软件到云协作Flash Table AI分钟级生成表单,打造企业远程高效率办公的利器
前端·后端·前端框架
chancygcx_1 天前
前端框架Vue3(二)——Vue3核心语法之OptionsAPI与CompositionAPI与setup
vue.js·前端框架
止观止1 天前
Remix框架:高性能React全栈开发实战
前端·react.js·前端框架·remix
今禾1 天前
Zustand状态管理(上):现代React应用的轻量级状态解决方案
前端·react.js·前端框架
今禾1 天前
Zustand状态管理(下):从基础到高级应用
前端·react.js·前端框架
jingling5551 天前
UniApp 实现顶部固定导航栏 Tab 及滚动变色效果
前端·javascript·前端框架·uni-app