同一套前端代码,如何根据不同登录信息,实现多租户多主题的样式加载?


theme: juejin

同一前端项目实现多套主体色设置

应用背景

昨天一个前端同事问我,为什么他本地项目读取到的管理端租户的主题色配置是错的。我倒还从没深究过这个问题,即同一个前端项目中如何读取不同的主题色配置。正好遇到了这个机会,把目前项目中的解决方案记录下,方便自己理解消化,也算扩宽思路。

这个产品是一个多租户场景的管理系统,因此在登陆时会根据当前域名请求后台租户信息接口,得到对应的系统主题色配置,存入本地缓存。每一个租户的主题色都不一样。

租户A的主题色是黄色,租户B的主题色是红色,但是前端的代码都是同一套。究竟是怎么实现登录不同的租户系统,加载不同的主题色的?

系统的按钮/侧边栏/背景/按钮等都会根据主题色进行设置,这次就着眼于按钮是如何设置主题色的,触类旁通,会了一个,其他的都迎刃而解了。

前端项目框架为vue2.0,组件为IView3.5.4

当我声明type="primary"时,颜色从哪来?

前端组件用的IView,好像还是老版本。

先直接说实现方法:全局覆写 .ivu-btn-primary 这个类的样式

但其实一开始我想知道的是,当我声明按钮组件Button的type为primary时,这个颜色值#2d8cf0是在哪设置的。

翻了一圈官方文档,好像也没找到设置primary颜色的api,那就看下源码吧。

iview/src/styles/components/button.less

less 复制代码
.@{btn-prefix-cls} {
    .btn;
    .btn-default;
    /**
    * 省略无关代码
    **/


    &-ghost&-primary{
        color: @primary-color;
        &:hover{
            color: tint(@primary-color, 20%);
            background: fade(tint(@primary-color, 95%), 50%);
        }
    }
}

不是很熟悉less,不过按照关键词搜索到-primary这个选择器

得了,没有显式的import语句,那这个@primary-color应该是一个全局变量了

问了下神奇海螺chatGPT,给我指路到了这儿

iview/src/styles/custom.less

找一圈算是把这个primary-color的定义位置找到了

其实知道了按钮的样式类名.ivu-btn-primary 就已经可以做这个需求了

我折腾看了下源码,主要是加深下印象

毕竟实现的时候,又不能真的侵入去修改组件的源码

但知其然,知其所以然

页面渲染时,是如何读取主题色并全局覆盖的?

我根据primary-color这个关键词在项目中搜了下,果不其然找到了

在项目全局的预处理.less文件中,有一个与primary-color相关的theme.less文件,位于src/styles/theme.less

src/styles这个目录下存放了许多自定义的预处理样式文件.less,除了按钮样式覆盖外还有许多其他内容,不详细介绍了

less 复制代码
@import "~view-design/src/styles/index.less";
@import "./reset.less";
@import "./loading.less";
//https://github.com/view-design/ViewUI/blob/master/src/styles/custom.less

@primary-color: #0072bc;


