vue+element实现动态换肤功能

约定不等于承诺〃 2023-02-24 03:47 105阅读 0赞

有时候一个项目的主题并不能满足所有人的审美,这时候换肤功能就很友好,本项目基于vue+element实现后台管理项目的换肤功能

1.创建换肤组件

  1. <template>
  2. <el-color-picker
  3. class="theme-picker"
  4. popper-class="theme-picker-dropdown"
  5. v-model="theme"
  6. :predefine="predefineColors"
  7. ></el-color-picker>
  8. </template>
  9. <script>
  10. const version = require("element-ui/package.json").version; // element-ui version from node_modules
  11. const ORIGINAL_THEME = "#409EFF"; // default color
  12. export default {
  13. name: "ThemePicker",
  14. props: {
  15. default: {
  16. // 初始化主题,可由外部传入
  17. type: String
  18. // default: '#2668b1'
  19. // default: `${localStorage.getItem("tremePackers")==null?"#C60404":localStorage.getItem("tremePackers")}`
  20. }
  21. // size: { // 初始化主题,可由外部传入
  22. // type: String,
  23. // default: 'small'
  24. // },
  25. },
  26. data() {
  27. return {
  28. chalk: "", // content of theme-chalk css
  29. theme: ORIGINAL_THEME,
  30. showSuccess: true, // 是否弹出换肤成功消息
  31. predefineColors: [
  32. "#2668b1",
  33. "#52b493",
  34. "#429798",
  35. "#32789c",
  36. "#1944a5",
  37. "#5944bc",
  38. "#995dcd",
  39. "#ce7e5b",
  40. "#ee8b9b",
  41. "#283551"
  42. ]
  43. };
  44. },
  45. mounted() {
  46. this.theme = this.defaultTheme;
  47. // this.$emit('onThemeChange', this.theme)
  48. this.showSuccess = true;
  49. },
  50. computed: {
  51. defaultTheme() {
  52. return this.$store.state.theme;
  53. }
  54. },
  55. watch: {
  56. async theme(val, oldVal) {
  57. if (typeof val !== "string") return;
  58. const themeCluster = this.getThemeCluster(val.replace("#", ""));
  59. const originalCluster = this.getThemeCluster(oldVal.replace("#", ""));
  60. const getHandler = (variable, id) => {
  61. return () => {
  62. const originalCluster = this.getThemeCluster(
  63. ORIGINAL_THEME.replace("#", "")
  64. );
  65. const newStyle = this.updateStyle(
  66. this[variable],
  67. originalCluster,
  68. themeCluster
  69. );
  70. let styleTag = document.getElementById(id);
  71. if (!styleTag) {
  72. styleTag = document.createElement("style");
  73. styleTag.setAttribute("id", id);
  74. // document.head.appendChild(styleTag)
  75. document
  76. .getElementsByTagName("style")[0]
  77. .insertBefore(styleTag, null);
  78. }
  79. styleTag.innerText = newStyle;
  80. };
  81. };
  82. const chalkHandler = getHandler("chalk", "chalk-style");
  83. if (!this.chalk) {
  84. const url = `../../assets/style/theme/index.css`;//本地css样式地址
  85. // const url = `./dist/index.css`;//项目打包后css地址(原文件放入public文件夹中)
  86. // const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`;//如果是公司内网,此网址则不适用
  87. this.getCSSString(url, chalkHandler, "chalk");
  88. } else {
  89. chalkHandler();
  90. }
  91. const styles = [].slice
  92. .call(document.querySelectorAll("style"))
  93. .filter(style => {
  94. const text = style.innerText;
  95. return (
  96. new RegExp(oldVal, "i").test(text) &&
  97. !/Chalk Variables/.test(text)
  98. );
  99. });
  100. styles.forEach(style => {
  101. const { innerText } = style;
  102. if (typeof innerText !== "string") return;
  103. style.innerText = this.updateStyle(
  104. innerText,
  105. originalCluster,
  106. themeCluster
  107. );
  108. });
  109. this.$store.commit("themColor", val);//将更换的颜色存入store
  110. this.$emit("onThemeChange", val);
  111. // 响应外部操作
  112. //存入localStorage
  113. // localStorage.setItem('tremePackers',val);
  114. // if(this.showSuccess) {
  115. // this.$message({
  116. // message: '换肤成功',
  117. // type: 'success'
  118. // })
  119. // } else {
  120. // this.showSuccess = true
  121. // }
  122. }
  123. },
  124. methods: {
  125. updateStyle(style, oldCluster, newCluster) {
  126. let newStyle = style;
  127. oldCluster.forEach((color, index) => {
  128. newStyle = newStyle.replace(new RegExp(color, "ig"), newCluster[index]);
  129. });
  130. return newStyle;
  131. },
  132. getCSSString(url, callback, variable) {
  133. const xhr = new XMLHttpRequest();
  134. xhr.onreadystatechange = () => {
  135. if (xhr.readyState === 4 && xhr.status === 200) {
  136. this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, "");
  137. callback();
  138. }
  139. };
  140. xhr.open("GET", url);
  141. xhr.send();
  142. },
  143. getThemeCluster(theme) {
  144. const tintColor = (color, tint) => {
  145. let red = parseInt(color.slice(0, 2), 16);
  146. let green = parseInt(color.slice(2, 4), 16);
  147. let blue = parseInt(color.slice(4, 6), 16);
  148. if (tint === 0) {
  149. // when primary color is in its rgb space
  150. return [red, green, blue].join(",");
  151. } else {
  152. red += Math.round(tint * (255 - red));
  153. green += Math.round(tint * (255 - green));
  154. blue += Math.round(tint * (255 - blue));
  155. red = red.toString(16);
  156. green = green.toString(16);
  157. blue = blue.toString(16);
  158. return `#${red}${green}${blue}`;
  159. }
  160. };
  161. const shadeColor = (color, shade) => {
  162. let red = parseInt(color.slice(0, 2), 16);
  163. let green = parseInt(color.slice(2, 4), 16);
  164. let blue = parseInt(color.slice(4, 6), 16);
  165. red = Math.round((1 - shade) * red);
  166. green = Math.round((1 - shade) * green);
  167. blue = Math.round((1 - shade) * blue);
  168. red = red.toString(16);
  169. green = green.toString(16);
  170. blue = blue.toString(16);
  171. return `#${red}${green}${blue}`;
  172. };
  173. const clusters = [theme];
  174. for (let i = 0; i <= 9; i++) {
  175. clusters.push(tintColor(theme, Number((i / 10).toFixed(2))));
  176. }
  177. clusters.push(shadeColor(theme, 0.1));
  178. return clusters;
  179. }
  180. }
  181. };
  182. </script>
  183. <style>
  184. .theme-picker .el-color-picker__trigger {
  185. vertical-align: middle;
  186. }
  187. .theme-picker-dropdown .el-color-dropdown__link-btn {
  188. display: none;
  189. }
  190. .el-color-picker--small .el-color-picker__trigger {
  191. border: none;
  192. }
  193. </style>

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25yc2toZHps_size_16_color_FFFFFF_t_70

