jjzjj

2022-03-01 Element UI切换主题、动态换肤功能

SHEN_JIAMEI 2023-11-29 原文

如果是新项目,可以选择使用element-plus,或者另寻他路,不要来踩这个坑。

以下方法适用于已经完成的项目,需要加换肤功能。借鉴官方在线切换主题。

 demo是新写的,node版本为v14.15.0,sass-loader版本为^12.0.0

https://github.com/jiameiShen/element-theme-demo.git

不想看我写废话的,可以直接去看项目~~~~~


1、引入主题element-variables,打包构建使用,可参考官方文档;

element-ui自定义主题官方文档

npm i element-theme -g  
npm i element-theme-chalk -D
et -i

常见报错

fs.js:45
} = primordials;
    ^

ReferenceError: primordials is not defined
    at fs.js:45:5

解决方案

这里我走了很多弯路,搜到过最多的说法是给node降级,虽然降级到node10x是可用的,但是会对影响项目本身,还会出现其他问题。

最后的解决方案是:

cnpm install element-themex -g

完美解决,然后再执行 : et -i 命令进行编译。

2、全局引入主题配置,方便文件中使用,个人习惯(可省略);

引入主题配置,全局可以使用主题样式,注意,我们的主题颜色是要动态变化的,所以不要在vue文件中使用$--color-primary以及其衍生的颜色,其他变量随便用;

// var.scss
/* 参考根目录下的 element-variables */
/* 切记此处不修改配置,以element-variables为准,可适当增加自定义配置 */
/* 如果要修改element-variables,请执行npm run build_theme更新主题 */

@import "element-variables";

// vue.config.js
css: {
    loaderOptions: {
      scss: {
        prependData: () => {
          return `
            @import "@/assets/scss/var.scss";
          `;
        },
      }
    }
  }

常见报错

 由于sass-loader版本不同,loaderOptions 中 additionalData的键名也不同,根据自己sass-loader的版本改就好了。

sass-loader v8-,这个选项名是 "data"
sass-loader v8 中,这个选项名是 "prependData"
sass-loader v10+,这个选项名是 "additionalData"

3、重点来了

首先获取已经生成好的css主题文件,然后将主题相关的颜色统一用标志置换,之后每次替换主题色,都去置换主题文件中的标志,即可得到最终的主题文件。

1、把theme相关的js写在一个文件夹下面:

// 安装css-color-function,后面会用到
cnpm i css-color-function -S
// color.js
import color from 'css-color-function'
import formula from './formula.json'

export function generateColors(primary) {
  let colors = {}

  Object.keys(formula).forEach((key) => {
    const value = formula[key].replace(/primary/g, primary)
    const c = color.convert(value)
    colors[key] = c.indexOf('rgba') > -1 ? c : colorRgbToHex(c)
  })

  return colors
}

/* 将rgb颜色转成hex */
export function colorRgbToHex(rgb) {
  let [r, g, b] = rgb.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',')

  return '#' + ((1 << 24) + (Number(r) << 16) + (Number(g) << 8) + Number(b)).toString(16).slice(1)
}
// formula.json
{
  "primary": "color(primary)",
  "shade-1": "color(primary shade(10%))",
  "shade-2": "color(primary shade(20%))",
  "shade-3": "color(primary shade(30%))",
  "shade-4": "color(primary shade(40%))",
  "shade-5": "color(primary shade(50%))",
  "shade-6": "color(primary shade(60%))",
  "shade-7": "color(primary shade(70%))",
  "shade-8": "color(primary shade(80%))",
  "shade-9": "color(primary shade(90%))",
  "alpha-1": "color(primary alpha(.1))",
  "alpha-2": "color(primary alpha(.2))",
  "alpha-3": "color(primary alpha(.3))",
  "alpha-4": "color(primary alpha(.4))",
  "alpha-5": "color(primary alpha(.5))",
  "alpha-6": "color(primary alpha(.6))",
  "alpha-7": "color(primary alpha(.7))",
  "alpha-8": "color(primary alpha(.8))",
  "alpha-9": "color(primary alpha(.9))",
  "light-1": "color(primary tint(10%))",
  "light-2": "color(primary tint(20%))",
  "light-3": "color(primary tint(30%))",
  "light-4": "color(primary tint(40%))",
  "light-5": "color(primary tint(50%))",
  "light-6": "color(primary tint(60%))",
  "light-7": "color(primary tint(70%))",
  "light-8": "color(primary tint(80%))",
  "light-9": "color(primary tint(90%))"
}
// index.js
import { generateColors } from './color'
import axios from 'axios'
import formula from './formula.json'
import variables from '@/assets/scss/var.scss'

