Vue2+Antd项目集成Semi主题化实战

项目背景:

项目使用的是vue2+antd搭建,是一个成熟完整项目。新需求要求,加入主题化功能。使用字节的semi(部分功能)来完成。

笔者的理解是用semi的主题化字典表(如:

--semi-color-primary: #3579ff;

--semi-color-primary-light-default: #e6f0ff;

--semi-color-bg-0: #ffffff;

--semi-color-text-0: #1c1f23;

但是具体的主题换转换功能,需要开发者自己实现。

开发者的任务是,将semi这些变量表注入到现有的项目中,同时,本项目中还有一些需求是有部分功能不受主题化影响,需做额外配置。

前置准备:

主题化npm包

下载依赖"@semi-bot/semi-theme-xxxxlight": "^1.0.4"---基于 Semi 定制的一套主题 npm 包

这个是自己推上去的css的npm包--即拿了官方的改成自己需要的

内部例:

javascript 复制代码
  --semi-color-text-0: rgba(var(--semi-grey-9),1);

主题化hooks文件

用于设置主题

javascript 复制代码
import Vue from 'vue';
import store from '@/store';

const THEMESTORAGENAMESPACE = Vue.ls.options.namespace;

// 主题常量
export const COMMINDTHEMESTYLE = {
  LIGHT: 'light',
  DARK: 'dark'
};

const COMMINDFIXEDTHEMEMAP = {
  [COMMINDTHEMESTYLE.LIGHT]: 'semi-always-light',
  [COMMINDTHEMESTYLE.DARK]: 'semi-always-dark'
};

/**
 * @description: 主题相关hooks
 * @return {*}
 */
export default function useThemeHooks() {
  /**
   * @description: 设置主题
   * @param {*} theme | COMMINDTHEMESTYLE
   * @return {*}
   */
  function setTheme(theme) {
    document.body.setAttribute('theme-mode', theme);
    Vue.ls.set('semi-theme', theme);
    store.dispatch('theme/setTheme', theme);
  }

  /**
   * @description: 获取主题
   * @return {*}
   */
  function getTheme() {
    return Vue.ls.get('semi-theme') || COMMINDTHEMESTYLE.LIGHT;
  }

  /**
   * @description: 切换主题
   * @return {*}
   */
  function toggleTheme() {
    const currentTheme = getTheme();
    const newTheme = currentTheme === COMMINDTHEMESTYLE.LIGHT ? COMMINDTHEMESTYLE.DARK : COMMINDTHEMESTYLE.LIGHT;
    setTheme(newTheme);
  }

  /**
   * @description: 初始化主题
   * @return {*}
   */
  function initTheme() {
    const savedTheme = getTheme();
    setTheme(savedTheme);
    addLocalStorageListener();
  }

  function setLightTheme() {
    setTheme(COMMINDTHEMESTYLE.LIGHT);
  }

  function setDarkTheme() {
    setTheme(COMMINDTHEMESTYLE.DARK);
  }

  /**
   * @description: 添加监听storage
   * @return {*}
   */
  function addLocalStorageListener() {
    window.addEventListener('storage', themeLocalStorageHandler);
  }

  /**
   * @description: 移除监听storage
   * @return {*}
   */
  function removeLocalStorageListener() {
    window.removeEventListener('storage', themeLocalStorageHandler);
  }

  /**
   * @description: 监听storage 切换主题
   * @param {*} e
   * @return {*}
   */
  function themeLocalStorageHandler(e) {
    const isThemeStorage = e.key === THEMESTORAGENAMESPACE + 'semi-theme';
    if (!isThemeStorage) return;
    const theme = getTheme();
    setTheme(theme);
  }

  return {
    setLightTheme,
    setDarkTheme,
    setTheme,
    getTheme,
    toggleTheme,
    initTheme,
    addLocalStorageListener,
    removeLocalStorageListener,
    COMMINDTHEMESTYLE,
    COMMINDFIXEDTHEMEMAP
  };
}

固定亮色主题hooks(即不需要主题化)vue自定义指令

用于固定主题,是vue自定义指令的形式

javascript 复制代码
import useThemeHooks from '@/hooks/useThemeHooks';
const { getTheme, COMMINDTHEMESTYLE } = useThemeHooks();

const fixedTheme = {
  bind() {
    // 预先设置 防止闪屏
    document.body.setAttribute('theme-mode', COMMINDTHEMESTYLE.LIGHT);

    // 1. 给body 添加mode
    setTimeout(() => {
      // 防止unbind执行时,dom还没渲染
      document.body.setAttribute('theme-mode', COMMINDTHEMESTYLE.LIGHT);
    }, 0);
  },
  unbind() {
    const theme = getTheme();
    // 恢复原来的mode
    document.body.setAttribute('theme-mode', theme);
  }
};

export default fixedTheme;

覆盖原antd的组件样式

javascript 复制代码
.ant-modal {
  color: var(--semi-color-text-0);
}
.ant-modal-header {
  background: var(--semi-color-bg-1);
  border-bottom: 1px solid var(--semi-color-border-1);
}
.ant-modal-title {
  color: var(--semi-color-text-0);
}

......

整体思路:

精简版:

先载入semi样式,然后在main.js初始化加载主题化-即

javascript 复制代码
document.body.setAttribute('theme-mode', theme);

组件主题化使用方式为样式里值写成变量如 color: var(--semi-color-text-0);

通过事件例如点击按钮,切换主题化。

特殊场景,可以使用

javascript 复制代码
[theme-mode='dark'] {
  .create-detail-modal {
    background-image: none;
  }
}

复杂版:

首先在global.less文件中引入semi的样式文件。

  • 启动时初始化主题:

    • 在 main.js 调用了 initTheme ,在页面刚加载时把主题应用到页面(设置 body 属性、写入 localStorage、写入 Vuex)。

      javascript 复制代码
      // 初始化主题
      const { initTheme } = useThemeHooks();
      initTheme();
    • 主题初始化逻辑在 useThemeHooks:包含 setTheme、getTheme、initTheme 等函数,以及主题常量 COMMINDTHEMESTYLE 和映射 COMMINDFIXEDTHEMEMAP。

  • 主题持久化与广播:

    • 主题会写入 localStorage(通过 Vue.ls),key 为 'semi-theme'(由 Vue.ls 配置的命名空间控制):见 useThemeHooks 中的 setTheme 实现。
    • 同时把 theme 写进 Vuex(store/modules/theme.js),并通过 getters 暴露。
  • 主题驱动方式:

    • 核心是把主题写到 document.body 的 attribute:document.body.setAttribute('theme-mode', theme)(在 setTheme 中)。组件常用两种方式判断当前主题:
      1. 读取 Vuex:this.$store?.state?.theme?.theme(若有响应式切换)。
      2. 兜底读取 DOM attribute:document.body.getAttribute('theme-mode')

具体使用方法:

首页主题载入(头部拦和侧边菜单栏部分)

javascript 复制代码
<a-layout class="layout-wrapper" :class="themeClass">
</a-layout>

<script>
import useThemeHooks from '@/hooks/useThemeHooks';

const { COMMINDFIXEDTHEMEMAP } = useThemeHooks();

export default {
  props: {
    ...HomeHeader.props
  },
  computed: {
    themeClass() {
      if (this.fixedTheme) {
        return COMMINDFIXEDTHEMEMAP[this.fixedTheme];
      }
      return '';
    },
    isInIframe() {
      if (this.$route.query.isInIframe === 'true') {
        return true;
      }
      return window.self !== window.top;
    }
  }
};
</script>

<style lang="less" scoped>
.semi-always-light {
  background-size: cover;
  background-position: top center;
  background-repeat: no-repeat;
  background-color: var(--semi-color-bg-0);
}
.semi-always-dark {
  background-color: var(--semi-color-bg-0);
  background-image: none;
}
.layout-wrapper {
  min-width: fit-content;
}
.layout-content {
  min-width: fit-content;
}
</style>

如何切换主题

javascript 复制代码
<a-button  @click="setDarkTheme"/>
const { COMMINDTHEMESTYLE, setLightTheme, setDarkTheme } = useThemeHooks();

固定化主题(固定浅色模式)用法

javascript 复制代码
  <Layout v-fixed-theme />

自定义组件添加主题化效果

例如下面,在深色模式下取消背景图。或者开发者希望在暗色模式下,做任何特殊化处理,都可以在这添加。

javascript 复制代码
[theme-mode='dark'] {
  .create-detail-modal {
    background-image: none;
  }
}

[theme-mode='dark'] {
  .personal-assets-recent .header .type-select .selected-creation-type {
    background: var(--semi-color-fill-3);
  }
}
相关推荐
ZHOUPUYU6 小时前
PHP 8.3网关优化:我用JIT将QPS提升300%的真实踩坑录
开发语言·php
mCell9 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell10 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭10 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
寻寻觅觅☆10 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
少云清10 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
萧曵 丶10 小时前
Vue 中父子组件之间最常用的业务交互场景
javascript·vue.js·交互
银烛木10 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_6070766011 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声11 小时前
CSS3 图片模糊处理
前端·css·css3