上面这块代码值得注意,红框里的代码是在head里所有节点后插入一个新的style标签,打包后优先级较高,但有个问题,有些地方的颜色直接消失变成空白影响了样式,于是改成绿框里的代码,但绿框里的代码打包后优先级会低于原来样式颜色的而优先级,所以需要根据项目调样式优先级

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25yc2toZHps_size_16_color_FFFFFF_t_70 1

这块的代码也需要注意,如果公司直接使用的外网那么直接使用第三个url就行,如果公司使用的内网访问不了外部网页那么可以通过第三条url下载项目对应element版本的css样式,把css文件放到项目里,但要注意放到不会被编译的问价夹里,我项目用的是vue cli4,所以我动态转换的css文件放在public文件夹里,放到assets问价夹中样式文件会被编译,因而路径会报404,并且这块使用的url是文件打包编译后样式的路径,这是值得注意的地方

2.如果项目中有的样式颜色没有用到element,可以把颜色缓存到vuex里,然后在具体逐渐里通过计算属性获取再在样式上动态绑定

vuex:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25yc2toZHps_size_16_color_FFFFFF_t_70 2

使用的组件中:

20200708133515521.png

20200708133341603.png

发表评论

表情:
评论列表 (有 0 条评论,105人围观)

还没有评论,来说两句吧...

相关阅读

    相关 uni-app 应用功能

    应用换肤这个 “毫无卵用”  又浪费时间的的功能却是许多产品经理们喜欢玩的(某信国民应用都不做的功能)。 首先我坦白,我TM就从没用过 应用换肤 这种功能,其次我 氪金 跟有