let originalStyle = ''

export function writeNewStyle(themeColor) {
  let colors = generateColors(themeColor)
  let cssText = originalStyle
  let colorsCssText = ''
  Object.keys(colors).forEach((key) => {
    cssText = cssText.replace(new RegExp('(:|\\s+)' + key, 'g'), '$1' + `${colors[key]}`)
    colorsCssText += `
      .color-${key}{color: ${colors[key]}!important;}
      .bg-${key}{background-color: ${colors[key]}!important;}
      .border-${key}{border-color: ${colors[key]}!important;}
    `
  })
  document.getElementById('themeStyle').innerText = cssText + colorsCssText
}

export function getIndexStyle() {
  return new Promise((resolve) => {
    if (!originalStyle) {
      axios.get('/theme/index.css').then(file => {
          const fileData = file.data
          originalStyle = getStyleTemplate(fileData)
          resolve()
      })
    } else {
      resolve()
    }
  })
}

export function getStyleTemplate(data) {
  let colors = generateColors(variables.themeColor)
  const colorMap = new Map()
  Object.keys(formula).forEach((key) => {
    colorMap.set(colors[key], key)
  })

  for (let [key, value] of colorMap) {
    data = data.replace(new RegExp(key, 'ig'), value)
  }

  return data
}

2、我们要保证生成的新css能覆盖旧css,我用的笨方法,直接在index.html中引入css,同时把main.js里面的主题文件去掉,以免新css无法覆盖;

<link rel="stylesheet" href="<%= BASE_URL %>theme/index.css">
<!-- id为themeStyle后面会用到,不要删,与js文件中的id同步修改 -->
<style id="themeStyle" type="text/css"></style>

3、浅用一下

// main.js中使用
import variables from '@/assets/scss/var.scss'
import { getIndexStyle, writeNewStyle } from '@/tools/theme/index'

/* 初始化主题样式 */
getIndexStyle().then(() => {
  writeNewStyle(variables.themeColor)
})

报错了。。。

这里我们会先得到一个错误,那是因为我们的themeColor是想引用scss里面配置的变量,但是var.scss没有暴露出这个参数,所以我们改一改var.scss。

/* 参考根目录下的 element-variables */
/* 切记此处不修改配置,以element-variables为准,可适当增加自定义配置 */
/* 如果要修改element-variables,请执行npm run build_theme更新主题 */

@import "element-variables";

:export {
  name: "scss"; 
  themeColor: $--color-primary;
}

如果还是报同样的错误,就说明:export无效,我们打印variables 会发现得到的是空对象;

常见问题:

我这边直接贴上我的解决方案

// vue.config.js
css: {
    loaderOptions: {
      // 解决scss :export为空的情况
      // https://webpack.js.org/loaders/css-loader/
      css: {
        modules: {
          mode: 'icss',
        },
      },
      sass: {
        // additionalData: `@import "@/assets/scss/var.scss";`,
        // 文件中再引入var.scss会抛错
        // 该写法解决Syntax Error: SassError: This file is already being loaded.
        additionalData: (content, loaderContext) => {
          const { resourcePath } = loaderContext
          if (resourcePath.endsWith('var.scss')) return content
          return `@import "@/assets/scss/var.scss"; ${content}`
        },
      },
    },
  },

4、动态切换颜色

切换颜色随便用什么组件

主要就是使用writeNewStyle

import { writeNewStyle } from '@/tools/theme/index'

const newColor = '#6772E5'

writeNewStyle(newColor)

最后关于怎么保存和使用themeColor我就不赘述了,拜拜~~


总结

 demo是新写的,node版本为v14.15.0,sass-loader版本为^12.0.0

https://github.com/jiameiShen/element-theme-demo.git

以上解决方案用的是直接改写源css替换,算不上高级,但是有用。欢迎广大网友提建议。

