From bd2f187c3f6e24576919e71c794cf8cb9f8c8bac Mon Sep 17 00:00:00 2001
From: loulijun2021 <1694218219@qq.com>
Date: 星期四, 29 十二月 2022 11:21:41 +0800
Subject: [PATCH] 1.初级实现项目换肤功能
---
src/styles/element-variables.scss | 31 ++
src/components/RightPanel/index.vue | 145 ++++++++++
src/layout/index.vue | 79 +++--
src/settings.js | 13
src/layout/components/index.js | 1
src/components/ThemePicker/index.vue | 175 ++++++++++++
src/utils/index.js | 240 +++++++++++++++++
src/layout/components/Settings/index.vue | 109 +++++++
src/store/modules/settings.js | 2
9 files changed, 760 insertions(+), 35 deletions(-)
diff --git a/src/components/RightPanel/index.vue b/src/components/RightPanel/index.vue
new file mode 100644
index 0000000..55e8c1e
--- /dev/null
+++ b/src/components/RightPanel/index.vue
@@ -0,0 +1,145 @@
+<template>
+ <div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
+ <div class="rightPanel-background" />
+ <div class="rightPanel">
+ <div class="handle-button" :style="{'top':buttonTop+'px','background-color':theme}" @click="show=!show">
+ <i :class="show?'el-icon-close':'el-icon-setting'" />
+ </div>
+ <div class="rightPanel-items">
+ <slot />
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+import { addClass, removeClass } from '@/utils'
+
+export default {
+ name: 'RightPanel',
+ props: {
+ clickNotClose: {
+ default: false,
+ type: Boolean
+ },
+ buttonTop: {
+ default: 250,
+ type: Number
+ }
+ },
+ data() {
+ return {
+ show: false
+ }
+ },
+ computed: {
+ theme() {
+ return this.$store.state.settings.theme
+ }
+ },
+ watch: {
+ show(value) {
+ if (value && !this.clickNotClose) {
+ this.addEventClick()
+ }
+ if (value) {
+ addClass(document.body, 'showRightPanel')
+ } else {
+ removeClass(document.body, 'showRightPanel')
+ }
+ }
+ },
+ mounted() {
+ this.insertToBody()
+ },
+ beforeDestroy() {
+ const elx = this.$refs.rightPanel
+ elx.remove()
+ },
+ methods: {
+ addEventClick() {
+ window.addEventListener('click', this.closeSidebar)
+ },
+ closeSidebar(evt) {
+ const parent = evt.target.closest('.rightPanel')
+ if (!parent) {
+ this.show = false
+ window.removeEventListener('click', this.closeSidebar)
+ }
+ },
+ insertToBody() {
+ const elx = this.$refs.rightPanel
+ const body = document.querySelector('body')
+ body.insertBefore(elx, body.firstChild)
+ }
+ }
+}
+</script>
+
+<style>
+.showRightPanel {
+ overflow: hidden;
+ position: relative;
+ width: calc(100% - 15px);
+}
+</style>
+
+<style lang="scss" scoped>
+.rightPanel-background {
+ position: fixed;
+ top: 0;
+ left: 0;
+ opacity: 0;
+ transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
+ background: rgba(0, 0, 0, .2);
+ z-index: -1;
+}
+
+.rightPanel {
+ width: 100%;
+ max-width: 260px;
+ height: 100vh;
+ position: fixed;
+ top: 0;
+ right: 0;
+ box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
+ transition: all .25s cubic-bezier(.7, .3, .1, 1);
+ transform: translate(100%);
+ background: #fff;
+ z-index: 40000;
+}
+
+.show {
+ transition: all .3s cubic-bezier(.7, .3, .1, 1);
+
+ .rightPanel-background {
+ z-index: 20000;
+ opacity: 1;
+ width: 100%;
+ height: 100%;
+ }
+
+ .rightPanel {
+ transform: translate(0);
+ }
+}
+
+.handle-button {
+ width: 48px;
+ height: 48px;
+ position: absolute;
+ left: -48px;
+ text-align: center;
+ font-size: 24px;
+ border-radius: 6px 0 0 6px !important;
+ z-index: 0;
+ pointer-events: auto;
+ cursor: pointer;
+ color: #fff;
+ line-height: 48px;
+ i {
+ font-size: 24px;
+ line-height: 48px;
+ }
+}
+</style>
diff --git a/src/components/ThemePicker/index.vue b/src/components/ThemePicker/index.vue
new file mode 100644
index 0000000..3879c5a
--- /dev/null
+++ b/src/components/ThemePicker/index.vue
@@ -0,0 +1,175 @@
+<template>
+ <el-color-picker
+ v-model="theme"
+ :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
+ class="theme-picker"
+ popper-class="theme-picker-dropdown"
+ />
+</template>
+
+<script>
+const version = require('element-ui/package.json').version // element-ui version from node_modules
+const ORIGINAL_THEME = '#409EFF' // default color
+
+export default {
+ data() {
+ return {
+ chalk: '', // content of theme-chalk css
+ theme: ''
+ }
+ },
+ computed: {
+ defaultTheme() {
+ return this.$store.state.settings.theme
+ }
+ },
+ watch: {
+ defaultTheme: {
+ handler: function(val, oldVal) {
+ this.theme = val
+ },
+ immediate: true
+ },
+ async theme(val) {
+ const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
+ if (typeof val !== 'string') return
+ const themeCluster = this.getThemeCluster(val.replace('#', ''))
+ const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
+ console.log(themeCluster, originalCluster)
+
+ const $message = this.$message({
+ message: ' Compiling the theme',
+ customClass: 'theme-message',
+ type: 'success',
+ duration: 0,
+ iconClass: 'el-icon-loading'
+ })
+
+ const getHandler = (variable, id) => {
+ return () => {
+ const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
+ const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
+
+ let styleTag = document.getElementById(id)
+ if (!styleTag) {
+ styleTag = document.createElement('style')
+ styleTag.setAttribute('id', id)
+ document.head.appendChild(styleTag)
+ }
+ styleTag.innerText = newStyle
+ }
+ }
+
+ if (!this.chalk) {
+ const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
+ await this.getCSSString(url, 'chalk')
+ }
+
+ const chalkHandler = getHandler('chalk', 'chalk-style')
+
+ chalkHandler()
+
+ const styles = [].slice.call(document.querySelectorAll('style'))
+ .filter(style => {
+ const text = style.innerText
+ return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
+ })
+ styles.forEach(style => {
+ const { innerText } = style
+ if (typeof innerText !== 'string') return
+ style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
+ })
+
+ this.$emit('change', val)
+
+ $message.close()
+ }
+ },
+
+ methods: {
+ updateStyle(style, oldCluster, newCluster) {
+ let newStyle = style
+ oldCluster.forEach((color, index) => {
+ newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
+ })
+ return newStyle
+ },
+
+ getCSSString(url, variable) {
+ return new Promise(resolve => {
+ const xhr = new XMLHttpRequest()
+ xhr.onreadystatechange = () => {
+ if (xhr.readyState === 4 && xhr.status === 200) {
+ this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
+ resolve()
+ }
+ }
+ xhr.open('GET', url)
+ xhr.send()
+ })
+ },
+
+ getThemeCluster(theme) {
+ const tintColor = (color, tint) => {
+ let red = parseInt(color.slice(0, 2), 16)
+ let green = parseInt(color.slice(2, 4), 16)
+ let blue = parseInt(color.slice(4, 6), 16)
+
+ if (tint === 0) { // when primary color is in its rgb space
+ return [red, green, blue].join(',')
+ } else {
+ red += Math.round(tint * (255 - red))
+ green += Math.round(tint * (255 - green))
+ blue += Math.round(tint * (255 - blue))
+
+ red = red.toString(16)
+ green = green.toString(16)
+ blue = blue.toString(16)
+
+ return `#${red}${green}${blue}`
+ }
+ }
+
+ const shadeColor = (color, shade) => {
+ let red = parseInt(color.slice(0, 2), 16)
+ let green = parseInt(color.slice(2, 4), 16)
+ let blue = parseInt(color.slice(4, 6), 16)
+
+ red = Math.round((1 - shade) * red)
+ green = Math.round((1 - shade) * green)
+ blue = Math.round((1 - shade) * blue)
+
+ red = red.toString(16)
+ green = green.toString(16)
+ blue = blue.toString(16)
+
+ return `#${red}${green}${blue}`
+ }
+
+ const clusters = [theme]
+ for (let i = 0; i <= 9; i++) {
+ clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
+ }
+ clusters.push(shadeColor(theme, 0.1))
+ return clusters
+ }
+ }
+}
+</script>
+
+<style>
+.theme-message,
+.theme-picker-dropdown {
+ z-index: 99999 !important;
+}
+
+.theme-picker .el-color-picker__trigger {
+ height: 26px !important;
+ width: 26px !important;
+ padding: 2px;
+}
+
+.theme-picker-dropdown .el-color-dropdown__link-btn {
+ display: none;
+}
+</style>
diff --git a/src/layout/components/Settings/index.vue b/src/layout/components/Settings/index.vue
new file mode 100644
index 0000000..e0490c9
--- /dev/null
+++ b/src/layout/components/Settings/index.vue
@@ -0,0 +1,109 @@
+<template>
+ <div class="drawer-container">
+ <div>
+ <h3 class="drawer-title">Page style setting</h3>
+
+ <div class="drawer-item">
+ <span>Theme Color</span>
+ <theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" />
+ </div>
+
+ <!-- 姝や笁涓姛鑳芥殏鏃朵笉鐢�-->
+ <!-- <div class="drawer-item">-->
+ <!-- <span>Open Tags-View</span>-->
+ <!-- <el-switch v-model="tagsView" class="drawer-switch" />-->
+ <!-- </div>-->
+
+ <!-- <div class="drawer-item">-->
+ <!-- <span>Fixed Header</span>-->
+ <!-- <el-switch v-model="fixedHeader" class="drawer-switch" />-->
+ <!-- </div>-->
+
+ <!-- <div class="drawer-item">-->
+ <!-- <span>Sidebar Logo</span>-->
+ <!-- <el-switch v-model="sidebarLogo" class="drawer-switch" />-->
+ <!-- </div>-->
+
+ </div>
+ </div>
+</template>
+
+<script>
+import ThemePicker from '@/components/ThemePicker'
+
+export default {
+ components: { ThemePicker },
+ data() {
+ return {}
+ },
+ computed: {
+ fixedHeader: {
+ get() {
+ return this.$store.state.settings.fixedHeader
+ },
+ set(val) {
+ this.$store.dispatch('settings/changeSetting', {
+ key: 'fixedHeader',
+ value: val
+ })
+ }
+ },
+ tagsView: {
+ get() {
+ return this.$store.state.settings.tagsView
+ },
+ set(val) {
+ this.$store.dispatch('settings/changeSetting', {
+ key: 'tagsView',
+ value: val
+ })
+ }
+ },
+ sidebarLogo: {
+ get() {
+ return this.$store.state.settings.sidebarLogo
+ },
+ set(val) {
+ this.$store.dispatch('settings/changeSetting', {
+ key: 'sidebarLogo',
+ value: val
+ })
+ }
+ }
+ },
+ methods: {
+ themeChange(val) {
+ this.$store.dispatch('settings/changeSetting', {
+ key: 'theme',
+ value: val
+ })
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.drawer-container {
+ padding: 24px;
+ font-size: 14px;
+ line-height: 1.5;
+ word-wrap: break-word;
+
+ .drawer-title {
+ margin-bottom: 12px;
+ color: rgba(0, 0, 0, .85);
+ font-size: 14px;
+ line-height: 22px;
+ }
+
+ .drawer-item {
+ color: rgba(0, 0, 0, .65);
+ font-size: 14px;
+ padding: 12px 0;
+ }
+
+ .drawer-switch {
+ float: right
+ }
+}
+</style>
diff --git a/src/layout/components/index.js b/src/layout/components/index.js
index 9fc98d6..9e7c4a5 100644
--- a/src/layout/components/index.js
+++ b/src/layout/components/index.js
@@ -2,3 +2,4 @@
export { default as Sidebar } from './Sidebar'
export { default as AppMain } from './AppMain'
export { default as TagsView } from './TagsView'
+export { default as Settings } from './Settings'
diff --git a/src/layout/index.vue b/src/layout/index.vue
index 6ae88f3..58ca9b0 100644
--- a/src/layout/index.vue
+++ b/src/layout/index.vue
@@ -8,13 +8,17 @@
<tags-view v-if="needTagsView" />
</div>
<app-main />
+ <right-panel v-if="showSettings">
+ <settings />
+ </right-panel>
</div>
</div>
</template>
<script>
-import { Navbar, Sidebar, AppMain, TagsView } from './components'
+import { Navbar, Sidebar, AppMain, TagsView, Settings } from './components'
import ResizeMixin from './mixin/ResizeHandler'
+import RightPanel from '@/components/RightPanel'
export default {
name: 'Layout',
@@ -22,7 +26,9 @@
Navbar,
Sidebar,
AppMain,
- TagsView
+ TagsView,
+ RightPanel,
+ Settings
},
mixins: [ResizeMixin],
computed: {
@@ -37,6 +43,9 @@
},
fixedHeader() {
return this.$store.state.settings.fixedHeader
+ },
+ showSettings() {
+ return this.$store.state.settings.showSettings
},
classObj() {
return {
@@ -56,43 +65,45 @@
</script>
<style lang="scss" scoped>
- @import "~@/styles/mixin.scss";
- @import "~@/styles/variables.scss";
+@import "~@/styles/mixin.scss";
+@import "~@/styles/variables.scss";
- .app-wrapper {
- @include clearfix;
- position: relative;
- height: 100%;
- width: 100%;
- &.mobile.openSidebar{
- position: fixed;
- top: 0;
- }
- }
- .drawer-bg {
- background: #000;
- opacity: 0.3;
- width: 100%;
- top: 0;
- height: 100%;
- position: absolute;
- z-index: 999;
- }
+.app-wrapper {
+ @include clearfix;
+ position: relative;
+ height: 100%;
+ width: 100%;
- .fixed-header {
+ &.mobile.openSidebar {
position: fixed;
top: 0;
- right: 0;
- z-index: 9;
- width: calc(100% - #{$sideBarWidth});
- transition: width 0.28s;
}
+}
- .hideSidebar .fixed-header {
- width: calc(100% - 54px)
- }
+.drawer-bg {
+ background: #000;
+ opacity: 0.3;
+ width: 100%;
+ top: 0;
+ height: 100%;
+ position: absolute;
+ z-index: 999;
+}
- .mobile .fixed-header {
- width: 100%;
- }
+.fixed-header {
+ position: fixed;
+ top: 0;
+ right: 0;
+ z-index: 9;
+ width: calc(100% - #{$sideBarWidth});
+ transition: width 0.28s;
+}
+
+.hideSidebar .fixed-header {
+ width: calc(100% - 54px)
+}
+
+.mobile .fixed-header {
+ width: 100%;
+}
</style>
diff --git a/src/settings.js b/src/settings.js
index 8f14f63..8c2deab 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -13,6 +13,17 @@
* @description Whether show the logo in sidebar
*/
sidebarLogo: false,
- tagsView: true // tagsView鏄鹃殣鎺у埗
+
+ /**
+ * @type {boolean} true | false
+ * @description Whether show the settings right-panel
+ */
+ showSettings: true,
+
+ /**
+ * @type {boolean} true | false
+ * @description Whether need tagsView
+ */
+ tagsView: true
}
diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js
index fd3ed35..dd8931e 100644
--- a/src/store/modules/settings.js
+++ b/src/store/modules/settings.js
@@ -1,8 +1,10 @@
import defaultSettings from '@/settings'
+import variables from '@/styles/element-variables.scss'
const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings
const state = {
+ theme: variables.theme,
showSettings: showSettings,
fixedHeader: fixedHeader,
sidebarLogo: sidebarLogo,
diff --git a/src/styles/element-variables.scss b/src/styles/element-variables.scss
new file mode 100644
index 0000000..5bdc4da
--- /dev/null
+++ b/src/styles/element-variables.scss
@@ -0,0 +1,31 @@
+/**
+* I think element-ui's default theme color is too light for long-term use.
+* So I modified the default color and you can modify it to your liking.
+**/
+
+/* theme color */
+$--color-primary: #1890ff;
+$--color-success: #13ce66;
+$--color-warning: #ffba00;
+$--color-danger: #ff4949;
+// $--color-info: #1E1E1E;
+
+$--button-font-weight: 400;
+
+// $--color-text-regular: #1f2d3d;
+
+$--border-color-light: #dfe4ed;
+$--border-color-lighter: #e6ebf5;
+
+$--table-border: 1px solid #dfe6ec;
+
+/* icon font path, required */
+$--font-path: "~element-ui/lib/theme-chalk/fonts";
+
+@import "~element-ui/packages/theme-chalk/src/index";
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+ theme: $--color-primary;
+}
diff --git a/src/utils/index.js b/src/utils/index.js
index 4830c04..3225d3c 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -98,6 +98,69 @@
* @param {string} url
* @returns {Object}
*/
+export function getQueryObject(url) {
+ url = url == null ? window.location.href : url
+ const search = url.substring(url.lastIndexOf('?') + 1)
+ const obj = {}
+ const reg = /([^?&=]+)=([^?&=]*)/g
+ search.replace(reg, (rs, $1, $2) => {
+ const name = decodeURIComponent($1)
+ let val = decodeURIComponent($2)
+ val = String(val)
+ obj[name] = val
+ return rs
+ })
+ return obj
+}
+
+/**
+ * @param {string} input value
+ * @returns {number} output value
+ */
+export function byteLength(str) {
+ // returns the byte length of an utf8 string
+ let s = str.length
+ for (var i = str.length - 1; i >= 0; i--) {
+ const code = str.charCodeAt(i)
+ if (code > 0x7f && code <= 0x7ff) s++
+ else if (code > 0x7ff && code <= 0xffff) s += 2
+ if (code >= 0xDC00 && code <= 0xDFFF) i--
+ }
+ return s
+}
+
+/**
+ * @param {Array} actual
+ * @returns {Array}
+ */
+export function cleanArray(actual) {
+ const newArray = []
+ for (let i = 0; i < actual.length; i++) {
+ if (actual[i]) {
+ newArray.push(actual[i])
+ }
+ }
+ return newArray
+}
+
+/**
+ * @param {Object} json
+ * @returns {Array}
+ */
+export function param(json) {
+ if (!json) return ''
+ return cleanArray(
+ Object.keys(json).map(key => {
+ if (json[key] === undefined) return ''
+ return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
+ })
+ ).join('&')
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
export function param2Obj(url) {
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
if (!search) {
@@ -115,3 +178,180 @@
})
return obj
}
+
+/**
+ * @param {string} val
+ * @returns {string}
+ */
+export function html2Text(val) {
+ const div = document.createElement('div')
+ div.innerHTML = val
+ return div.textContent || div.innerText
+}
+
+/**
+ * Merges two objects, giving the last one precedence
+ * @param {Object} target
+ * @param {(Object|Array)} source
+ * @returns {Object}
+ */
+export function objectMerge(target, source) {
+ if (typeof target !== 'object') {
+ target = {}
+ }
+ if (Array.isArray(source)) {
+ return source.slice()
+ }
+ Object.keys(source).forEach(property => {
+ const sourceProperty = source[property]
+ if (typeof sourceProperty === 'object') {
+ target[property] = objectMerge(target[property], sourceProperty)
+ } else {
+ target[property] = sourceProperty
+ }
+ })
+ return target
+}
+
+/**
+ * @param {HTMLElement} element
+ * @param {string} className
+ */
+export function toggleClass(element, className) {
+ if (!element || !className) {
+ return
+ }
+ let classString = element.className
+ const nameIndex = classString.indexOf(className)
+ if (nameIndex === -1) {
+ classString += '' + className
+ } else {
+ classString =
+ classString.substr(0, nameIndex) +
+ classString.substr(nameIndex + className.length)
+ }
+ element.className = classString
+}
+
+/**
+ * @param {string} type
+ * @returns {Date}
+ */
+export function getTime(type) {
+ if (type === 'start') {
+ return new Date().getTime() - 3600 * 1000 * 24 * 90
+ } else {
+ return new Date(new Date().toDateString())
+ }
+}
+
+/**
+ * @param {Function} func
+ * @param {number} wait
+ * @param {boolean} immediate
+ * @return {*}
+ */
+export function debounce(func, wait, immediate) {
+ let timeout, args, context, timestamp, result
+
+ const later = function() {
+ // 鎹笂涓�娆¤Е鍙戞椂闂撮棿闅�
+ const last = +new Date() - timestamp
+
+ // 涓婃琚寘瑁呭嚱鏁拌璋冪敤鏃堕棿闂撮殧 last 灏忎簬璁惧畾鏃堕棿闂撮殧 wait
+ if (last < wait && last > 0) {
+ timeout = setTimeout(later, wait - last)
+ } else {
+ timeout = null
+ // 濡傛灉璁惧畾涓篿mmediate===true锛屽洜涓哄紑濮嬭竟鐣屽凡缁忚皟鐢ㄨ繃浜嗘澶勬棤闇�璋冪敤
+ if (!immediate) {
+ result = func.apply(context, args)
+ if (!timeout) context = args = null
+ }
+ }
+ }
+
+ return function(...args) {
+ context = this
+ timestamp = +new Date()
+ const callNow = immediate && !timeout
+ // 濡傛灉寤舵椂涓嶅瓨鍦紝閲嶆柊璁惧畾寤舵椂
+ if (!timeout) timeout = setTimeout(later, wait)
+ if (callNow) {
+ result = func.apply(context, args)
+ context = args = null
+ }
+
+ return result
+ }
+}
+
+/**
+ * This is just a simple version of deep copy
+ * Has a lot of edge cases bug
+ * If you want to use a perfect deep copy, use lodash's _.cloneDeep
+ * @param {Object} source
+ * @returns {Object}
+ */
+export function deepClone(source) {
+ if (!source && typeof source !== 'object') {
+ throw new Error('error arguments', 'deepClone')
+ }
+ const targetObj = source.constructor === Array ? [] : {}
+ Object.keys(source).forEach(keys => {
+ if (source[keys] && typeof source[keys] === 'object') {
+ targetObj[keys] = deepClone(source[keys])
+ } else {
+ targetObj[keys] = source[keys]
+ }
+ })
+ return targetObj
+}
+
+/**
+ * @param {Array} arr
+ * @returns {Array}
+ */
+export function uniqueArr(arr) {
+ return Array.from(new Set(arr))
+}
+
+/**
+ * @returns {string}
+ */
+export function createUniqueString() {
+ const timestamp = +new Date() + ''
+ const randomNum = parseInt((1 + Math.random()) * 65536) + ''
+ return (+(randomNum + timestamp)).toString(32)
+}
+
+/**
+ * Check if an element has a class
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ * @returns {boolean}
+ */
+export function hasClass(ele, cls) {
+ return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
+}
+
+/**
+ * Add class to element
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ */
+export function addClass(ele, cls) {
+ if (!hasClass(ele, cls)) ele.className += ' ' + cls
+}
+
+/**
+ * Remove class from element
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ */
+export function removeClass(ele, cls) {
+ if (hasClass(ele, cls)) {
+ const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
+ ele.className = ele.className.replace(reg, ' ')
+ }
+}
--
Gitblit v1.9.3