.ivu-btn-primary {
  color: #fff;
  background-color: var(--main-button-color, #0072bc)!important;
  border-color: var(--main-button-color, #0072bc)!important;
  transition: all .2s ease-in!important;
}
.ivu-btn-primary:hover {
  color: #fff;
  background-color: var(--main-button-color, #0072bc);
  border-color: var(--main-button-color, #0072bc);
  opacity: .8;
}

// 省略其余

.ivu-btn-primary,找到这个样式的覆写了

在这里又引用了一个自定义的CSS属性--main-button-color,如果 --main-button-color 自定义属性没有定义,则使用默认的颜色值 #0072bc。

另外加上了 !important 来强制覆盖其他样式

和我想的实现方法大致差不多一样了

在 CSS 中,以两个连字符(--)开头的变量名是 CSS 自定义属性(CSS Custom Properties)的语法。在 CSS Custom Properties 中,你可以自定义变量,并在样式规则中使用这些变量。
定义自定义属性的语法如下:

--variable-name: value;
使用自定义属性的语法如下:
selector { property: var(--variable-name); }
--感谢chatGPT的馈赠

覆盖的样式已定义好了,挂载的步骤是怎么做的?

搜索下这个自定义的main-button-color属性,bingo,找到了!

在vue项目根目录下的index.html,也就是入口html的head标签里,找到了之前一位老哥自定义的script标签

xml 复制代码
<!-- index.html -->

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <title></title>
    <meta name="keywords" content="" />
    <meta name="description" content="" />
    <link
      rel="shortcut icon"
      id="favicon"
      href="./favicon.ico"
      type="image/x-icon"
    />
    <!-- <style id="css-vars">
      :root {
        --main-sub-menu-color: #0072bc;
        --main-sub-menu-title-color: #0072bc;
        --main-menu-active-color: #2D8cF0;
        --main-menu-active-head-color: #2D8cF0;
        --main-menu-title-hover-color: #2D8cF0;
      }
    </style> -->
    <script src="./css-vars.js"></script>
  </head>

  <!-- 省略若干代码 -->

</body>

甚至可以看到,刚开始项目未应用多主题色的时候,这些自定义属性值是写死的,注释都还保留着呢

后来用一段自定义的js代码来设置这些自定义属性了。看看这个css-vars.js的代码

javascript 复制代码
// css-vars.js

// 读取本地缓存,获取主题色信息
var platInfo = localStorage.platInfo ? JSON.parse(localStorage.platInfo) : {};
var themeBtnColor = platInfo.style.buttonColor || '';

// 拼接 style字符串
var styleStr = `
  <style id="css-vars">
  :root {
  
`

/**
*  省略其余和按钮颜色不相干的代码
**/

// 设置自定义CSS属性
if (themeBtnColor) {
  styleStr += `
    --main-button-color: ${themeBtnColor};
  `
} 

styleStr += `
}    
<\/style>
`
document.write(styleStr)

说白了,这段js代码做的事情就是在进行手动拼接,替换原来样式中写死的自定义属性的颜色,

拼成如下的格式,然后渲染到页面上。

在我的探寻过程中,我只关心--main-button-color这一个属性

但其实到了这一步,已经发现了,侧边栏/菜单的颜色都是取自定义属性来的

xml 复制代码
    <style id="css-vars">
      :root {
        --main-sub-menu-color: #0072bc;
        --main-sub-menu-title-color: #0072bc;
        --main-menu-active-color: #2D8cF0;
        --main-menu-active-head-color: #2D8cF0;
        --main-menu-title-hover-color: #2D8cF0;
        --main-button-color: #2D8cF0;
      }
    </style>

除此之外,css-vars.js还封装了一个函数,这个函数实现的逻辑和挂载的逻辑是几乎一模一样

javascript 复制代码
function setThemeColor () {
  console.log('setThemeColor')
  var style = document.getElementById('css-vars')
  var platInfo = localStorage.platInfo ? JSON.parse(localStorage.platInfo) : {}
  var themeBtnColor = platInfo.style.buttonColor || ''
  var styleStr = `
    :root {
  `

/**
*  省略其余和按钮颜色不相干的代码
**/

  if (themeBtnColor) {
    styleStr += `
      --main-button-color: ${themeBtnColor};
    `
  }

  styleStr += `
  }    
  `
  style.textContent = styleStr
}

每当用户手动更改了主题色时,前端再去请求一次后端主题色的接口,拿到更新后的数据,调用这个函数,就可以立即重新渲染。

赛后总结

总结一下实现这个功能的流程

  1. 声明了一个自定义的样式预处理.less,在这个文件中声明类.ivu-btn-primary,覆盖IView原生的样式
  2. 在自定义的.ivu-btn-primary样式中,使用了CSS的自定义属性--main-button-color
  3. 在项目的根html中,添加一个全局样式.root,这个样式中定义了自定义属性--main-button-color的具体变量值
  4. 页面准备渲染时,执行自定义的js代码段,读取本地缓存中的主题色配置,并拼接成style格式的字符串,覆盖掉原root的样式

有没有忘了些什么?

到了这,好像还是不能解答我那前端同事的疑惑:为什么本地调试时,取到的主题色不对?

好吧,最后发现这其实是数据问题。

他本地调试的时候设置了错误的token,而后端的接口是根据token解析返回不同系统的主题色的

怎么感觉像是白忙活一趟?算了,有所收获,善莫大焉~

相关推荐
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
前端郭德纲1 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR1 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式
帅帅哥的兜兜1 小时前
CSS:导航栏三角箭头
javascript·css3
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss
龙猫蓝图2 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
夜色呦2 小时前
掌握ECMAScript模块化:构建高效JavaScript应用
前端·javascript·ecmascript
peachSoda72 小时前
随手记:简单实现纯前端文件导出(XLSX)
前端·javascript·vue.js
大叔是90后大叔2 小时前
vue3中查找字典列表中某个元素的值对应的列表索引值
开发语言·前端·javascript