有关2022-03-01 Element UI切换主题、动态换肤功能的更多相关文章

  1. ruby-on-rails - Ruby on Rails with Haml - 如何从 erb 切换 - 2

    我正在从erb文件切换到HAML。我将hamlgem添加到我的系统中。我创建了app/views/layouts/application.html.haml文件。我应该只删除application.html.erb文件吗?此外,仍然有/public/index.html文件被呈现为默认页面。我想创建自己的默认index.html.haml页面。我应该把它放在哪里以及如何使系统呈现该文件而不是默认索引文件?谢谢! 最佳答案 是的,您可以删除任何已转换为HAML的View的ERB版本。至于你的另一个问题,删除public/index/h

  2. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  3. ruby - 在 Ruby 中动态创建数组 - 2

    有没有办法在Ruby中动态创建数组?例如,假设我想遍历用户输入的书籍数组:books=gets.chomp用户输入:"TheGreatGatsby,CrimeandPunishment,Dracula,Fahrenheit451,PrideandPrejudice,SenseandSensibility,Slaughterhouse-Five,TheAdventuresofHuckleberryFinn"我把它变成一个数组:books_array=books.split(",")现在,对于用户输入的每一本书,我想用Ruby创建一个数组。伪代码来做到这一点:x=0books_array.

  4. ruby - 是否可以将 IRB 提示配置为动态更改? - 2

    我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO

  5. ruby-on-rails - rails 功能测试 - 2

    在Rails自动生成的功能测试(test/functional/products_controller_test.rb)中,我看到以下代码:classProductsControllerTest我的问题是:方法调用products()在哪里/如何定义?products(:one)到底是什么意思?看代码,大概意思是“创建一个产品”,但是它是如何工作的呢?注意我是Ruby/Rails的新手,如果这些是微不足道的问题,我深表歉意。 最佳答案 如果您查看test/fixtures文件夹,您会看到一个products.yml文件。这是在您创建

  6. ruby-on-rails - carrierwave:在序列化动态属性上安装 uploader - 2

    首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟

  7. ruby - 在 Ruby 中动态生成多维数组 - 2

    我正在尝试动态构建一个多维数组。我想要的基本上是这样的(为简单起见写出来):b=0test=[[]]test[b]这给了我错误:NoMethodError:undefinedmethod`test=[[],[],[]]而且它工作正常,但在我的实际使用中,我不会事先知道需要多少个数组。有一个更好的方法吗?谢谢 最佳答案 不需要像您正在使用的索引变量。只需将每个数组附加到您的test数组:irb>test=[]=>[]irb>test[["a","b","c"]]irb>test[["a","b","c"],["d","e","f"]]

  8. ruby-on-rails - 使用 gmaps4rails 动态加载谷歌地图标记 - 2

    如何只加载map边界内的标记gmaps4rails?当然,在平移和/或缩放后加载新的。与此直接相关的是,如何获取map的当前边界和缩放级别? 最佳答案 我是这样做的,我只在用户完成平移或缩放后替换标记,如果您需要不同的行为,请使用不同的事件监听器:在你看来(index.html.erb):{"zoom"=>15,"auto_adjust"=>false,"detect_location"=>true,"center_on_user"=>true}},false,true)%>在View的底部添加:functiongmaps4rail

  9. ruby - 动态方法链? - 2

    如何在对象上调用方法名称的嵌套哈希?例如,给定以下哈希:hash={:a=>{:b=>{:c=>:d}}}我想创建一个方法,给定上面的散列,执行以下操作:object.send(:a).send(:b).send(:c).send(:d)我的想法是我需要从一个未知的关联中获取一个特定的属性(这个方法不知道,但程序员知道)。我希望能够指定一个方法链来以嵌套哈希的形式检索该属性。例如:hash={:manufacturer=>{:addresses=>{:first=>:postal_code}}}car.execute_method_hash(hash)=>90210

  10. ruby-on-rails - 功能测试 Authlogic? - 2

    在我的一些Controller中,我有一个before_filter检查用户是否登录?用于CRUD操作。application.rbdeflogged_in?unlesscurrent_userredirect_toroot_pathendendprivatedefcurrent_user_sessionreturn@current_user_sessionifdefined?(@current_user_session)@current_user_session=UserSession.findenddefcurrent_userreturn@current_userifdefine

随机推荐