first
This commit is contained in:
337
examples/components/demo-block.vue
Normal file
337
examples/components/demo-block.vue
Normal file
@@ -0,0 +1,337 @@
|
||||
<template>
|
||||
<div
|
||||
class="demo-block"
|
||||
:class="[blockClass, { 'hover': hovering }]"
|
||||
@mouseenter="hovering = true"
|
||||
@mouseleave="hovering = false">
|
||||
<div class="source">
|
||||
<slot name="source"></slot>
|
||||
</div>
|
||||
<div class="meta" ref="meta">
|
||||
<div class="description" v-if="$slots.default">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="highlight">
|
||||
<slot name="highlight"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="demo-block-control"
|
||||
ref="control"
|
||||
:class="{ 'is-fixed': fixedControl }"
|
||||
@click="isExpanded = !isExpanded">
|
||||
<transition name="arrow-slide">
|
||||
<i :class="[iconClass, { 'hovering': hovering }]"></i>
|
||||
</transition>
|
||||
<transition name="text-slide">
|
||||
<span v-show="hovering">{{ controlText }}</span>
|
||||
</transition>
|
||||
<el-tooltip effect="dark" :content="langConfig['tooltip-text']" placement="right">
|
||||
<transition name="text-slide">
|
||||
<el-button
|
||||
v-show="hovering || isExpanded"
|
||||
size="small"
|
||||
type="text"
|
||||
class="control-button"
|
||||
@click.stop="goCodepen">
|
||||
{{ langConfig['button-text'] }}
|
||||
</el-button>
|
||||
</transition>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.demo-block {
|
||||
border: solid 1px #ebebeb;
|
||||
border-radius: 3px;
|
||||
transition: .2s;
|
||||
|
||||
&.hover {
|
||||
box-shadow: 0 0 8px 0 rgba(232, 237, 250, .6), 0 2px 4px 0 rgba(232, 237, 250, .5);
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: Menlo, Monaco, Consolas, Courier, monospace;
|
||||
}
|
||||
|
||||
.demo-button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.source {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.meta {
|
||||
background-color: #fafafa;
|
||||
border-top: solid 1px #eaeefb;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
transition: height .2s;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
border: solid 1px #ebebeb;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #666;
|
||||
word-break: break-word;
|
||||
margin: 10px;
|
||||
background-color: #fff;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
code {
|
||||
color: #5e6d82;
|
||||
background-color: #e6effb;
|
||||
margin: 0 4px;
|
||||
display: inline-block;
|
||||
padding: 1px 5px;
|
||||
font-size: 12px;
|
||||
border-radius: 3px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.highlight {
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code.hljs {
|
||||
margin: 0;
|
||||
border: none;
|
||||
max-height: none;
|
||||
border-radius: 0;
|
||||
|
||||
&::before {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.demo-block-control {
|
||||
border-top: solid 1px #eaeefb;
|
||||
height: 44px;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
text-align: center;
|
||||
margin-top: -1px;
|
||||
color: #d3dce6;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&.is-fixed {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 868px;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
line-height: 44px;
|
||||
transition: .3s;
|
||||
&.hovering {
|
||||
transform: translateX(-40px);
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
position: absolute;
|
||||
transform: translateX(-30px);
|
||||
font-size: 14px;
|
||||
line-height: 44px;
|
||||
transition: .3s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #409EFF;
|
||||
background-color: #f9fafc;
|
||||
}
|
||||
|
||||
& .text-slide-enter,
|
||||
& .text-slide-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(10px);
|
||||
}
|
||||
|
||||
.control-button {
|
||||
line-height: 26px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 14px;
|
||||
padding-left: 5px;
|
||||
padding-right: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/babel">
|
||||
import compoLang from '../i18n/component.json';
|
||||
import Element from 'main/index.js';
|
||||
import { stripScript, stripStyle, stripTemplate } from '../util';
|
||||
const { version } = Element;
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
codepen: {
|
||||
script: '',
|
||||
html: '',
|
||||
style: ''
|
||||
},
|
||||
hovering: false,
|
||||
isExpanded: false,
|
||||
fixedControl: false,
|
||||
scrollParent: null
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
goCodepen() {
|
||||
// since 2.6.2 use code rather than jsfiddle https://blog.codepen.io/documentation/api/prefill/
|
||||
const { script, html, style } = this.codepen;
|
||||
const resourcesTpl = '<scr' + 'ipt src="//unpkg.com/vue/dist/vue.js"></scr' + 'ipt>' +
|
||||
'\n<scr' + `ipt src="//unpkg.com/element-ui@${ version }/lib/index.js"></scr` + 'ipt>';
|
||||
let jsTpl = (script || '').replace(/export default/, 'var Main =').trim();
|
||||
let htmlTpl = `${resourcesTpl}\n<div id="app">\n${html.trim()}\n</div>`;
|
||||
let cssTpl = `@import url("//unpkg.com/element-ui@${ version }/lib/theme-chalk/index.css");\n${(style || '').trim()}\n`;
|
||||
jsTpl = jsTpl
|
||||
? jsTpl + '\nvar Ctor = Vue.extend(Main)\nnew Ctor().$mount(\'#app\')'
|
||||
: 'new Vue().$mount(\'#app\')';
|
||||
const data = {
|
||||
js: jsTpl,
|
||||
css: cssTpl,
|
||||
html: htmlTpl
|
||||
};
|
||||
const form = document.getElementById('fiddle-form') || document.createElement('form');
|
||||
while (form.firstChild) {
|
||||
form.removeChild(form.firstChild);
|
||||
}
|
||||
form.method = 'POST';
|
||||
form.action = 'https://codepen.io/pen/define/';
|
||||
form.target = '_blank';
|
||||
form.style.display = 'none';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('name', 'data');
|
||||
input.setAttribute('type', 'hidden');
|
||||
input.setAttribute('value', JSON.stringify(data));
|
||||
|
||||
form.appendChild(input);
|
||||
document.body.appendChild(form);
|
||||
|
||||
form.submit();
|
||||
},
|
||||
|
||||
scrollHandler() {
|
||||
const { top, bottom, left } = this.$refs.meta.getBoundingClientRect();
|
||||
this.fixedControl = bottom > document.documentElement.clientHeight &&
|
||||
top + 44 <= document.documentElement.clientHeight;
|
||||
this.$refs.control.style.left = this.fixedControl ? `${ left }px` : '0';
|
||||
},
|
||||
|
||||
removeScrollHandler() {
|
||||
this.scrollParent && this.scrollParent.removeEventListener('scroll', this.scrollHandler);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
lang() {
|
||||
return this.$route.path.split('/')[1];
|
||||
},
|
||||
|
||||
langConfig() {
|
||||
return compoLang.filter(config => config.lang === this.lang)[0]['demo-block'];
|
||||
},
|
||||
|
||||
blockClass() {
|
||||
return `demo-${ this.lang } demo-${ this.$router.currentRoute.path.split('/').pop() }`;
|
||||
},
|
||||
|
||||
iconClass() {
|
||||
return this.isExpanded ? 'el-icon-caret-top' : 'el-icon-caret-bottom';
|
||||
},
|
||||
|
||||
controlText() {
|
||||
return this.isExpanded ? this.langConfig['hide-text'] : this.langConfig['show-text'];
|
||||
},
|
||||
|
||||
codeArea() {
|
||||
return this.$el.getElementsByClassName('meta')[0];
|
||||
},
|
||||
|
||||
codeAreaHeight() {
|
||||
if (this.$el.getElementsByClassName('description').length > 0) {
|
||||
return this.$el.getElementsByClassName('description')[0].clientHeight +
|
||||
this.$el.getElementsByClassName('highlight')[0].clientHeight + 20;
|
||||
}
|
||||
return this.$el.getElementsByClassName('highlight')[0].clientHeight;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
isExpanded(val) {
|
||||
this.codeArea.style.height = val ? `${ this.codeAreaHeight + 1 }px` : '0';
|
||||
if (!val) {
|
||||
this.fixedControl = false;
|
||||
this.$refs.control.style.left = '0';
|
||||
this.removeScrollHandler();
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.scrollParent = document.querySelector('.page-component__scroll > .el-scrollbar__wrap');
|
||||
this.scrollParent && this.scrollParent.addEventListener('scroll', this.scrollHandler);
|
||||
this.scrollHandler();
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
const highlight = this.$slots.highlight;
|
||||
if (highlight && highlight[0]) {
|
||||
let code = '';
|
||||
let cur = highlight[0];
|
||||
if (cur.tag === 'pre' && (cur.children && cur.children[0])) {
|
||||
cur = cur.children[0];
|
||||
if (cur.tag === 'code') {
|
||||
code = cur.children[0].text;
|
||||
}
|
||||
}
|
||||
if (code) {
|
||||
this.codepen.html = stripTemplate(code);
|
||||
this.codepen.script = stripScript(code);
|
||||
this.codepen.style = stripStyle(code);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
let highlight = this.$el.getElementsByClassName('highlight')[0];
|
||||
if (this.$el.getElementsByClassName('description').length === 0) {
|
||||
highlight.style.width = '100%';
|
||||
highlight.borderRight = 'none';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.removeScrollHandler();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
121
examples/components/footer-nav.vue
Normal file
121
examples/components/footer-nav.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="footer-nav">
|
||||
<span
|
||||
v-if="leftNav"
|
||||
class="footer-nav-link footer-nav-left"
|
||||
@click="handleNavClick('prev')">
|
||||
<i class="el-icon-arrow-left"></i>
|
||||
{{ leftNav.title || leftNav.name }}
|
||||
</span>
|
||||
<span
|
||||
v-if="rightNav"
|
||||
class="footer-nav-link footer-nav-right"
|
||||
@click="handleNavClick('next')">
|
||||
{{ rightNav.title || rightNav.name }}
|
||||
<i class="el-icon-arrow-right"></i>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.footer-nav {
|
||||
padding: 40px 0;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
& i {
|
||||
transition: .3s;
|
||||
color: #999;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-nav-link {
|
||||
cursor: pointer;
|
||||
transition: .3s;
|
||||
|
||||
&:hover {
|
||||
color: #409EFF;
|
||||
|
||||
& i {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-nav-left {
|
||||
float: left;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.footer-nav-right {
|
||||
float: right;
|
||||
margin-right: -4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import navConfig from '../nav.config.json';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentComponent: null,
|
||||
nav: [],
|
||||
currentIndex: -1,
|
||||
leftNav: null,
|
||||
rightNav: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
lang() {
|
||||
return this.$route.meta.lang;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'$route.path'() {
|
||||
this.setNav();
|
||||
this.updateNav();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
setNav() {
|
||||
let nav = navConfig[this.lang];
|
||||
this.nav = [nav[0]].concat(nav[3].children);
|
||||
nav[4].groups.map(group => group.list).forEach(list => {
|
||||
this.nav = this.nav.concat(list);
|
||||
});
|
||||
},
|
||||
|
||||
updateNav() {
|
||||
this.currentComponent = '/' + this.$route.path.split('/')[3];
|
||||
for (let i = 0, len = this.nav.length; i < len; i++) {
|
||||
if (this.nav[i].path === this.currentComponent) {
|
||||
this.currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.leftNav = this.nav[this.currentIndex - 1];
|
||||
this.rightNav = this.nav[this.currentIndex + 1];
|
||||
},
|
||||
|
||||
handleNavClick(direction) {
|
||||
this.$router.push(`/${ this.lang }/component${ direction === 'prev' ? this.leftNav.path : this.rightNav.path }`);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.setNav();
|
||||
this.updateNav();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
188
examples/components/footer.vue
Normal file
188
examples/components/footer.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-main">
|
||||
<h4>{{ langConfig.links }}</h4>
|
||||
<a href="https://github.com/ElemeFE/element" class="footer-main-link" target="_blank">{{ langConfig.repo }}</a>
|
||||
<a href="https://github.com/ElemeFE/element/releases" class="footer-main-link" target="_blank">{{ langConfig.changelog }}</a>
|
||||
<a href="https://github.com/ElemeFE/element/blob/dev/FAQ.md" class="footer-main-link" target="_blank">{{ langConfig.faq }}</a>
|
||||
<a href="https://github.com/ElementUI/element-starter" class="footer-main-link" target="_blank">{{ langConfig.starter }}</a>
|
||||
<a :href="'/#/' + lang + '/component/custom-theme'" class="footer-main-link" target="_blank">{{ langConfig.theme }}</a>
|
||||
<a href="https://github.com/elemefe/element-react" class="footer-main-link" target="_blank">Element-React</a>
|
||||
<a href="https://github.com/ElemeFE/element-angular" class="footer-main-link" target="_blank">Element-Angular</a>
|
||||
</div>
|
||||
<div class="footer-main">
|
||||
<h4>{{ langConfig.community }}</h4>
|
||||
<a :href="gitterLink" class="footer-main-link" target="_blank">{{ langConfig.gitter }}</a>
|
||||
<a href="https://github.com/ElemeFE/element/issues" class="footer-main-link" target="_blank">{{ langConfig.feedback }}</a>
|
||||
<a :href="`https://github.com/ElemeFE/element/blob/master/.github/CONTRIBUTING.${ lang }.md`" class="footer-main-link" target="_blank">{{ langConfig.contribution }}</a>
|
||||
<a href="https://segmentfault.com/t/element-ui" class="footer-main-link" target="_blank">SegmentFault</a>
|
||||
<a href="https://github.com/ElementUI/awesome-element" class="footer-main-link" target="_blank">Awesome Element</a>
|
||||
</div>
|
||||
<div class="footer-social">
|
||||
<p class="footer-social-title">Element {{ version && version.slice(0, 3) }} Fullerene</p>
|
||||
<el-popover
|
||||
ref="weixin"
|
||||
placement="top"
|
||||
width="120"
|
||||
popper-class="footer-popover"
|
||||
trigger="hover">
|
||||
<div class="footer-popover-title">{{ langConfig.eleme }} UED</div>
|
||||
<img src="../assets/images/qrcode.png" alt="">
|
||||
</el-popover>
|
||||
<i class="doc-icon-weixin elementdoc" v-popover:weixin></i>
|
||||
<a href="https://github.com/elemefe" target="_blank">
|
||||
<i class="doc-icon-github elementdoc"></i>
|
||||
</a>
|
||||
<a :href="gitterLink" target="_blank">
|
||||
<i class="doc-icon-gitter elementdoc"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.footer {
|
||||
background-color: #F7FBFD;
|
||||
width: 100%;
|
||||
padding: 40px 150px;
|
||||
margin-top: -340px;
|
||||
box-sizing: border-box;
|
||||
height: 340px;
|
||||
|
||||
.container {
|
||||
box-sizing: border-box;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer-main {
|
||||
font-size: 0;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-right: 110px;
|
||||
|
||||
h4 {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
line-height: 1;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.footer-main-link {
|
||||
display: block;
|
||||
margin: 0;
|
||||
line-height: 2;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
|
||||
&:hover {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-social {
|
||||
float: right;
|
||||
text-align: right;
|
||||
|
||||
.footer-social-title {
|
||||
color: #666;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
margin: 0 0 20px 0;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.elementdoc {
|
||||
transition: .3s;
|
||||
display: inline-block;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
color: #c8d6e8;
|
||||
background-color: transparent;
|
||||
size: 32px;
|
||||
font-size: 32px;
|
||||
vertical-align: middle;
|
||||
margin-right: 20px;
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
color: #8D99AB;
|
||||
}
|
||||
}
|
||||
|
||||
.doc-icon-gitter {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-popover.footer-popover {
|
||||
padding: 0;
|
||||
min-width: 120px;
|
||||
line-height: normal;
|
||||
box-shadow: 0 0 11px 0 rgba(174, 187, 211, 0.24);
|
||||
|
||||
.footer-popover-title {
|
||||
border-bottom: solid 1px #eaeefb;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
color: #99a9bf;
|
||||
background-color: #f8f9fe;
|
||||
}
|
||||
|
||||
img {
|
||||
size: 100px;
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 1140px) {
|
||||
.footer {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.footer-social {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.footer {
|
||||
.footer-main {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/babel">
|
||||
import compoLang from '../i18n/component.json';
|
||||
import Element from 'main/index.js';
|
||||
const { version } = Element;
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
version
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
lang() {
|
||||
return this.$route.path.split('/')[1] || 'zh-CN';
|
||||
},
|
||||
|
||||
langConfig() {
|
||||
return compoLang.filter(config => config.lang === this.lang)[0]['footer'];
|
||||
},
|
||||
|
||||
gitterLink() {
|
||||
return this.lang === 'zh-CN' ? 'https://gitter.im/ElemeFE/element' : 'https://gitter.im/element-en/Lobby';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
524
examples/components/header.vue
Normal file
524
examples/components/header.vue
Normal file
@@ -0,0 +1,524 @@
|
||||
<style lang="scss" scoped>
|
||||
.headerWrapper {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
#v3-banner {
|
||||
background-color: #409EFF;
|
||||
min-height: 30px;
|
||||
padding: 5px 60px;
|
||||
z-index: 19;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
#v3-banner a {
|
||||
color: #FFF;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 80px;
|
||||
background-color: #fff;
|
||||
color: #fff;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
line-height: 80px;
|
||||
z-index: 100;
|
||||
position: relative;
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #DCDFE6;
|
||||
}
|
||||
|
||||
.nav-lang-spe {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
float: left;
|
||||
font-size: 32px;
|
||||
font-weight: normal;
|
||||
|
||||
a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
width: 34px;
|
||||
height: 18px;
|
||||
border: 1px solid rgba(255, 255, 255, .5);
|
||||
text-align: center;
|
||||
line-height: 18px;
|
||||
vertical-align: middle;
|
||||
margin-left: 10px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
float: right;
|
||||
height: 100%;
|
||||
line-height: 80px;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
&::before, &::after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
&::after {
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-gap {
|
||||
position: relative;
|
||||
width: 1px;
|
||||
height: 80px;
|
||||
padding: 0 20px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: calc(50% - 8px);
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background: #ebebeb;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-logo,
|
||||
.nav-logo-small {
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
.nav-logo-small {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin: 0;
|
||||
float: left;
|
||||
list-style: none;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&.nav-algolia-search {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.lang-item,
|
||||
&:last-child {
|
||||
cursor: default;
|
||||
margin-left: 34px;
|
||||
|
||||
span {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.nav-lang {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
color: #888;
|
||||
|
||||
&:hover {
|
||||
color: #409EFF;
|
||||
}
|
||||
&.active {
|
||||
font-weight: bold;
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #1989FA;
|
||||
opacity: 0.5;
|
||||
display: block;
|
||||
padding: 0 22px;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.active::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: calc(50% - 15px);
|
||||
width: 30px;
|
||||
height: 2px;
|
||||
background: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-dropdown {
|
||||
margin-bottom: 6px;
|
||||
padding-left: 18px;
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
color: #888;
|
||||
line-height: 40px;
|
||||
transition: .2s;
|
||||
padding-bottom: 6px;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
transition: .2s;
|
||||
font-size: 12px;
|
||||
color: #979797;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.is-active {
|
||||
span, i {
|
||||
color: #409EFF;
|
||||
}
|
||||
i {
|
||||
transform: rotateZ(180deg) translateY(3px);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
span, i {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-dropdown-list {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 850px) {
|
||||
.header {
|
||||
.nav-logo {
|
||||
display: none;
|
||||
}
|
||||
.nav-logo-small {
|
||||
display: inline-block;
|
||||
}
|
||||
.nav-item {
|
||||
margin-left: 6px;
|
||||
|
||||
&.lang-item,
|
||||
&:last-child {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
.nav-theme-switch, .nav-algolia-search {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.header {
|
||||
.container {
|
||||
padding: 0 12px;
|
||||
}
|
||||
.nav-item {
|
||||
a {
|
||||
font-size: 12px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&.lang-item {
|
||||
height: 100%;
|
||||
|
||||
.nav-lang {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.nav-dropdown {
|
||||
padding: 0;
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.nav-gap {
|
||||
padding: 0 8px;
|
||||
}
|
||||
.nav-versions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="headerWrapper">
|
||||
<div id="v3-banner" v-if="isHome">
|
||||
<template v-if="lang === 'zh-CN'">
|
||||
您正在浏览基于 Vue 2.x 的 Element UI 文档;
|
||||
<a href="https://element-plus.org/#/zh-CN">点击这里</a> 查看 Vue 3.x 的升级版本
|
||||
</template>
|
||||
<template v-else>
|
||||
You’re browsing the documentation of Element UI for Vue 2.x version.
|
||||
<a href="https://element-plus.org">Click here</a> for Vue 3.x version
|
||||
</template>
|
||||
</div>
|
||||
<header class="header" ref="header">
|
||||
<div class="container">
|
||||
<h1><router-link :to="`/${ lang }`">
|
||||
<!-- logo -->
|
||||
<slot>
|
||||
<img
|
||||
src="../assets/images/element-logo.svg"
|
||||
alt="element-logo"
|
||||
class="nav-logo">
|
||||
<img
|
||||
src="../assets/images/element-logo-small.svg"
|
||||
alt="element-logo"
|
||||
class="nav-logo-small">
|
||||
</slot>
|
||||
|
||||
</router-link></h1>
|
||||
|
||||
<!-- nav -->
|
||||
<ul class="nav">
|
||||
<li class="nav-item nav-algolia-search" v-show="isComponentPage">
|
||||
<algolia-search></algolia-search>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
active-class="active"
|
||||
:to="`/${ lang }/guide`">{{ langConfig.guide }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
active-class="active"
|
||||
:to="`/${ lang }/component`">{{ langConfig.components }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li
|
||||
class="nav-item nav-item-theme"
|
||||
>
|
||||
<router-link
|
||||
active-class="active"
|
||||
:to="`/${ lang }/theme`">{{ langConfig.theme }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
active-class="active"
|
||||
:to="`/${ lang }/resource`"
|
||||
exact>{{ langConfig.resource }}
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
<!-- gap -->
|
||||
<li class="nav-item" v-show="isComponentPage">
|
||||
<div class="nav-gap"></div>
|
||||
</li>
|
||||
|
||||
<!-- 版本选择器 -->
|
||||
<li class="nav-item nav-versions" v-show="isComponentPage">
|
||||
<el-dropdown
|
||||
trigger="click"
|
||||
class="nav-dropdown"
|
||||
:class="{ 'is-active': verDropdownVisible }">
|
||||
<span>
|
||||
{{ version }}
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</span>
|
||||
<el-dropdown-menu
|
||||
slot="dropdown"
|
||||
class="nav-dropdown-list"
|
||||
@input="handleVerDropdownToggle">
|
||||
<el-dropdown-item
|
||||
v-for="item in Object.keys(versions)"
|
||||
:key="item"
|
||||
@click.native="switchVersion(item)">
|
||||
{{ item }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</li>
|
||||
|
||||
<!-- 语言选择器 -->
|
||||
<li class="nav-item lang-item">
|
||||
<el-dropdown
|
||||
trigger="click"
|
||||
class="nav-dropdown nav-lang"
|
||||
:class="{ 'is-active': langDropdownVisible }">
|
||||
<span>
|
||||
{{ displayedLang }}
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</span>
|
||||
<el-dropdown-menu
|
||||
slot="dropdown"
|
||||
class="nav-dropdown-list"
|
||||
@input="handleLangDropdownToggle">
|
||||
<el-dropdown-item
|
||||
v-for="(value, key) in langs"
|
||||
:key="key"
|
||||
@click.native="switchLang(key)">
|
||||
{{ value }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ThemePicker from './theme-picker.vue';
|
||||
import AlgoliaSearch from './search.vue';
|
||||
import compoLang from '../i18n/component.json';
|
||||
import Element from 'main/index.js';
|
||||
import themeLoader from './theme/loader';
|
||||
import { getTestEle } from './theme/loader/api.js';
|
||||
import bus from '../bus';
|
||||
import { ACTION_USER_CONFIG_UPDATE } from './theme/constant.js';
|
||||
|
||||
const { version } = Element;
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
active: '',
|
||||
versions: [],
|
||||
version,
|
||||
verDropdownVisible: true,
|
||||
langDropdownVisible: true,
|
||||
langs: {
|
||||
'zh-CN': '中文',
|
||||
'en-US': 'English',
|
||||
'es': 'Español',
|
||||
'fr-FR': 'Français'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
mixins: [themeLoader],
|
||||
|
||||
components: {
|
||||
ThemePicker,
|
||||
AlgoliaSearch
|
||||
},
|
||||
|
||||
computed: {
|
||||
lang() {
|
||||
return this.$route.path.split('/')[1] || 'zh-CN';
|
||||
},
|
||||
displayedLang() {
|
||||
return this.langs[this.lang] || '中文';
|
||||
},
|
||||
langConfig() {
|
||||
return compoLang.filter(config => config.lang === this.lang)[0]['header'];
|
||||
},
|
||||
isComponentPage() {
|
||||
return /^component/.test(this.$route.name);
|
||||
},
|
||||
isHome() {
|
||||
return /^home/.test(this.$route.name);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
getTestEle()
|
||||
.then(() => {
|
||||
this.$isEle = true;
|
||||
ga('send', 'event', 'DocView', 'Ele', 'Inner');
|
||||
})
|
||||
.catch((err) => {
|
||||
ga('send', 'event', 'DocView', 'Ele', 'Outer');
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
const testInnerImg = new Image();
|
||||
testInnerImg.onload = () => {
|
||||
this.$isEle = true;
|
||||
ga('send', 'event', 'DocView', 'Ali', 'Inner');
|
||||
};
|
||||
testInnerImg.onerror = (err) => {
|
||||
ga('send', 'event', 'DocView', 'Ali', 'Outer');
|
||||
console.error(err);
|
||||
};
|
||||
testInnerImg.src = `https://private-alipayobjects.alipay.com/alipay-rmsdeploy-image/rmsportal/VmvVUItLdPNqKlNGuRHi.png?t=${Date.now()}`;
|
||||
},
|
||||
methods: {
|
||||
switchVersion(version) {
|
||||
if (version === this.version) return;
|
||||
location.href = `${ location.origin }/${ this.versions[version] }/${ location.hash } `;
|
||||
},
|
||||
|
||||
switchLang(targetLang) {
|
||||
if (this.lang === targetLang) return;
|
||||
localStorage.setItem('ELEMENT_LANGUAGE', targetLang);
|
||||
this.$router.push(this.$route.path.replace(this.lang, targetLang));
|
||||
},
|
||||
|
||||
handleVerDropdownToggle(visible) {
|
||||
this.verDropdownVisible = visible;
|
||||
},
|
||||
|
||||
handleLangDropdownToggle(visible) {
|
||||
this.langDropdownVisible = visible;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = _ => {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
const versions = JSON.parse(xhr.responseText);
|
||||
this.versions = Object.keys(versions).reduce((prev, next) => {
|
||||
prev[next] = versions[next];
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
};
|
||||
xhr.open('GET', '/versions.json');
|
||||
xhr.send();
|
||||
let primaryLast = '#409EFF';
|
||||
bus.$on(ACTION_USER_CONFIG_UPDATE, (val) => {
|
||||
let primaryColor = val.global['$--color-primary'];
|
||||
if (!primaryColor) primaryColor = '#409EFF';
|
||||
const base64svg = 'data:image/svg+xml;base64,';
|
||||
const imgSet = document.querySelectorAll('h1 img');
|
||||
imgSet.forEach((img) => {
|
||||
img.src = `${base64svg}${window.btoa(window.atob(img.src.replace(base64svg, '')).replace(primaryLast, primaryColor))}`;
|
||||
});
|
||||
primaryLast = primaryColor;
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
222
examples/components/search.vue
Normal file
222
examples/components/search.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<el-autocomplete
|
||||
v-model="query"
|
||||
size="small"
|
||||
:popper-class="`algolia-search${ isEmpty ? ' is-empty' : '' }`"
|
||||
:fetch-suggestions="querySearch"
|
||||
:placeholder="placeholder"
|
||||
:trigger-on-focus="false"
|
||||
@select="handleSelect"
|
||||
highlight-first-item>
|
||||
<template slot-scope="props">
|
||||
<p class="algolia-search-title" v-if="props.item.title">
|
||||
<span v-html="props.item.highlightedCompo"></span>
|
||||
<span class="algolia-search-separator"></span>
|
||||
<span v-html="props.item.title"></span>
|
||||
</p>
|
||||
<p
|
||||
class="algolia-search-content"
|
||||
v-if="props.item.content"
|
||||
v-html="props.item.content"></p>
|
||||
<a
|
||||
class="algolia-search-link"
|
||||
v-if="props.item.img"
|
||||
target="_blank"
|
||||
href="https://www.algolia.com/docsearch">
|
||||
<img
|
||||
class="algolia-search-logo"
|
||||
src="../assets/images/search-by-algolia.svg"
|
||||
alt="algolia-logo">
|
||||
</a>
|
||||
<p
|
||||
class="algolia-search-empty"
|
||||
v-if="props.item.isEmpty">{{ emptyText }}</p>
|
||||
</template>
|
||||
</el-autocomplete>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.algolia-search {
|
||||
width: 450px !important;
|
||||
|
||||
&.is-empty {
|
||||
.el-autocomplete-suggestion__list {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.el-autocomplete-suggestion__list {
|
||||
position: static !important;
|
||||
padding-bottom: 28px;
|
||||
}
|
||||
|
||||
li {
|
||||
border-bottom: solid 1px #ebebeb;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.algolia-highlight {
|
||||
color: #409EFF;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.algolia-search-title {
|
||||
font-size: 14px;
|
||||
margin: 6px 0;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.algolia-search-separator {
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.algolia-search-content {
|
||||
font-size: 12px;
|
||||
margin: 6px 0;
|
||||
line-height: 2.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.algolia-search-link {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding-right: 20px;
|
||||
background-color: #e4e7ed;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
text-align: right;
|
||||
|
||||
&:hover {
|
||||
background-color: #e4e7ed;
|
||||
}
|
||||
|
||||
img {
|
||||
display: inline-block;
|
||||
height: 17px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.algolia-search-empty {
|
||||
margin: 5px 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import algoliasearch from 'algoliasearch';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
index: null,
|
||||
query: '',
|
||||
isEmpty: false,
|
||||
langs: {
|
||||
'zh-CN': {
|
||||
search: '搜索文档',
|
||||
empty: '无匹配结果',
|
||||
index: 'zh'
|
||||
},
|
||||
'en-US': {
|
||||
search: 'Search',
|
||||
empty: 'No results',
|
||||
index: 'en'
|
||||
},
|
||||
'es': {
|
||||
search: 'Buscar',
|
||||
empty: 'No hay datos que coincidan',
|
||||
index: 'es'
|
||||
},
|
||||
'fr-FR': {
|
||||
search: 'Rechercher',
|
||||
empty: 'Aucun résultat',
|
||||
index: 'fr'
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
lang() {
|
||||
return this.$route.meta.lang;
|
||||
},
|
||||
|
||||
placeholder() {
|
||||
return this.lang ? this.langs[this.lang].search : '';
|
||||
},
|
||||
|
||||
emptyText() {
|
||||
return this.lang ? this.langs[this.lang].empty : '';
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
lang() {
|
||||
this.initIndex();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
initIndex() {
|
||||
const client = algoliasearch('4C63BTGP6S', '0729c3c7f4dc8db7395ad0b19c0748d2');
|
||||
this.index = client.initIndex(`element-${ this.lang ? this.langs[this.lang].index : 'zh' }`);
|
||||
},
|
||||
|
||||
querySearch(query, cb) {
|
||||
if (!query) return;
|
||||
this.index.search({ query, hitsPerPage: 6 }, (err, res) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
if (res.hits.length > 0) {
|
||||
this.isEmpty = false;
|
||||
cb(res.hits.map(hit => {
|
||||
let content = hit._highlightResult.content.value.replace(/\s+/g, ' ');
|
||||
const highlightStart = content.indexOf('<span class="algolia-highlight">');
|
||||
if (highlightStart > -1) {
|
||||
const startEllipsis = highlightStart - 15 > 0;
|
||||
content = (startEllipsis ? '...' : '') +
|
||||
content.slice(Math.max(0, highlightStart - 15), content.length);
|
||||
} else if (content.indexOf('|') > -1) {
|
||||
content = '';
|
||||
}
|
||||
return {
|
||||
anchor: hit.anchor,
|
||||
component: hit.component,
|
||||
highlightedCompo: hit._highlightResult.component.value,
|
||||
title: hit._highlightResult.title.value,
|
||||
content
|
||||
};
|
||||
}).concat({ img: true }));
|
||||
} else {
|
||||
this.isEmpty = true;
|
||||
cb([{ isEmpty: true }]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleSelect(val) {
|
||||
if (val.img || val.isEmpty) return;
|
||||
const component = val.component || '';
|
||||
const anchor = val.anchor;
|
||||
this.$router.push(`/${ this.lang }/component/${ component }${ anchor ? `#${ anchor }` : '' }`);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.initIndex();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
274
examples/components/side-nav.vue
Normal file
274
examples/components/side-nav.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<style lang="scss">
|
||||
.side-nav {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-right: 30px;
|
||||
transition: opacity .3s;
|
||||
&.is-fade {
|
||||
transition: opacity 3s;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> ul > .nav-item > a {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
> ul > .nav-item:nth-child(-n + 4) > a {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
a {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
line-height: 40px;
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
position: relative;
|
||||
transition: .15s ease-out;
|
||||
font-weight: bold;
|
||||
|
||||
&.active {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
a {
|
||||
display: block;
|
||||
height: 40px;
|
||||
color: #444;
|
||||
line-height: 40px;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: normal;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.sponsors {
|
||||
& > .sub-nav {
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
& > a {
|
||||
color: #777;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: inline-block;
|
||||
|
||||
a {
|
||||
height: auto;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 8px 12px 12px 0;
|
||||
|
||||
img {
|
||||
width: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child a img {
|
||||
width: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-group__title {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
line-height: 26px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
#code-sponsor-widget {
|
||||
margin: 0 0 0 -20px;
|
||||
}
|
||||
}
|
||||
.nav-dropdown-list {
|
||||
width: 120px;
|
||||
margin-top: -8px;
|
||||
li {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div
|
||||
class="side-nav"
|
||||
@mouseenter="isFade = false"
|
||||
:class="{ 'is-fade': isFade }"
|
||||
:style="navStyle">
|
||||
<ul>
|
||||
<li
|
||||
class="nav-item"
|
||||
v-for="(item, key) in data"
|
||||
:key="key">
|
||||
<a v-if="!item.path && !item.href" @click="expandMenu">{{item.name}}</a>
|
||||
<a v-if="item.href" :href="item.href" target="_blank">{{item.name}}</a>
|
||||
<router-link
|
||||
v-if="item.path"
|
||||
active-class="active"
|
||||
:to="base + item.path"
|
||||
exact
|
||||
v-text="item.title || item.name">
|
||||
</router-link>
|
||||
<ul class="pure-menu-list sub-nav" v-if="item.children">
|
||||
<li
|
||||
class="nav-item"
|
||||
v-for="(navItem, key) in item.children"
|
||||
:key="key">
|
||||
<router-link
|
||||
class=""
|
||||
active-class="active"
|
||||
:to="base + navItem.path"
|
||||
exact
|
||||
v-text="navItem.title || navItem.name">
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<template v-if="item.groups">
|
||||
<div
|
||||
class="nav-group"
|
||||
v-for="(group, key) in item.groups"
|
||||
:key="key"
|
||||
>
|
||||
<div class="nav-group__title" @click="expandMenu">{{group.groupName}}</div>
|
||||
<ul class="pure-menu-list">
|
||||
<li
|
||||
class="nav-item"
|
||||
v-for="(navItem, key) in group.list"
|
||||
v-show="!navItem.disabled"
|
||||
:key="key">
|
||||
<router-link
|
||||
active-class="active"
|
||||
:to="base + navItem.path"
|
||||
exact
|
||||
v-text="navItem.title"></router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</li>
|
||||
</ul>
|
||||
<!--<div id="code-sponsor-widget"></div>-->
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import bus from '../bus';
|
||||
import compoLang from '../i18n/component.json';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: Array,
|
||||
base: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
highlights: [],
|
||||
navState: [],
|
||||
isSmallScreen: false,
|
||||
isFade: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
'$route.path'() {
|
||||
this.handlePathChange();
|
||||
},
|
||||
isFade(val) {
|
||||
bus.$emit('navFade', val);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
navStyle() {
|
||||
const style = {};
|
||||
if (this.isSmallScreen) {
|
||||
style.paddingBottom = '60px';
|
||||
}
|
||||
style.opacity = this.isFade ? '0.5' : '1';
|
||||
return style;
|
||||
},
|
||||
lang() {
|
||||
return this.$route.meta.lang;
|
||||
},
|
||||
langConfig() {
|
||||
return compoLang.filter(config => config.lang === this.lang)[0]['nav'];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleResize() {
|
||||
this.isSmallScreen = document.documentElement.clientWidth < 768;
|
||||
this.handlePathChange();
|
||||
},
|
||||
handlePathChange() {
|
||||
if (!this.isSmallScreen) {
|
||||
this.expandAllMenu();
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.hideAllMenu();
|
||||
let activeAnchor = this.$el.querySelector('a.active');
|
||||
let ul = activeAnchor.parentNode;
|
||||
while (ul.tagName !== 'UL') {
|
||||
ul = ul.parentNode;
|
||||
}
|
||||
ul.style.height = 'auto';
|
||||
});
|
||||
},
|
||||
hideAllMenu() {
|
||||
[].forEach.call(this.$el.querySelectorAll('.pure-menu-list'), ul => {
|
||||
ul.style.height = '0';
|
||||
});
|
||||
},
|
||||
expandAllMenu() {
|
||||
[].forEach.call(this.$el.querySelectorAll('.pure-menu-list'), ul => {
|
||||
ul.style.height = 'auto';
|
||||
});
|
||||
},
|
||||
expandMenu(event) {
|
||||
if (!this.isSmallScreen) return;
|
||||
let target = event.currentTarget;
|
||||
if (!target.nextElementSibling || target.nextElementSibling.tagName !== 'UL') return;
|
||||
this.hideAllMenu();
|
||||
event.currentTarget.nextElementSibling.style.height = 'auto';
|
||||
}
|
||||
},
|
||||
created() {
|
||||
bus.$on('fadeNav', () => {
|
||||
this.isFade = true;
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.handleResize();
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
153
examples/components/theme-configurator/action.vue
Normal file
153
examples/components/theme-configurator/action.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div class="configurator-action">
|
||||
<div class="action-group">
|
||||
<el-tooltip :content="getActionDisplayName('undo')">
|
||||
<img
|
||||
src="../../assets/images/icon-undo.svg"
|
||||
@click="onUndo"
|
||||
:class="{ 'active': userConfigHistory.length > 0 }"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="getActionDisplayName('redo')">
|
||||
<img
|
||||
src="../../assets/images/icon-redo.svg"
|
||||
@click="onRedo"
|
||||
:class="{ 'active': userConfigRedoHistory.length > 0 }"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<div class="button-group">
|
||||
<el-button
|
||||
class="reset"
|
||||
type="primary"
|
||||
round
|
||||
size="mini"
|
||||
:disabled="isOfficial"
|
||||
@click="onReset"
|
||||
>
|
||||
{{getActionDisplayName('reset-theme')}}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="download"
|
||||
type="primary"
|
||||
round
|
||||
size="mini"
|
||||
:disabled="downloadDisabled"
|
||||
@click="onDownload"
|
||||
>
|
||||
{{getActionDisplayName('download-theme')}}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-select v-model="selectedComponent" class="selector">
|
||||
<el-option
|
||||
v-for="item in selectOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.configurator-action {
|
||||
padding: 15px 18px 0;
|
||||
.action-group {
|
||||
padding: 5px 0;
|
||||
img {
|
||||
cursor: not-allowed;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 7px 0;
|
||||
margin-left: 5px;
|
||||
opacity: .5;
|
||||
&.active {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.button-group {
|
||||
float: right;
|
||||
.el-button {
|
||||
padding: 6px 15px;
|
||||
&.is-disabled {
|
||||
color: #C0C4CC;
|
||||
background-color: #fff;
|
||||
border-color: #EBEEF5;
|
||||
}
|
||||
}
|
||||
.reset {
|
||||
background: #E6F1FC;
|
||||
color: #1989FA;
|
||||
border-color: #A2CFFC;
|
||||
}
|
||||
.download {
|
||||
background: #1989FA;
|
||||
color: #FFF;
|
||||
border-color: #1989FA
|
||||
}
|
||||
}
|
||||
}
|
||||
.selector {
|
||||
width: 100%;
|
||||
input {
|
||||
background: #f5f7fa;
|
||||
border: none;
|
||||
font-size: 18px;
|
||||
padding-left: 0;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
.line {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
border-bottom: 1px solid #DCDFE6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { getActionDisplayName } from './utils/utils';
|
||||
export default {
|
||||
props: {
|
||||
selectOptions: Array,
|
||||
userConfigHistory: Array,
|
||||
userConfigRedoHistory: Array,
|
||||
isOfficial: Boolean,
|
||||
onUndo: Function,
|
||||
onRedo: Function
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedComponent: 'color',
|
||||
downloadDisabled: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
selectedComponent: {
|
||||
handler(val) {
|
||||
this.$emit('select', val);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getActionDisplayName(key) {
|
||||
return getActionDisplayName(key);
|
||||
},
|
||||
onReset() {
|
||||
this.$parent.onReset();
|
||||
},
|
||||
onDownload() {
|
||||
this.downloadDisabled = true;
|
||||
this.$parent.onDownload();
|
||||
setTimeout(() => {
|
||||
this.downloadDisabled = false;
|
||||
}, 2500);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<theme-input
|
||||
v-if="isGlobal"
|
||||
:val="value"
|
||||
@change="onChange"
|
||||
></theme-input>
|
||||
<el-select
|
||||
size="medium"
|
||||
v-if="!isGlobal"
|
||||
v-model="value"
|
||||
class="select"
|
||||
@change="onSelectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.select {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Mixin from './mixin';
|
||||
import Input from './input';
|
||||
import { getStyleDisplayName } from '../utils/utils.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
value: ''
|
||||
};
|
||||
},
|
||||
components: {
|
||||
themeInput: Input
|
||||
},
|
||||
mixins: [Mixin],
|
||||
computed: {
|
||||
isGlobalInputValue() {
|
||||
return this.config.value.startsWith('$');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelectChange(e) {
|
||||
this.onChange(e);
|
||||
},
|
||||
initSelectOption() {
|
||||
this.options = [];
|
||||
const golbalV = this.golbalValue.border;
|
||||
if (golbalV) {
|
||||
Object.keys(golbalV).forEach((font) => {
|
||||
if (font.includes('border-radius')) {
|
||||
const size = golbalV[font];
|
||||
this.options.push({
|
||||
value: size.key,
|
||||
label: getStyleDisplayName(size)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.initSelectOption();
|
||||
this.value = this.mergedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
158
examples/components/theme-configurator/editor/boxShadow.vue
Normal file
158
examples/components/theme-configurator/editor/boxShadow.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
class="plus-button"
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-plus"
|
||||
@click.stop="onAddShadow"
|
||||
>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="config-content" v-for="(each, key) in valueArr" :key="key">
|
||||
<div class="content-10">
|
||||
<color-picker
|
||||
size="mini"
|
||||
class="colorPicker"
|
||||
v-model="each.color"
|
||||
@change="val => onInputChange(val, key, 'color')"
|
||||
show-alpha
|
||||
></color-picker>
|
||||
<span class="content-tip">Color</span>
|
||||
</div>
|
||||
<div class="content-20">
|
||||
<theme-input
|
||||
size="mini"
|
||||
:val="each.offsetX"
|
||||
@change="val => onInputChange(Number(val), key, 'offsetX')"
|
||||
>
|
||||
</theme-input>
|
||||
<span class="content-tip">X-px</span>
|
||||
</div>
|
||||
<div class="content-20">
|
||||
<theme-input
|
||||
size="mini"
|
||||
:val="each.offsetY"
|
||||
@change="val => onInputChange(Number(val), key, 'offsetY')"
|
||||
>
|
||||
</theme-input>
|
||||
<span class="content-tip">Y-px</span>
|
||||
</div>
|
||||
<div class="content-20">
|
||||
<theme-input
|
||||
size="mini"
|
||||
:val="each.spreadRadius"
|
||||
@change="val => onInputChange(Number(val), key, 'spreadRadius')"
|
||||
>
|
||||
</theme-input>
|
||||
<span class="content-tip">Spread</span>
|
||||
</div>
|
||||
<div class="content-20">
|
||||
<theme-input
|
||||
size="mini"
|
||||
:val="each.blurRadius"
|
||||
@change="val => onInputChange(Number(val), key, 'blurRadius')"
|
||||
>
|
||||
</theme-input>
|
||||
<span class="content-tip">Blur</span>
|
||||
</div>
|
||||
<div class="content-10">
|
||||
<el-button
|
||||
size="mini"
|
||||
round
|
||||
icon="el-icon-minus"
|
||||
@click.stop="val => onMinusShadow(key)"
|
||||
></el-button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.plus-button {
|
||||
position: absolute;
|
||||
left: 90%;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.colorPicker {
|
||||
margin-left: 0;
|
||||
}
|
||||
.content-20 .el-input__suffix-inner span{
|
||||
line-height: 28px;
|
||||
}
|
||||
.content-20 {
|
||||
padding: 0 3px;
|
||||
}
|
||||
.content-10 {
|
||||
vertical-align: top;
|
||||
}
|
||||
.content-tip {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
.config-content {
|
||||
padding: 5px 0;
|
||||
}
|
||||
/* Element buton style override */
|
||||
.el-button--mini.is-round {
|
||||
padding: 3px 3px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import Mixin from './mixin';
|
||||
import Input from './input';
|
||||
import { parse as parseShaodw, stringify as stringifyShaodw } from '../utils/boxShadow.js';
|
||||
import ColorPicker from './color-picker';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColorPicker,
|
||||
themeInput: Input
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueArr: []
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
methods: {
|
||||
onAddShadow() {
|
||||
this.valueArr.push({
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
spreadRadius: 0,
|
||||
blurRadius: 0,
|
||||
color: 'rgba(0,0,0,0)',
|
||||
inset: false
|
||||
});
|
||||
},
|
||||
onMinusShadow(index) {
|
||||
this.valueArr.splice(index, 1);
|
||||
this.onShadowChange();
|
||||
},
|
||||
onInputChange(e, index, key) {
|
||||
const arr = this.valueArr[index];
|
||||
arr[key] = e;
|
||||
this.valueArr.splice(index, 1, arr);
|
||||
this.onShadowChange();
|
||||
},
|
||||
onShadowChange() {
|
||||
this.onChange(
|
||||
stringifyShaodw(this.valueArr)
|
||||
);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.valueArr = parseShaodw(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,8 @@
|
||||
import ColorPicker from './src/main';
|
||||
|
||||
/* istanbul ignore next */
|
||||
ColorPicker.install = function(Vue) {
|
||||
Vue.component(ColorPicker.name, ColorPicker);
|
||||
};
|
||||
|
||||
export default ColorPicker;
|
||||
@@ -0,0 +1,316 @@
|
||||
const hsv2hsl = function(hue, sat, val) {
|
||||
return [
|
||||
hue,
|
||||
(sat * val / ((hue = (2 - sat) * val) < 1 ? hue : 2 - hue)) || 0,
|
||||
hue / 2
|
||||
];
|
||||
};
|
||||
|
||||
// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
|
||||
// <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
|
||||
const isOnePointZero = function(n) {
|
||||
return typeof n === 'string' && n.indexOf('.') !== -1 && parseFloat(n) === 1;
|
||||
};
|
||||
|
||||
const isPercentage = function(n) {
|
||||
return typeof n === 'string' && n.indexOf('%') !== -1;
|
||||
};
|
||||
|
||||
// Take input from [0, n] and return it as [0, 1]
|
||||
const bound01 = function(value, max) {
|
||||
if (isOnePointZero(value)) value = '100%';
|
||||
|
||||
const processPercent = isPercentage(value);
|
||||
value = Math.min(max, Math.max(0, parseFloat(value)));
|
||||
|
||||
// Automatically convert percentage into number
|
||||
if (processPercent) {
|
||||
value = parseInt(value * max, 10) / 100;
|
||||
}
|
||||
|
||||
// Handle floating point rounding errors
|
||||
if ((Math.abs(value - max) < 0.000001)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Convert into [0, 1] range if it isn't already
|
||||
return (value % max) / parseFloat(max);
|
||||
};
|
||||
|
||||
const INT_HEX_MAP = { 10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F' };
|
||||
|
||||
const toHex = function({ r, g, b }) {
|
||||
const hexOne = function(value) {
|
||||
value = Math.min(Math.round(value), 255);
|
||||
const high = Math.floor(value / 16);
|
||||
const low = value % 16;
|
||||
return '' + (INT_HEX_MAP[high] || high) + (INT_HEX_MAP[low] || low);
|
||||
};
|
||||
|
||||
if (isNaN(r) || isNaN(g) || isNaN(b)) return '';
|
||||
|
||||
return '#' + hexOne(r) + hexOne(g) + hexOne(b);
|
||||
};
|
||||
|
||||
const HEX_INT_MAP = { A: 10, B: 11, C: 12, D: 13, E: 14, F: 15 };
|
||||
|
||||
const parseHexChannel = function(hex) {
|
||||
if (hex.length === 2) {
|
||||
return (HEX_INT_MAP[hex[0].toUpperCase()] || +hex[0]) * 16 + (HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1]);
|
||||
}
|
||||
|
||||
return HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1];
|
||||
};
|
||||
|
||||
const hsl2hsv = function(hue, sat, light) {
|
||||
sat = sat / 100;
|
||||
light = light / 100;
|
||||
let smin = sat;
|
||||
const lmin = Math.max(light, 0.01);
|
||||
let sv;
|
||||
let v;
|
||||
|
||||
light *= 2;
|
||||
sat *= (light <= 1) ? light : 2 - light;
|
||||
smin *= lmin <= 1 ? lmin : 2 - lmin;
|
||||
v = (light + sat) / 2;
|
||||
sv = light === 0 ? (2 * smin) / (lmin + smin) : (2 * sat) / (light + sat);
|
||||
|
||||
return {
|
||||
h: hue,
|
||||
s: sv * 100,
|
||||
v: v * 100
|
||||
};
|
||||
};
|
||||
|
||||
// `rgbToHsv`
|
||||
// Converts an RGB color value to HSV
|
||||
// *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
|
||||
// *Returns:* { h, s, v } in [0,1]
|
||||
const rgb2hsv = function(r, g, b) {
|
||||
r = bound01(r, 255);
|
||||
g = bound01(g, 255);
|
||||
b = bound01(b, 255);
|
||||
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
let h, s;
|
||||
let v = max;
|
||||
|
||||
const d = max - min;
|
||||
s = max === 0 ? 0 : d / max;
|
||||
|
||||
if (max === min) {
|
||||
h = 0; // achromatic
|
||||
} else {
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0);
|
||||
break;
|
||||
case g:
|
||||
h = (b - r) / d + 2;
|
||||
break;
|
||||
case b:
|
||||
h = (r - g) / d + 4;
|
||||
break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return { h: h * 360, s: s * 100, v: v * 100 };
|
||||
};
|
||||
|
||||
// `hsvToRgb`
|
||||
// Converts an HSV color value to RGB.
|
||||
// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
|
||||
// *Returns:* { r, g, b } in the set [0, 255]
|
||||
const hsv2rgb = function(h, s, v) {
|
||||
h = bound01(h, 360) * 6;
|
||||
s = bound01(s, 100);
|
||||
v = bound01(v, 100);
|
||||
|
||||
const i = Math.floor(h);
|
||||
const f = h - i;
|
||||
const p = v * (1 - s);
|
||||
const q = v * (1 - f * s);
|
||||
const t = v * (1 - (1 - f) * s);
|
||||
const mod = i % 6;
|
||||
const r = [v, q, p, p, t, v][mod];
|
||||
const g = [t, v, v, q, p, p][mod];
|
||||
const b = [p, p, t, v, v, q][mod];
|
||||
|
||||
return {
|
||||
r: Math.round(r * 255),
|
||||
g: Math.round(g * 255),
|
||||
b: Math.round(b * 255)
|
||||
};
|
||||
};
|
||||
|
||||
export default class Color {
|
||||
constructor(options) {
|
||||
this._hue = 0;
|
||||
this._saturation = 100;
|
||||
this._value = 100;
|
||||
this._alpha = 100;
|
||||
|
||||
this.enableAlpha = false;
|
||||
this.format = 'hex';
|
||||
this.value = '';
|
||||
|
||||
options = options || {};
|
||||
|
||||
for (let option in options) {
|
||||
if (options.hasOwnProperty(option)) {
|
||||
this[option] = options[option];
|
||||
}
|
||||
}
|
||||
|
||||
this.doOnChange();
|
||||
}
|
||||
|
||||
set(prop, value) {
|
||||
if (arguments.length === 1 && typeof prop === 'object') {
|
||||
for (let p in prop) {
|
||||
if (prop.hasOwnProperty(p)) {
|
||||
this.set(p, prop[p]);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this['_' + prop] = value;
|
||||
this.doOnChange();
|
||||
}
|
||||
|
||||
get(prop) {
|
||||
return this['_' + prop];
|
||||
}
|
||||
|
||||
toRgb() {
|
||||
return hsv2rgb(this._hue, this._saturation, this._value);
|
||||
}
|
||||
|
||||
fromString(value) {
|
||||
if (!value) {
|
||||
this._hue = 0;
|
||||
this._saturation = 100;
|
||||
this._value = 100;
|
||||
|
||||
this.doOnChange();
|
||||
return;
|
||||
}
|
||||
|
||||
const fromHSV = (h, s, v) => {
|
||||
this._hue = Math.max(0, Math.min(360, h));
|
||||
this._saturation = Math.max(0, Math.min(100, s));
|
||||
this._value = Math.max(0, Math.min(100, v));
|
||||
|
||||
this.doOnChange();
|
||||
};
|
||||
|
||||
if (value.indexOf('hsl') !== -1) {
|
||||
const parts = value.replace(/hsla|hsl|\(|\)/gm, '')
|
||||
.split(/\s|,/g).filter((val) => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10));
|
||||
|
||||
if (parts.length === 4) {
|
||||
this._alpha = Math.floor(parseFloat(parts[3]) * 100);
|
||||
} else if (parts.length === 3) {
|
||||
this._alpha = 100;
|
||||
}
|
||||
if (parts.length >= 3) {
|
||||
const { h, s, v } = hsl2hsv(parts[0], parts[1], parts[2]);
|
||||
fromHSV(h, s, v);
|
||||
}
|
||||
} else if (value.indexOf('hsv') !== -1) {
|
||||
const parts = value.replace(/hsva|hsv|\(|\)/gm, '')
|
||||
.split(/\s|,/g).filter((val) => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10));
|
||||
|
||||
if (parts.length === 4) {
|
||||
this._alpha = Math.floor(parseFloat(parts[3]) * 100);
|
||||
} else if (parts.length === 3) {
|
||||
this._alpha = 100;
|
||||
}
|
||||
if (parts.length >= 3) {
|
||||
fromHSV(parts[0], parts[1], parts[2]);
|
||||
}
|
||||
} else if (value.indexOf('rgb') !== -1) {
|
||||
const parts = value.replace(/rgba|rgb|\(|\)/gm, '')
|
||||
.split(/\s|,/g).filter((val) => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10));
|
||||
|
||||
if (parts.length === 4) {
|
||||
this._alpha = Math.floor(parseFloat(parts[3]) * 100);
|
||||
} else if (parts.length === 3) {
|
||||
this._alpha = 100;
|
||||
}
|
||||
if (parts.length >= 3) {
|
||||
const { h, s, v } = rgb2hsv(parts[0], parts[1], parts[2]);
|
||||
fromHSV(h, s, v);
|
||||
}
|
||||
} else if (value.indexOf('#') !== -1) {
|
||||
const hex = value.replace('#', '').trim();
|
||||
let r, g, b;
|
||||
|
||||
if (hex.length === 3) {
|
||||
r = parseHexChannel(hex[0] + hex[0]);
|
||||
g = parseHexChannel(hex[1] + hex[1]);
|
||||
b = parseHexChannel(hex[2] + hex[2]);
|
||||
} else if (hex.length === 6 || hex.length === 8) {
|
||||
r = parseHexChannel(hex.substring(0, 2));
|
||||
g = parseHexChannel(hex.substring(2, 4));
|
||||
b = parseHexChannel(hex.substring(4, 6));
|
||||
}
|
||||
|
||||
if (hex.length === 8) {
|
||||
this._alpha = Math.floor(parseHexChannel(hex.substring(6)) / 255 * 100);
|
||||
} else if (hex.length === 3 || hex.length === 6) {
|
||||
this._alpha = 100;
|
||||
}
|
||||
|
||||
const { h, s, v } = rgb2hsv(r, g, b);
|
||||
fromHSV(h, s, v);
|
||||
}
|
||||
}
|
||||
|
||||
compare(color) {
|
||||
return Math.abs(color._hue - this._hue) < 2 &&
|
||||
Math.abs(color._saturation - this._saturation) < 1 &&
|
||||
Math.abs(color._value - this._value) < 1 &&
|
||||
Math.abs(color._alpha - this._alpha) < 1;
|
||||
}
|
||||
|
||||
doOnChange() {
|
||||
const { _hue, _saturation, _value, _alpha, format } = this;
|
||||
|
||||
if (this.enableAlpha) {
|
||||
switch (format) {
|
||||
case 'hsl':
|
||||
const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100);
|
||||
this.value = `hsla(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%, ${ _alpha / 100})`;
|
||||
break;
|
||||
case 'hsv':
|
||||
this.value = `hsva(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%, ${ _alpha / 100})`;
|
||||
break;
|
||||
default:
|
||||
const { r, g, b } = hsv2rgb(_hue, _saturation, _value);
|
||||
this.value = `rgba(${r}, ${g}, ${b}, ${ _alpha / 100 })`;
|
||||
}
|
||||
} else {
|
||||
switch (format) {
|
||||
case 'hsl':
|
||||
const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100);
|
||||
this.value = `hsl(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%)`;
|
||||
break;
|
||||
case 'hsv':
|
||||
this.value = `hsv(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%)`;
|
||||
break;
|
||||
case 'rgb':
|
||||
const { r, g, b } = hsv2rgb(_hue, _saturation, _value);
|
||||
this.value = `rgb(${r}, ${g}, ${b})`;
|
||||
break;
|
||||
default:
|
||||
this.value = toHex(hsv2rgb(_hue, _saturation, _value));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="el-color-alpha-slider" :class="{ 'is-vertical': vertical }">
|
||||
<div class="el-color-alpha-slider__bar"
|
||||
@click="handleClick"
|
||||
ref="bar"
|
||||
:style="{
|
||||
background: background
|
||||
}">
|
||||
</div>
|
||||
<div class="el-color-alpha-slider__thumb"
|
||||
ref="thumb"
|
||||
:style="{
|
||||
left: thumbLeft + 'px',
|
||||
top: thumbTop + 'px'
|
||||
}">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from '../draggable';
|
||||
|
||||
export default {
|
||||
name: 'el-color-alpha-slider',
|
||||
|
||||
props: {
|
||||
color: {
|
||||
required: true
|
||||
},
|
||||
vertical: Boolean
|
||||
},
|
||||
|
||||
watch: {
|
||||
'color._alpha'() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
'color.value'() {
|
||||
this.update();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick(event) {
|
||||
const thumb = this.$refs.thumb;
|
||||
const target = event.target;
|
||||
|
||||
if (target !== thumb) {
|
||||
this.handleDrag(event);
|
||||
}
|
||||
},
|
||||
|
||||
handleDrag(event) {
|
||||
const rect = this.$el.getBoundingClientRect();
|
||||
const { thumb } = this.$refs;
|
||||
|
||||
if (!this.vertical) {
|
||||
let left = event.clientX - rect.left;
|
||||
left = Math.max(thumb.offsetWidth / 2, left);
|
||||
left = Math.min(left, rect.width - thumb.offsetWidth / 2);
|
||||
|
||||
this.color.set('alpha', Math.round((left - thumb.offsetWidth / 2) / (rect.width - thumb.offsetWidth) * 100));
|
||||
} else {
|
||||
let top = event.clientY - rect.top;
|
||||
top = Math.max(thumb.offsetHeight / 2, top);
|
||||
top = Math.min(top, rect.height - thumb.offsetHeight / 2);
|
||||
|
||||
this.color.set('alpha', Math.round((top - thumb.offsetHeight / 2) / (rect.height - thumb.offsetHeight) * 100));
|
||||
}
|
||||
},
|
||||
|
||||
getThumbLeft() {
|
||||
if (this.vertical) return 0;
|
||||
const el = this.$el;
|
||||
const alpha = this.color._alpha;
|
||||
|
||||
if (!el) return 0;
|
||||
const thumb = this.$refs.thumb;
|
||||
return Math.round(alpha * (el.offsetWidth - thumb.offsetWidth / 2) / 100);
|
||||
},
|
||||
|
||||
getThumbTop() {
|
||||
if (!this.vertical) return 0;
|
||||
const el = this.$el;
|
||||
const alpha = this.color._alpha;
|
||||
|
||||
if (!el) return 0;
|
||||
const thumb = this.$refs.thumb;
|
||||
return Math.round(alpha * (el.offsetHeight - thumb.offsetHeight / 2) / 100);
|
||||
},
|
||||
|
||||
getBackground() {
|
||||
if (this.color && this.color.value) {
|
||||
const { r, g, b } = this.color.toRgb();
|
||||
return `linear-gradient(to right, rgba(${r}, ${g}, ${b}, 0) 0%, rgba(${r}, ${g}, ${b}, 1) 100%)`;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
update() {
|
||||
this.thumbLeft = this.getThumbLeft();
|
||||
this.thumbTop = this.getThumbTop();
|
||||
this.background = this.getBackground();
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
thumbLeft: 0,
|
||||
thumbTop: 0,
|
||||
background: null
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const { bar, thumb } = this.$refs;
|
||||
|
||||
const dragConfig = {
|
||||
drag: (event) => {
|
||||
this.handleDrag(event);
|
||||
},
|
||||
end: (event) => {
|
||||
this.handleDrag(event);
|
||||
}
|
||||
};
|
||||
|
||||
draggable(bar, dragConfig);
|
||||
draggable(thumb, dragConfig);
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div class="el-color-predefine color-list-container">
|
||||
<div class="el-color-predefine__colors color-list">
|
||||
<div class="color-list-item"
|
||||
:class="{selected: item.selected, 'is-alpha': item._alpha < 100}"
|
||||
v-for="(item, index) in rgbaColors"
|
||||
:key="colors[index].variable"
|
||||
@click="handleSelect(index)">
|
||||
<span class="color-list-item-ball" :style="{'background-color': item.value}">
|
||||
</span>
|
||||
<div class="color-list-item-label">
|
||||
{{item.info.label}}
|
||||
<div class="color-list-item-value">
|
||||
{{item.info.value}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
.color-list-container {
|
||||
border-top: 1px solid #EBEEF5;
|
||||
margin-top: 15px;
|
||||
padding-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
.color-list {
|
||||
max-height: 138px;
|
||||
overflow: auto;
|
||||
}
|
||||
.color-list-item {
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
margin: 2px 0;
|
||||
position: relative;
|
||||
}
|
||||
.color-list-item:hover {
|
||||
background: #efefef;
|
||||
}
|
||||
.color-list-item-ball {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-top: 2px;
|
||||
margin-left: 5px;
|
||||
border-radius: 100%;
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
.color-list-item-label {
|
||||
margin-left: 35px;
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
display: inline-block;
|
||||
width: 85%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.color-list-item-value {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Color from '../color';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
colors: { type: Array, required: true },
|
||||
color: { required: true }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rgbaColors: this.parseColors(this.colors, this.color)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSelect(index) {
|
||||
this.color.fromString(this.colors[index].value);
|
||||
this.$emit('select', this.colors[index]);
|
||||
},
|
||||
parseColors(colors, color) {
|
||||
return colors.map(value => {
|
||||
const c = new Color();
|
||||
c.enableAlpha = true;
|
||||
c.format = 'rgba';
|
||||
c.fromString(value.value);
|
||||
c.info = value;
|
||||
c.selected = c.value === color.value;
|
||||
return c;
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$parent.currentColor'(val) {
|
||||
const color = new Color();
|
||||
color.fromString(val);
|
||||
|
||||
this.rgbaColors.forEach(item => {
|
||||
item.selected = color.compare(item);
|
||||
});
|
||||
},
|
||||
colors(newVal) {
|
||||
this.rgbaColors = this.parseColors(newVal, this.color);
|
||||
},
|
||||
color(newVal) {
|
||||
this.rgbaColors = this.parseColors(this.colors, newVal);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="el-color-hue-slider" :class="{ 'is-vertical': vertical }">
|
||||
<div class="el-color-hue-slider__bar" @click="handleClick" ref="bar"></div>
|
||||
<div class="el-color-hue-slider__thumb"
|
||||
:style="{
|
||||
left: thumbLeft + 'px',
|
||||
top: thumbTop + 'px'
|
||||
}"
|
||||
ref="thumb">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from '../draggable';
|
||||
|
||||
export default {
|
||||
name: 'el-color-hue-slider',
|
||||
|
||||
props: {
|
||||
color: {
|
||||
required: true
|
||||
},
|
||||
|
||||
vertical: Boolean
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
thumbLeft: 0,
|
||||
thumbTop: 0
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hueValue() {
|
||||
const hue = this.color.get('hue');
|
||||
return hue;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
hueValue() {
|
||||
this.update();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick(event) {
|
||||
const thumb = this.$refs.thumb;
|
||||
const target = event.target;
|
||||
|
||||
if (target !== thumb) {
|
||||
this.handleDrag(event);
|
||||
}
|
||||
},
|
||||
|
||||
handleDrag(event) {
|
||||
const rect = this.$el.getBoundingClientRect();
|
||||
const { thumb } = this.$refs;
|
||||
let hue;
|
||||
|
||||
if (!this.vertical) {
|
||||
let left = event.clientX - rect.left;
|
||||
left = Math.min(left, rect.width - thumb.offsetWidth / 2);
|
||||
left = Math.max(thumb.offsetWidth / 2, left);
|
||||
|
||||
hue = Math.round((left - thumb.offsetWidth / 2) / (rect.width - thumb.offsetWidth) * 360);
|
||||
} else {
|
||||
let top = event.clientY - rect.top;
|
||||
top = Math.min(top, rect.height - thumb.offsetHeight / 2);
|
||||
top = Math.max(thumb.offsetHeight / 2, top);
|
||||
|
||||
hue = Math.round((top - thumb.offsetHeight / 2) / (rect.height - thumb.offsetHeight) * 360);
|
||||
}
|
||||
|
||||
this.color.set('hue', hue);
|
||||
},
|
||||
|
||||
getThumbLeft() {
|
||||
if (this.vertical) return 0;
|
||||
const el = this.$el;
|
||||
const hue = this.color.get('hue');
|
||||
|
||||
if (!el) return 0;
|
||||
const thumb = this.$refs.thumb;
|
||||
return Math.round(hue * (el.offsetWidth - thumb.offsetWidth / 2) / 360);
|
||||
},
|
||||
|
||||
getThumbTop() {
|
||||
if (!this.vertical) return 0;
|
||||
const el = this.$el;
|
||||
const hue = this.color.get('hue');
|
||||
|
||||
if (!el) return 0;
|
||||
const thumb = this.$refs.thumb;
|
||||
return Math.round(hue * (el.offsetHeight - thumb.offsetHeight / 2) / 360);
|
||||
},
|
||||
|
||||
update() {
|
||||
this.thumbLeft = this.getThumbLeft();
|
||||
this.thumbTop = this.getThumbTop();
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const { bar, thumb } = this.$refs;
|
||||
|
||||
const dragConfig = {
|
||||
drag: (event) => {
|
||||
this.handleDrag(event);
|
||||
},
|
||||
end: (event) => {
|
||||
this.handleDrag(event);
|
||||
}
|
||||
};
|
||||
|
||||
draggable(bar, dragConfig);
|
||||
draggable(thumb, dragConfig);
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<transition name="el-zoom-in-top" @after-leave="doDestroy">
|
||||
<div
|
||||
class="el-color-dropdown"
|
||||
v-show="showPopper">
|
||||
<div class="el-color-dropdown__main-wrapper">
|
||||
<hue-slider ref="hue" :color="color" vertical style="float: right;"></hue-slider>
|
||||
<sv-panel ref="sl" :color="color"></sv-panel>
|
||||
</div>
|
||||
<alpha-slider v-if="showAlpha" ref="alpha" :color="color"></alpha-slider>
|
||||
<predefine v-if="predefine" :color="color" :colors="predefine"></predefine>
|
||||
<div class="el-color-dropdown__btns">
|
||||
<span class="el-color-dropdown__value">
|
||||
<el-input
|
||||
v-model="customInput"
|
||||
@keyup.native.enter="handleConfirm"
|
||||
@blur="handleConfirm"
|
||||
:validate-event="false"
|
||||
size="mini">
|
||||
</el-input>
|
||||
</span>
|
||||
<!-- <el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
class="el-color-dropdown__link-btn"
|
||||
@click="$emit('clear')">
|
||||
{{ t('el.colorpicker.clear') }}
|
||||
</el-button> -->
|
||||
<el-button
|
||||
plain
|
||||
size="mini"
|
||||
type="primary"
|
||||
class="el-color-dropdown__btn"
|
||||
@click="confirmValue">
|
||||
{{ t('el.colorpicker.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<color-list
|
||||
v-if="colorList && colorList.length > 0"
|
||||
:color="color"
|
||||
:colors="colorList"
|
||||
@select=onColorListSelect
|
||||
></color-list>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SvPanel from './sv-panel';
|
||||
import HueSlider from './hue-slider';
|
||||
import AlphaSlider from './alpha-slider';
|
||||
import Predefine from './predefine';
|
||||
import ColorList from './color-list';
|
||||
import Popper from 'element-ui/src/utils/vue-popper';
|
||||
import Locale from 'element-ui/src/mixins/locale';
|
||||
import ElInput from 'element-ui/packages/input';
|
||||
import ElButton from 'element-ui/packages/button';
|
||||
|
||||
export default {
|
||||
name: 'el-color-picker-dropdown',
|
||||
|
||||
mixins: [Popper, Locale],
|
||||
|
||||
components: {
|
||||
SvPanel,
|
||||
HueSlider,
|
||||
AlphaSlider,
|
||||
ElInput,
|
||||
ElButton,
|
||||
Predefine,
|
||||
ColorList
|
||||
},
|
||||
|
||||
props: {
|
||||
color: {
|
||||
required: true
|
||||
},
|
||||
showAlpha: Boolean,
|
||||
predefine: Array,
|
||||
colorList: Array
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
customInput: ''
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentColor() {
|
||||
const parent = this.$parent;
|
||||
return !parent.value && !parent.showPanelColor ? '' : parent.color.value;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
confirmValue() {
|
||||
this.$emit('pick');
|
||||
},
|
||||
|
||||
onColorListSelect(e) {
|
||||
this.$emit('pick', e);
|
||||
},
|
||||
|
||||
handleConfirm() {
|
||||
this.color.fromString(this.customInput);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$parent.popperElm = this.popperElm = this.$el;
|
||||
this.referenceElm = this.$parent.$el;
|
||||
},
|
||||
|
||||
watch: {
|
||||
showPopper(val) {
|
||||
if (val === true) {
|
||||
this.$nextTick(() => {
|
||||
const { sl, hue, alpha } = this.$refs;
|
||||
sl && sl.update();
|
||||
hue && hue.update();
|
||||
alpha && alpha.update();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
currentColor: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.customInput = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="el-color-predefine">
|
||||
<div class="el-color-predefine__colors">
|
||||
<div class="el-color-predefine__color-selector"
|
||||
:class="{selected: item.selected, 'is-alpha': item._alpha < 100}"
|
||||
v-for="(item, index) in rgbaColors"
|
||||
:key="colors[index]"
|
||||
@click="handleSelect(index)">
|
||||
<div :style="{'background-color': item.value}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Color from '../color';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
colors: { type: Array, required: true },
|
||||
color: { required: true }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rgbaColors: this.parseColors(this.colors, this.color)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSelect(index) {
|
||||
this.color.fromString(this.colors[index]);
|
||||
},
|
||||
parseColors(colors, color) {
|
||||
return colors.map(value => {
|
||||
const c = new Color();
|
||||
c.enableAlpha = true;
|
||||
c.format = 'rgba';
|
||||
c.fromString(value);
|
||||
c.selected = c.value === color.value;
|
||||
return c;
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$parent.currentColor'(val) {
|
||||
const color = new Color();
|
||||
color.fromString(val);
|
||||
|
||||
this.rgbaColors.forEach(item => {
|
||||
item.selected = color.compare(item);
|
||||
});
|
||||
},
|
||||
colors(newVal) {
|
||||
this.rgbaColors = this.parseColors(newVal, this.color);
|
||||
},
|
||||
color(newVal) {
|
||||
this.rgbaColors = this.parseColors(this.colors, newVal);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="el-color-svpanel"
|
||||
:style="{
|
||||
backgroundColor: background
|
||||
}">
|
||||
<div class="el-color-svpanel__white"></div>
|
||||
<div class="el-color-svpanel__black"></div>
|
||||
<div class="el-color-svpanel__cursor"
|
||||
:style="{
|
||||
top: cursorTop + 'px',
|
||||
left: cursorLeft + 'px'
|
||||
}">
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from '../draggable';
|
||||
|
||||
export default {
|
||||
name: 'el-sl-panel',
|
||||
|
||||
props: {
|
||||
color: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
colorValue() {
|
||||
const hue = this.color.get('hue');
|
||||
const value = this.color.get('value');
|
||||
return { hue, value };
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
colorValue() {
|
||||
this.update();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
const saturation = this.color.get('saturation');
|
||||
const value = this.color.get('value');
|
||||
|
||||
const el = this.$el;
|
||||
let { clientWidth: width, clientHeight: height } = el;
|
||||
|
||||
this.cursorLeft = saturation * width / 100;
|
||||
this.cursorTop = (100 - value) * height / 100;
|
||||
|
||||
this.background = 'hsl(' + this.color.get('hue') + ', 100%, 50%)';
|
||||
},
|
||||
|
||||
handleDrag(event) {
|
||||
const el = this.$el;
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
||||
let left = event.clientX - rect.left;
|
||||
let top = event.clientY - rect.top;
|
||||
left = Math.max(0, left);
|
||||
left = Math.min(left, rect.width);
|
||||
|
||||
top = Math.max(0, top);
|
||||
top = Math.min(top, rect.height);
|
||||
|
||||
this.cursorLeft = left;
|
||||
this.cursorTop = top;
|
||||
this.color.set({
|
||||
saturation: left / rect.width * 100,
|
||||
value: 100 - top / rect.height * 100
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
draggable(this.$el, {
|
||||
drag: (event) => {
|
||||
this.handleDrag(event);
|
||||
},
|
||||
end: (event) => {
|
||||
this.handleDrag(event);
|
||||
}
|
||||
});
|
||||
|
||||
this.update();
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
cursorTop: 0,
|
||||
cursorLeft: 0,
|
||||
background: 'hsl(0, 100%, 50%)'
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
import Vue from 'vue';
|
||||
let isDragging = false;
|
||||
|
||||
export default function(element, options) {
|
||||
if (Vue.prototype.$isServer) return;
|
||||
const moveFn = function(event) {
|
||||
if (options.drag) {
|
||||
options.drag(event);
|
||||
}
|
||||
};
|
||||
const upFn = function(event) {
|
||||
document.removeEventListener('mousemove', moveFn);
|
||||
document.removeEventListener('mouseup', upFn);
|
||||
document.onselectstart = null;
|
||||
document.ondragstart = null;
|
||||
|
||||
isDragging = false;
|
||||
|
||||
if (options.end) {
|
||||
options.end(event);
|
||||
}
|
||||
};
|
||||
element.addEventListener('mousedown', function(event) {
|
||||
if (isDragging) return;
|
||||
document.onselectstart = function() { return false; };
|
||||
document.ondragstart = function() { return false; };
|
||||
|
||||
document.addEventListener('mousemove', moveFn);
|
||||
document.addEventListener('mouseup', upFn);
|
||||
isDragging = true;
|
||||
|
||||
if (options.start) {
|
||||
options.start(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'el-color-picker',
|
||||
colorDisabled ? 'is-disabled' : '',
|
||||
colorSize ? `el-color-picker--${ colorSize }` : ''
|
||||
]"
|
||||
v-clickoutside="hide">
|
||||
<div class="el-color-picker__mask" v-if="colorDisabled"></div>
|
||||
<div class="el-color-picker__trigger" @click="handleTrigger">
|
||||
<span class="el-color-picker__color" :class="{ 'is-alpha': showAlpha }">
|
||||
<span class="el-color-picker__color-inner"
|
||||
:style="{
|
||||
backgroundColor: displayedColor
|
||||
}"></span>
|
||||
<span class="el-color-picker__empty el-icon-close" v-if="!value && !showPanelColor"></span>
|
||||
</span>
|
||||
<span class="el-color-picker__icon el-icon-arrow-down" v-show="value || showPanelColor"></span>
|
||||
</div>
|
||||
<picker-dropdown
|
||||
ref="dropdown"
|
||||
:class="['el-color-picker__panel', popperClass || '']"
|
||||
v-model="showPicker"
|
||||
@pick="confirmValue"
|
||||
@clear="clearValue"
|
||||
:color="color"
|
||||
:show-alpha="showAlpha"
|
||||
:predefine="predefine"
|
||||
:colorList="colorList">
|
||||
</picker-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Color from './color';
|
||||
import PickerDropdown from './components/picker-dropdown.vue';
|
||||
import Clickoutside from 'element-ui/src/utils/clickoutside';
|
||||
import Emitter from 'element-ui/src/mixins/emitter';
|
||||
|
||||
export default {
|
||||
name: 'ElColorPicker',
|
||||
|
||||
mixins: [Emitter],
|
||||
|
||||
props: {
|
||||
value: String,
|
||||
showAlpha: Boolean,
|
||||
colorFormat: String,
|
||||
disabled: Boolean,
|
||||
size: String,
|
||||
popperClass: String,
|
||||
predefine: Array,
|
||||
colorList: Array
|
||||
},
|
||||
|
||||
inject: {
|
||||
elForm: {
|
||||
default: ''
|
||||
},
|
||||
elFormItem: {
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
directives: { Clickoutside },
|
||||
|
||||
computed: {
|
||||
displayedColor() {
|
||||
if (!this.value && !this.showPanelColor) {
|
||||
return 'transparent';
|
||||
}
|
||||
|
||||
return this.displayedRgb(this.color, this.showAlpha);
|
||||
},
|
||||
|
||||
_elFormItemSize() {
|
||||
return (this.elFormItem || {}).elFormItemSize;
|
||||
},
|
||||
|
||||
colorSize() {
|
||||
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
|
||||
},
|
||||
|
||||
colorDisabled() {
|
||||
return this.disabled || (this.elForm || {}).disabled;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
if (!val) {
|
||||
this.showPanelColor = false;
|
||||
} else if (val && val !== this.color.value) {
|
||||
this.color.fromString(val);
|
||||
}
|
||||
},
|
||||
color: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.showPanelColor = true;
|
||||
}
|
||||
},
|
||||
displayedColor(val) {
|
||||
if (!this.showPicker) return;
|
||||
const currentValueColor = new Color({
|
||||
enableAlpha: this.showAlpha,
|
||||
format: this.colorFormat
|
||||
});
|
||||
currentValueColor.fromString(this.value);
|
||||
|
||||
const currentValueColorRgb = this.displayedRgb(currentValueColor, this.showAlpha);
|
||||
if (val !== currentValueColorRgb) {
|
||||
this.$emit('active-change', val);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleTrigger() {
|
||||
if (this.colorDisabled) return;
|
||||
this.showPicker = !this.showPicker;
|
||||
},
|
||||
confirmValue(selection) {
|
||||
const value = selection || this.color.value;
|
||||
this.$emit('input', value);
|
||||
this.$emit('change', value);
|
||||
this.dispatch('ElFormItem', 'el.form.change', value);
|
||||
this.showPicker = false;
|
||||
},
|
||||
clearValue() {
|
||||
this.$emit('input', null);
|
||||
this.$emit('change', null);
|
||||
if (this.value !== null) {
|
||||
this.dispatch('ElFormItem', 'el.form.change', null);
|
||||
}
|
||||
this.showPanelColor = false;
|
||||
this.showPicker = false;
|
||||
this.resetColor();
|
||||
},
|
||||
hide() {
|
||||
this.showPicker = false;
|
||||
this.resetColor();
|
||||
},
|
||||
resetColor() {
|
||||
this.$nextTick(_ => {
|
||||
if (this.value) {
|
||||
this.color.fromString(this.value);
|
||||
} else {
|
||||
this.showPanelColor = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
displayedRgb(color, showAlpha) {
|
||||
if (!(color instanceof Color)) {
|
||||
throw Error('color should be instance of Color Class');
|
||||
}
|
||||
|
||||
const { r, g, b } = color.toRgb();
|
||||
return showAlpha
|
||||
? `rgba(${ r }, ${ g }, ${ b }, ${ color.get('alpha') / 100 })`
|
||||
: `rgb(${ r }, ${ g }, ${ b })`;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const value = this.value;
|
||||
if (value) {
|
||||
this.color.fromString(value);
|
||||
}
|
||||
this.popperElm = this.$refs.dropdown.$el;
|
||||
},
|
||||
|
||||
data() {
|
||||
const color = new Color({
|
||||
enableAlpha: this.showAlpha,
|
||||
format: this.colorFormat
|
||||
});
|
||||
|
||||
return {
|
||||
color,
|
||||
showPicker: false,
|
||||
showPanelColor: false
|
||||
};
|
||||
},
|
||||
|
||||
components: {
|
||||
PickerDropdown
|
||||
}
|
||||
};
|
||||
</script>
|
||||
93
examples/components/theme-configurator/editor/color.vue
Normal file
93
examples/components/theme-configurator/editor/color.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<div class="content-80">
|
||||
<el-input
|
||||
size="medium"
|
||||
:value=displayValue
|
||||
readonly
|
||||
slot="reference"
|
||||
@click.native="onInputClick"
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="content-20">
|
||||
<color-picker
|
||||
size="medium"
|
||||
ref="colorPicker"
|
||||
class="colorPicker"
|
||||
v-model="pickerColor"
|
||||
@change=onPickerChange
|
||||
:colorList="golbalColorList"
|
||||
></color-picker>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
input {
|
||||
cursor: pointer;
|
||||
}
|
||||
.colorPicker {
|
||||
margin-left: 10px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Mixin from './mixin';
|
||||
import { getStyleDisplayValue, getStyleDisplayName } from '../utils/utils.js';
|
||||
import ColorPicker from './color-picker';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColorPicker
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pickerColor: ''
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
watch: {
|
||||
displayValue: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
if (value.startsWith('#')) {
|
||||
this.pickerColor = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
golbalColor() {
|
||||
return this.golbalValue.color;
|
||||
},
|
||||
displayValue() {
|
||||
return getStyleDisplayValue(this.mergedValue, this.golbalColor);
|
||||
},
|
||||
golbalColorList() {
|
||||
return this.isGlobal ? [] : Object.keys(this.golbalColor).map((c) => (
|
||||
{
|
||||
label: getStyleDisplayName(this.golbalColor[c]),
|
||||
value: this.golbalColor[c].value,
|
||||
variable: c
|
||||
}
|
||||
));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onInputClick() {
|
||||
this.$refs.colorPicker && this.$refs.colorPicker.handleTrigger();
|
||||
},
|
||||
onPickerChange(e) {
|
||||
this.onChange(e.variable || e);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
104
examples/components/theme-configurator/editor/fontLineHeight.vue
Normal file
104
examples/components/theme-configurator/editor/fontLineHeight.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<el-select
|
||||
v-model="value"
|
||||
class="select"
|
||||
size="medium"
|
||||
@change="onSelectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.select {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const defaultFontLineHeight = [
|
||||
'1',
|
||||
'1.3',
|
||||
'1.5',
|
||||
'1.7',
|
||||
'12px',
|
||||
'16px',
|
||||
'20px',
|
||||
'24px',
|
||||
'28px'
|
||||
];
|
||||
import Mixin from './mixin';
|
||||
import { getStyleDisplayName } from '../utils/utils.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
componentName: {
|
||||
type: String
|
||||
},
|
||||
golbalValue: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
value: ''
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
computed: {
|
||||
isGlobalInputValue() {
|
||||
return this.config.value.startsWith('$');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelectChange(e) {
|
||||
this.onChange(e);
|
||||
},
|
||||
initSelectOption() {
|
||||
this.options = [];
|
||||
defaultFontLineHeight.forEach((size) => {
|
||||
this.options.push({
|
||||
value: size,
|
||||
label: size
|
||||
});
|
||||
});
|
||||
const golbalTypography = this.golbalValue.typography;
|
||||
if (this.isGlobalInputValue && golbalTypography) {
|
||||
Object.keys(golbalTypography).forEach((font) => {
|
||||
if (font.includes('font-line-height')) {
|
||||
const size = golbalTypography[font];
|
||||
this.options.push({
|
||||
value: size.key,
|
||||
label: getStyleDisplayName(size)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.initSelectOption();
|
||||
this.value = this.mergedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
105
examples/components/theme-configurator/editor/fontSize.vue
Normal file
105
examples/components/theme-configurator/editor/fontSize.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<el-select
|
||||
v-model="value"
|
||||
class="select"
|
||||
size="medium"
|
||||
@change="onSelectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.select {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const defaultFontSize = [
|
||||
'12px',
|
||||
'13px',
|
||||
'14px',
|
||||
'16px',
|
||||
'18px',
|
||||
'20px',
|
||||
'22px',
|
||||
'28px',
|
||||
'36px',
|
||||
'48px'
|
||||
];
|
||||
import Mixin from './mixin';
|
||||
import { getStyleDisplayName } from '../utils/utils.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
componentName: {
|
||||
type: String
|
||||
},
|
||||
golbalValue: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
value: ''
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
computed: {
|
||||
isGlobalInputValue() {
|
||||
return this.config.value.startsWith('$');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelectChange(e) {
|
||||
this.onChange(e);
|
||||
},
|
||||
initSelectOption() {
|
||||
this.options = [];
|
||||
defaultFontSize.forEach((size) => {
|
||||
this.options.push({
|
||||
value: size,
|
||||
label: size
|
||||
});
|
||||
});
|
||||
const golbalTypography = this.golbalValue.typography;
|
||||
if (this.isGlobalInputValue && golbalTypography) {
|
||||
Object.keys(golbalTypography).forEach((font) => {
|
||||
if (font.includes('font-size')) {
|
||||
const size = golbalTypography[font];
|
||||
this.options.push({
|
||||
value: size.key,
|
||||
label: getStyleDisplayName(size)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.initSelectOption();
|
||||
this.value = this.mergedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
109
examples/components/theme-configurator/editor/fontWeight.vue
Normal file
109
examples/components/theme-configurator/editor/fontWeight.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<el-select
|
||||
v-model="value"
|
||||
class="select"
|
||||
size="medium"
|
||||
@change="onSelectChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.select {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const defaultFontWeight = [
|
||||
'normal',
|
||||
'bold',
|
||||
'bolder',
|
||||
'lighter',
|
||||
'100',
|
||||
'200',
|
||||
'300',
|
||||
'400',
|
||||
'500',
|
||||
'600',
|
||||
'700',
|
||||
'800',
|
||||
'900',
|
||||
'inherit'
|
||||
];
|
||||
import Mixin from './mixin';
|
||||
import { getStyleDisplayName } from '../utils/utils.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
componentName: {
|
||||
type: String
|
||||
},
|
||||
golbalValue: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
value: ''
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
computed: {
|
||||
isGlobalInputValue() {
|
||||
return this.config.value.startsWith('$');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelectChange(e) {
|
||||
this.onChange(e);
|
||||
},
|
||||
initSelectOption() {
|
||||
this.options = [];
|
||||
defaultFontWeight.forEach((weight) => {
|
||||
this.options.push({
|
||||
value: weight,
|
||||
label: weight
|
||||
});
|
||||
});
|
||||
const golbalTypography = this.golbalValue.typography;
|
||||
if (this.isGlobalInputValue && golbalTypography) {
|
||||
Object.keys(golbalTypography).forEach((font) => {
|
||||
if (font.includes('font-weight')) {
|
||||
const weight = golbalTypography[font];
|
||||
this.options.push({
|
||||
value: weight.key,
|
||||
label: getStyleDisplayName(weight)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.initSelectOption();
|
||||
this.value = this.mergedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
45
examples/components/theme-configurator/editor/input.vue
Normal file
45
examples/components/theme-configurator/editor/input.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<el-input
|
||||
@keyup.enter.native="onUpdate"
|
||||
v-model="value"
|
||||
@blur="onUpdate"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template slot="suffix">
|
||||
<slot name="suffix"></slot>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: ['val', 'onChange'],
|
||||
data() {
|
||||
return {
|
||||
value: '',
|
||||
oldValue: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onUpdate(e) {
|
||||
const { value } = e.target;
|
||||
if (value !== this.oldValue) {
|
||||
this.oldValue = value;
|
||||
this.$emit('change', value);
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
val: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.value = value;
|
||||
if (!this.oldValue) {
|
||||
this.oldValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
81
examples/components/theme-configurator/editor/mixin.vue
Normal file
81
examples/components/theme-configurator/editor/mixin.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<style>
|
||||
.config {
|
||||
padding: 5px 0;
|
||||
}
|
||||
.config-label {
|
||||
color: #606266;;
|
||||
font-size: 14px;
|
||||
padding-bottom: 8px;
|
||||
position: relative;
|
||||
}
|
||||
.config-content {
|
||||
|
||||
}
|
||||
.content-80 {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 80%;
|
||||
}
|
||||
.content-20 {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 20%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.content-10 {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 10%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.content-15 {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 15%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { getStyleDisplayName } from '../utils/utils.js';
|
||||
export default {
|
||||
props: {
|
||||
config: {
|
||||
type: Object
|
||||
},
|
||||
userConfig: {
|
||||
type: Object
|
||||
},
|
||||
golbalValue: {
|
||||
type: Object
|
||||
},
|
||||
componentName: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mergedValue() {
|
||||
return this.userConfig[this.config.key] || this.config.value;
|
||||
},
|
||||
displayName() {
|
||||
return getStyleDisplayName(this.config, this.componentName);
|
||||
},
|
||||
displayKeyName() {
|
||||
if (this.config.name) {
|
||||
return this.config.key.replace('$--', '');
|
||||
}
|
||||
return this.config.key.replace(`$--${this.componentName}-`, '');
|
||||
},
|
||||
isGlobal() {
|
||||
return !this.config.value.startsWith('$');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(value) {
|
||||
this.$emit('onChange', {
|
||||
key: this.config.key,
|
||||
value
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
41
examples/components/theme-configurator/editor/simpleText.vue
Normal file
41
examples/components/theme-configurator/editor/simpleText.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<section class="config" :key="displayName">
|
||||
<div class="config-label">
|
||||
<el-tooltip :content="displayName" placement="top">
|
||||
<span>{{displayKeyName}}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="config-content">
|
||||
<theme-input
|
||||
:val="value"
|
||||
size="medium"
|
||||
@change="onChange"
|
||||
></theme-input>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Input from './input';
|
||||
import Mixin from './mixin';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
themeInput: Input
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: ''
|
||||
};
|
||||
},
|
||||
mixins: [Mixin],
|
||||
watch: {
|
||||
'mergedValue': {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.value = this.mergedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
195
examples/components/theme-configurator/index.vue
Normal file
195
examples/components/theme-configurator/index.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="main-configurator" ref='configurator'>
|
||||
<action-panel
|
||||
:selectOptions="selectOptions"
|
||||
:userConfigHistory="userConfigHistory"
|
||||
:userConfigRedoHistory="userConfigRedoHistory"
|
||||
:onUndo="undo"
|
||||
:onRedo="redo"
|
||||
:isOfficial="isOfficial"
|
||||
@select="onSelectChange"
|
||||
></action-panel>
|
||||
<main-panel
|
||||
v-if="defaultConfig"
|
||||
:currentConfig="currentConfig"
|
||||
:defaultConfig="defaultConfig"
|
||||
:userConfig="userConfig"
|
||||
:globalValue="globalValue"
|
||||
@onChange="userConfigChange"
|
||||
></main-panel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.main-configurator {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import bus from '../../bus.js';
|
||||
import { getVars } from '../theme/loader/api.js';
|
||||
import mainPanel from './main';
|
||||
import actionPanel from './action';
|
||||
import {
|
||||
filterConfigType,
|
||||
filterGlobalValue,
|
||||
getActionDisplayName
|
||||
} from './utils/utils.js';
|
||||
import Shortcut from './shortcut';
|
||||
import {
|
||||
ACTION_APPLY_THEME,
|
||||
ACTION_DOWNLOAD_THEME,
|
||||
ACTION_COMPONECT_SELECT
|
||||
} from '../theme/constant.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
themeConfig: Object,
|
||||
previewConfig: Object,
|
||||
isOfficial: Boolean,
|
||||
onUserConfigUpdate: Function
|
||||
},
|
||||
components: {
|
||||
mainPanel,
|
||||
actionPanel
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
init: false,
|
||||
defaultConfig: null,
|
||||
currentConfig: null,
|
||||
userConfig: {
|
||||
global: {},
|
||||
local: {}
|
||||
},
|
||||
userConfigHistory: [],
|
||||
userConfigRedoHistory: [],
|
||||
hasLocalConfig: false,
|
||||
selectOptions: [],
|
||||
selectedComponent: 'color'
|
||||
};
|
||||
},
|
||||
mixins: [Shortcut],
|
||||
computed: {
|
||||
globalValue() {
|
||||
return filterGlobalValue(this.defaultConfig, this.userConfig);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
ga('send', 'event', 'ThemeConfigurator', 'Init');
|
||||
this.showConfigurator();
|
||||
this.enableShortcut();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.disableShortcut();
|
||||
},
|
||||
methods: {
|
||||
getActionDisplayName(key) {
|
||||
return getActionDisplayName(key);
|
||||
},
|
||||
showConfigurator() {
|
||||
if (this.init) return;
|
||||
this.$nextTick(() => {
|
||||
const loading = this.$loading({
|
||||
target: this.$refs.configurator
|
||||
});
|
||||
let defaultConfig;
|
||||
getVars()
|
||||
.then(res => {
|
||||
defaultConfig = res;
|
||||
})
|
||||
.catch(err => {
|
||||
this.onError && this.onError(err);
|
||||
})
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
if (defaultConfig) {
|
||||
this.defaultConfig = defaultConfig;
|
||||
this.setSelectOption();
|
||||
this.filterCurrentConfig();
|
||||
this.init = true;
|
||||
}
|
||||
loading.close();
|
||||
}, 300); // action after transition
|
||||
});
|
||||
});
|
||||
},
|
||||
setSelectOption() {
|
||||
this.selectOptions = this.defaultConfig.map((config) => ({
|
||||
label: config.name.charAt(0).toUpperCase() + config.name.slice(1),
|
||||
value: config.name
|
||||
})).sort((a, b) => {
|
||||
const A = a.label;
|
||||
const B = b.label;
|
||||
if (A < B) return -1;
|
||||
if (A > B) return 1;
|
||||
return 0;
|
||||
});
|
||||
},
|
||||
filterCurrentConfig() {
|
||||
this.currentConfig = this.defaultConfig.find(config => {
|
||||
return (
|
||||
config.name === this.selectedComponent
|
||||
);
|
||||
});
|
||||
},
|
||||
userConfigChange(e) {
|
||||
this.userConfigHistory.push(JSON.stringify(this.userConfig));
|
||||
this.userConfigRedoHistory = [];
|
||||
this.$set(
|
||||
this.userConfig[filterConfigType(this.currentConfig.name)],
|
||||
e.key,
|
||||
e.value
|
||||
);
|
||||
this.onAction();
|
||||
},
|
||||
onReset() {
|
||||
this.userConfigRedoHistory = [];
|
||||
this.userConfigHistory = [];
|
||||
this.userConfig = {
|
||||
global: {},
|
||||
local: {}
|
||||
};
|
||||
this.onAction();
|
||||
},
|
||||
onDownload() {
|
||||
bus.$emit(ACTION_DOWNLOAD_THEME, this.userConfig, this.previewConfig.name);
|
||||
},
|
||||
onAction() {
|
||||
this.onUserConfigUpdate(this.userConfig);
|
||||
bus.$emit(ACTION_APPLY_THEME, this.userConfig);
|
||||
},
|
||||
undo() {
|
||||
if (this.userConfigHistory.length > 0) {
|
||||
this.userConfigRedoHistory.push(JSON.stringify(this.userConfig));
|
||||
this.userConfig = JSON.parse(this.userConfigHistory.pop());
|
||||
this.onAction();
|
||||
}
|
||||
},
|
||||
redo() {
|
||||
if (this.userConfigRedoHistory.length > 0) {
|
||||
this.userConfigHistory.push(JSON.stringify(this.userConfig));
|
||||
this.userConfig = JSON.parse(this.userConfigRedoHistory.shift());
|
||||
this.onAction();
|
||||
}
|
||||
},
|
||||
onSelectChange(val) {
|
||||
bus.$emit(ACTION_COMPONECT_SELECT, val);
|
||||
this.selectedComponent = val;
|
||||
this.filterCurrentConfig();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
themeConfig: {
|
||||
handler(val, oldVal) {
|
||||
if (!oldVal.globnal) {
|
||||
this.userConfig = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
130
examples/components/theme-configurator/main.vue
Normal file
130
examples/components/theme-configurator/main.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="editor-main" ref="mainPanel">
|
||||
<!-- <span>{{configName}}</span> -->
|
||||
<div v-for="(config, key) in configByOrder" :key="key">
|
||||
<span
|
||||
v-if="showCategory(config.category, key + 1)"
|
||||
class="category-name"
|
||||
>
|
||||
{{config.category}}
|
||||
</span>
|
||||
<component
|
||||
:is="editorComponent(config.type)"
|
||||
:componentName=configName
|
||||
:config=config
|
||||
:userConfig=userConfigByType
|
||||
:golbalValue=globalValue
|
||||
@onChange=onChange
|
||||
>
|
||||
</component>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.editor-main {
|
||||
padding: 0 18px 15px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.category-name {
|
||||
color: #C0C4CC;
|
||||
font-size: 18px;
|
||||
display: block;
|
||||
margin: 13px 0 3px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import ColorEditor from './editor/color';
|
||||
import fontWeightEditor from './editor/fontWeight';
|
||||
import fontSizeEditor from './editor/fontSize';
|
||||
import fontLineHeightEditor from './editor/fontLineHeight';
|
||||
import borderRadiusEditor from './editor/borderRadius';
|
||||
import boxShadowEditor from './editor/boxShadow';
|
||||
import simpleTextEditor from './editor/simpleText';
|
||||
import { filterConfigType } from './utils/utils.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColorEditor,
|
||||
fontSizeEditor,
|
||||
fontLineHeightEditor,
|
||||
simpleTextEditor,
|
||||
borderRadiusEditor,
|
||||
boxShadowEditor,
|
||||
fontWeightEditor
|
||||
},
|
||||
props: {
|
||||
defaultConfig: {
|
||||
type: Array
|
||||
},
|
||||
currentConfig: {
|
||||
type: Object
|
||||
},
|
||||
userConfig: {
|
||||
type: Object
|
||||
},
|
||||
globalValue: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
configName() {
|
||||
return this.currentConfig.name;
|
||||
},
|
||||
userConfigByType() {
|
||||
return this.userConfig[filterConfigType(this.configName)];
|
||||
},
|
||||
configByOrder() {
|
||||
return this.currentConfig.config.sort((a, b) => (a.order - b.order));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
editorComponent(type) {
|
||||
switch (type) {
|
||||
case 'color':
|
||||
return ColorEditor;
|
||||
case 'fontWeight':
|
||||
return fontWeightEditor;
|
||||
case 'fontSize':
|
||||
return fontSizeEditor;
|
||||
case 'fontLineHeight':
|
||||
return fontLineHeightEditor;
|
||||
case 'borderRadius':
|
||||
return borderRadiusEditor;
|
||||
case 'boxShadow':
|
||||
return boxShadowEditor;
|
||||
default:
|
||||
return simpleTextEditor;
|
||||
}
|
||||
},
|
||||
onChange(e) {
|
||||
this.$emit('onChange', e);
|
||||
},
|
||||
showCategory(name, key) {
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
if (!this.categoryDisplay[name] || this.categoryDisplay[name] === key) {
|
||||
this.categoryDisplay[name] = key;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
categoryDisplay: {}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
currentConfig: {
|
||||
handler() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.mainPanel.scrollTo(0, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
27
examples/components/theme-configurator/shortcut.vue
Normal file
27
examples/components/theme-configurator/shortcut.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
downloading: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
shortcut(e) {
|
||||
if (e.keyCode === 90 && (e.ctrlKey || e.metaKey)) {
|
||||
if (e.shiftKey) {
|
||||
this.redo();
|
||||
} else {
|
||||
this.undo();
|
||||
}
|
||||
}
|
||||
},
|
||||
enableShortcut() {
|
||||
document.addEventListener('keydown', this.shortcut);
|
||||
},
|
||||
disableShortcut() {
|
||||
document.removeEventListener('keydown', this.shortcut);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
59
examples/components/theme-configurator/utils/boxShadow.js
Normal file
59
examples/components/theme-configurator/utils/boxShadow.js
Normal file
@@ -0,0 +1,59 @@
|
||||
const VALUES_REG = /,(?![^\(]*\))/;
|
||||
const PARTS_REG = /\s(?![^(]*\))/;
|
||||
const LENGTH_REG = /^[0-9]+[a-zA-Z%]+?$/;
|
||||
|
||||
const parseValue = str => {
|
||||
const parts = str.split(PARTS_REG);
|
||||
const inset = parts.includes('inset');
|
||||
const last = parts.slice(-1)[0];
|
||||
const color = !isLength(last) ? last : undefined;
|
||||
|
||||
const nums = parts
|
||||
.filter(n => n !== 'inset')
|
||||
.filter(n => n !== color)
|
||||
.map(toNum);
|
||||
const [ offsetX, offsetY, blurRadius, spreadRadius ] = nums;
|
||||
|
||||
return {
|
||||
inset,
|
||||
offsetX,
|
||||
offsetY,
|
||||
blurRadius,
|
||||
spreadRadius,
|
||||
color
|
||||
};
|
||||
};
|
||||
|
||||
const stringifyValue = obj => {
|
||||
const {
|
||||
inset,
|
||||
offsetX = 0,
|
||||
offsetY = 0,
|
||||
blurRadius = 0,
|
||||
spreadRadius,
|
||||
color
|
||||
} = obj || {};
|
||||
|
||||
return [
|
||||
(inset ? 'inset' : null),
|
||||
offsetX,
|
||||
offsetY,
|
||||
blurRadius,
|
||||
spreadRadius,
|
||||
color
|
||||
].filter(v => v !== null && v !== undefined)
|
||||
.map(toPx)
|
||||
.map(s => ('' + s).trim())
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
const isLength = v => v === '0' || LENGTH_REG.test(v);
|
||||
const toNum = v => {
|
||||
if (!/px$/.test(v) && v !== '0') return v;
|
||||
const n = parseFloat(v);
|
||||
return !isNaN(n) ? n : v;
|
||||
};
|
||||
const toPx = n => typeof n === 'number' && n !== 0 ? (n + 'px') : n;
|
||||
|
||||
export const parse = str => str.split(VALUES_REG).map(s => s.trim()).map(parseValue);
|
||||
export const stringify = arr => arr.map(stringifyValue).join(', ');
|
||||
68
examples/components/theme-configurator/utils/utils.js
Normal file
68
examples/components/theme-configurator/utils/utils.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import deepmerge from 'deepmerge';
|
||||
import constant from '../../../i18n/theme-editor.json';
|
||||
|
||||
export const filterConfigType = (name) => {
|
||||
switch (name) {
|
||||
case 'color':
|
||||
case 'typography':
|
||||
case 'border':
|
||||
return 'global';
|
||||
default:
|
||||
return 'local';
|
||||
}
|
||||
};
|
||||
|
||||
export const filterGlobalValue = (defaultConfig, userConfig) => {
|
||||
const valueObject = {};
|
||||
const globalArr = ['color', 'typography', 'border'];
|
||||
globalArr.forEach((global) => {
|
||||
const configObj = {};
|
||||
defaultConfig
|
||||
.find(config => (config.name === global))
|
||||
.config.forEach(c => (configObj[c.key] = deepmerge({}, c)));
|
||||
valueObject[global] = configObj;
|
||||
Object.keys(configObj).forEach((c) => {
|
||||
if (userConfig.global[c]) {
|
||||
configObj[c].value = userConfig.global[c];
|
||||
}
|
||||
});
|
||||
});
|
||||
return valueObject;
|
||||
};
|
||||
|
||||
export const getStyleDisplayValue = (displayValue, global) => {
|
||||
if (displayValue.startsWith('$')) {
|
||||
return global[displayValue].value;
|
||||
}
|
||||
return displayValue;
|
||||
};
|
||||
|
||||
const getLang = () => {
|
||||
return location.hash.replace('#', '').split('/')[1] || 'zh-CN';
|
||||
};
|
||||
|
||||
const getNameFromI18N = (name) => {
|
||||
const lang = getLang();
|
||||
return constant.filter(config => config.lang === lang)[0][name];
|
||||
};
|
||||
|
||||
export const getVariableDisplayName = (key) => {
|
||||
return getNameFromI18N('variable-name')[key] || key;
|
||||
};
|
||||
|
||||
export const getStyleDisplayName = (config, componentName) => {
|
||||
const displayNameMap = getNameFromI18N('display-name');
|
||||
if (config.name) {
|
||||
return getVariableDisplayName(config.key.replace('$--', ''));
|
||||
}
|
||||
let displayName = config.key.replace(`$--${componentName}-`, '');
|
||||
Object.keys(displayNameMap).forEach((name) => {
|
||||
displayName = displayName.replace(name, displayNameMap[name]);
|
||||
});
|
||||
displayName = displayName.replace(/-/g, ' ');
|
||||
return displayName.trim();
|
||||
};
|
||||
|
||||
export const getActionDisplayName = (key) => {
|
||||
return getNameFromI18N('action')[key] || key;
|
||||
};
|
||||
157
examples/components/theme-picker.vue
Normal file
157
examples/components/theme-picker.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<el-color-picker
|
||||
class="theme-picker"
|
||||
popper-class="theme-picker-dropdown"
|
||||
v-model="theme"></el-color-picker>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.theme-picker {
|
||||
height: 80px;
|
||||
display: inline-block;
|
||||
// @utils-vertical-center;
|
||||
}
|
||||
|
||||
.theme-picker .el-color-picker__trigger {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.theme-picker-dropdown .el-color-dropdown__link-btn {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Element from 'main/index.js';
|
||||
const { version } = Element;
|
||||
|
||||
const ORIGINAL_THEME = '#409EFF';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
chalk: '', // content of theme-chalk css
|
||||
docs: '', // content of docs css
|
||||
theme: ORIGINAL_THEME
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
theme(val, oldVal) {
|
||||
if (typeof val !== 'string') return;
|
||||
const themeCluster = this.getThemeCluster(val.replace('#', ''));
|
||||
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''));
|
||||
const getHandler = (variable, id) => {
|
||||
return () => {
|
||||
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''));
|
||||
let 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;
|
||||
};
|
||||
};
|
||||
|
||||
const chalkHandler = getHandler('chalk', 'chalk-style');
|
||||
const docsHandler = getHandler('docs', 'docs-style');
|
||||
|
||||
if (!this.chalk) {
|
||||
const url = `https://unpkg.com/element-ui@${ version }/lib/theme-chalk/index.css`;
|
||||
this.getCSSString(url, chalkHandler, 'chalk');
|
||||
} else {
|
||||
chalkHandler();
|
||||
}
|
||||
|
||||
if (!this.docs) {
|
||||
const links = [].filter.call(document.querySelectorAll('link'), link => {
|
||||
return /docs\..+\.css/.test(link.href || '');
|
||||
});
|
||||
links[0] && this.getCSSString(links[0].href, docsHandler, 'docs');
|
||||
} else {
|
||||
docsHandler();
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
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, callback, variable) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '');
|
||||
callback();
|
||||
}
|
||||
};
|
||||
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>
|
||||
280
examples/components/theme/basic-tokens-preview.vue
Normal file
280
examples/components/theme/basic-tokens-preview.vue
Normal file
@@ -0,0 +1,280 @@
|
||||
<style lang="scss">
|
||||
.component-preview {
|
||||
.heading>div{
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight:400;
|
||||
padding: 0 20px
|
||||
}
|
||||
.paragraph {
|
||||
padding: 0 20px
|
||||
}
|
||||
.demo-color-box {
|
||||
margin: 0;
|
||||
}
|
||||
.color {
|
||||
margin-right: -12%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="component-preview">
|
||||
<h4>Color</h4>
|
||||
<div class="color">
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="4" v-for="(color, key) in colorLine" :key="key">
|
||||
<div class="demo-color-box" :style="{ background: dataProxy(color) }">
|
||||
{{color}}
|
||||
<div class="value">{{dataProxy(color)}}</div>
|
||||
<div class="bg-color-sub">
|
||||
<div
|
||||
class="bg-success-sub-item"
|
||||
v-for="(item, key) in Array(2)"
|
||||
:key="key"
|
||||
:style="{ background: tintColor(dataProxy(color), (key + 8) / 10) }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="4">
|
||||
<div class="demo-color-box demo-color-box-other" :style="{ background: color_text_primary }">
|
||||
Primary Text
|
||||
<div class="value">{{color_text_primary}}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="demo-color-box demo-color-box-other" :style="{ background: color_text_regular }">
|
||||
Regular Text
|
||||
<div class="value">{{color_text_regular}}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="demo-color-box demo-color-box-other" :style="{ background: color_text_secondary }">
|
||||
Secondary Text
|
||||
<div class="value">{{color_text_secondary}}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="demo-color-box demo-color-box-other" :style="{ background: color_text_placeholder }">
|
||||
Placeholder
|
||||
<div class="value">{{color_text_placeholder}}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="4">
|
||||
<div
|
||||
class="demo-color-box demo-color-box-other demo-color-box-lite"
|
||||
:style="{ background: border_color_base }"
|
||||
>
|
||||
Border Base
|
||||
<div class="value">{{border_color_base}}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div
|
||||
class="demo-color-box demo-color-box-other demo-color-box-lite"
|
||||
:style="{ background: border_color_light }"
|
||||
>
|
||||
Border Light
|
||||
<div class="value">{{border_color_light}}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div
|
||||
class="demo-color-box demo-color-box-other demo-color-box-lite"
|
||||
:style="{ background: border_color_lighter }"
|
||||
>
|
||||
Border Lighter
|
||||
<div class="value">{{border_color_lighter}}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div
|
||||
class="demo-color-box demo-color-box-other demo-color-box-lite"
|
||||
:style="{ background: border_color_extra_light }"
|
||||
>
|
||||
Border Extralight
|
||||
<div class="value">{{border_color_extra_light}}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="4">
|
||||
<div class="demo-color-box demo-color-box-other" :style="{ background: color_black }">
|
||||
Background B
|
||||
<div class="value">{{color_black}}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div
|
||||
class="demo-color-box demo-color-box-other"
|
||||
:style="{ background: color_white, color: '#303133', border: '1px solid #eee' }"
|
||||
>
|
||||
Background W
|
||||
<div class="value">{{color_white}}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="demo-color-box demo-color-box-other bg-transparent">
|
||||
Background
|
||||
<div class="value">Transparent</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<h4>Typography</h4>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="6" class="heading">
|
||||
<div :style="{ fontSize: font_size_extra_large }">Heading1</div>
|
||||
<div :style="{ fontSize: font_size_large }">Heading2</div>
|
||||
<div :style="{ fontSize: font_size_medium }">Heading3</div>
|
||||
<div :style="{ fontSize: font_size_base }">Heading4</div>
|
||||
<div :style="{ fontSize: font_size_small }">Heading5</div>
|
||||
<div :style="{ fontSize: font_size_extra_small }">Heading6</div>
|
||||
</el-col>
|
||||
<el-col :span="9">
|
||||
<div class="title">Example body text</div>
|
||||
<p
|
||||
class="paragraph"
|
||||
:style="{
|
||||
fontSize: font_size_base,
|
||||
fontWeight: font_weight_primary,
|
||||
lineHeight: font_line_height_primary
|
||||
}" >
|
||||
With MySpace becoming more popular every day, there is the constant need to be different. There are millions of users. If MySpace layouts are chosen well, then you can enhance your profile a great deal.</p>
|
||||
</el-col>
|
||||
<el-col :span="9">
|
||||
<div class="title">Example small text</div>
|
||||
<p
|
||||
class="paragraph"
|
||||
:style="{
|
||||
fontSize: font_size_small,
|
||||
fontWeight: font_weight_secondary,
|
||||
lineHeight: font_line_height_secondary
|
||||
}" >
|
||||
Computers have become ubiquitous in almost every facet of our lives. At work, desk jockeys spend hours in front of their desktops, while delivery people scan bar codes with handhelds and workers in the field stay in touch with the central office via their notebooks. Computer hardware weaves itself through the fabric of our lives.</p>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bus from '../../bus';
|
||||
import { tintColor } from '../../color.js';
|
||||
import {
|
||||
ACTION_COMPONECT_SELECT,
|
||||
ACTION_USER_CONFIG_UPDATE
|
||||
} from './constant.js';
|
||||
|
||||
const original = {
|
||||
'color_primary': '#409EFF',
|
||||
'color_success': '#67C23A',
|
||||
'color_warning': '#E6A23C',
|
||||
'color_danger': '#F56C6C',
|
||||
'color_info': '#909399',
|
||||
'color_white': '#FFFFFF',
|
||||
'color_black': '#000000',
|
||||
'color_text_primary': '#303133',
|
||||
'color_text_regular': '#606266',
|
||||
'color_text_secondary': '#909399',
|
||||
'color_text_placeholder': '#C0C4CC',
|
||||
'border_color_base': '#DCDFE6',
|
||||
'border_color_light': '#E4E7ED',
|
||||
'border_color_lighter': '#EBEEF5',
|
||||
'border_color_extra_light': '#F2F6FC',
|
||||
'font_size_extra_large': '20px',
|
||||
'font_size_large': '18px',
|
||||
'font_size_medium': '16px',
|
||||
'font_size_base': '14px',
|
||||
'font_size_small': '13px',
|
||||
'font_size_extra_small': '12px',
|
||||
'font_weight_primary': 500,
|
||||
'font_weight_secondary': 100,
|
||||
'font_line_height_primary': '24px',
|
||||
'font_line_height_secondary': '16px'
|
||||
};
|
||||
|
||||
export default {
|
||||
created() {
|
||||
bus.$on(ACTION_USER_CONFIG_UPDATE, this.setGlobal);
|
||||
bus.$on(ACTION_COMPONECT_SELECT, (val) => {
|
||||
this.$nextTick(() => {
|
||||
const getSelectElement = Array.from(document.querySelectorAll('h4')).filter((el) => (el.innerText.toLowerCase() === val));
|
||||
if (getSelectElement[0]) {
|
||||
const elementTop = getSelectElement[0].getBoundingClientRect().top;
|
||||
window.scrollTo(0, window.pageYOffset + elementTop - 20); // 20 for padding
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.setGlobal();
|
||||
},
|
||||
methods: {
|
||||
tintColor(a, b) {
|
||||
return tintColor(a, b);
|
||||
},
|
||||
dataProxy(value) {
|
||||
return this[`color_${value.toLowerCase()}`];
|
||||
},
|
||||
setGlobal() {
|
||||
if (window.userThemeConfig) {
|
||||
this.global = window.userThemeConfig.global;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
global: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
Object.keys(original).forEach((v) => {
|
||||
const key = `$--${v.replace(/_/g, '-')}`;
|
||||
if (value[key]) {
|
||||
this[v] = value[key];
|
||||
} else {
|
||||
this[v] = original[v];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
global: {},
|
||||
colorLine: ['Primary', 'Success', 'Warning', 'Danger', 'Info'],
|
||||
'color_primary': '',
|
||||
'color_success': '',
|
||||
'color_warning': '',
|
||||
'color_danger': '',
|
||||
'color_info': '',
|
||||
'color_white': '',
|
||||
'color_black': '',
|
||||
'color_text_primary': '',
|
||||
'color_text_regular': '',
|
||||
'color_text_secondary': '',
|
||||
'color_text_placeholder': '',
|
||||
'border_color_base': '',
|
||||
'border_color_light': '',
|
||||
'border_color_lighter': '',
|
||||
'border_color_extra_light': '',
|
||||
'font_size_extra_large': '',
|
||||
'font_size_large': '',
|
||||
'font_size_medium': '',
|
||||
'font_size_base': '',
|
||||
'font_size_small': '',
|
||||
'font_size_extra_small': '',
|
||||
'font_weight_primary': 0,
|
||||
'font_weight_secondary': 0,
|
||||
'font_line_height_primary': '',
|
||||
'font_line_height_secondary': ''
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
539
examples/components/theme/components-preview.vue
Normal file
539
examples/components/theme/components-preview.vue
Normal file
@@ -0,0 +1,539 @@
|
||||
<style lang="scss">
|
||||
.component-preview {
|
||||
padding-right: 10px;
|
||||
&:last-of-type {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
h4 {
|
||||
font-size: 20px;
|
||||
margin: 40px 0 20px;
|
||||
color: #909399
|
||||
}
|
||||
.demo-item {
|
||||
margin-top: 10px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.demo-line {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n) {
|
||||
background-color: #99a9bf;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n + 1) {
|
||||
background-color: #d3dce6;
|
||||
}
|
||||
|
||||
.el-avatar:not(:last-child) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.avatar-demo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="component-preview">
|
||||
<h4>Button</h4>
|
||||
<el-row class="demo-line">
|
||||
<el-button>Default</el-button>
|
||||
<el-button type="primary">Primary</el-button>
|
||||
<el-button type="success">Success</el-button>
|
||||
<el-button type="info">Info</el-button>
|
||||
<el-button type="warning">Warning</el-button>
|
||||
<el-button type="danger">Danger</el-button>
|
||||
</el-row>
|
||||
<el-row class="demo-line">
|
||||
<el-button plain>Plain</el-button>
|
||||
<el-button type="primary" plain>Primary</el-button>
|
||||
<el-button type="success" plain>Success</el-button>
|
||||
<el-button type="info" plain>Info</el-button>
|
||||
<el-button type="warning" plain>Warning</el-button>
|
||||
<el-button type="danger" plain>Danger</el-button>
|
||||
</el-row>
|
||||
<el-row class="demo-line">
|
||||
<el-button round>Round</el-button>
|
||||
<el-button type="primary" round>Primary</el-button>
|
||||
<el-button type="success" round>Success</el-button>
|
||||
<el-button type="info" round>Info</el-button>
|
||||
<el-button type="warning" round>Warning</el-button>
|
||||
<el-button type="danger" round>Danger</el-button>
|
||||
</el-row>
|
||||
<el-row class="demo-line">
|
||||
<el-button icon="el-icon-search" circle></el-button>
|
||||
<el-button type="primary" icon="el-icon-edit" circle></el-button>
|
||||
<el-button type="success" icon="el-icon-check" circle></el-button>
|
||||
<el-button type="info" icon="el-icon-message" circle></el-button>
|
||||
<el-button type="warning" icon="el-icon-star-off" circle></el-button>
|
||||
<el-button type="danger" icon="el-icon-delete" circle></el-button>
|
||||
</el-row>
|
||||
<el-row class="demo-line">
|
||||
<el-button>Default</el-button>
|
||||
<el-button size="medium">Medium</el-button>
|
||||
<el-button size="small">Small</el-button>
|
||||
<el-button size="mini">Mini</el-button>
|
||||
</el-row>
|
||||
<h4>Radio</h4>
|
||||
<el-row class="demo-line">
|
||||
<el-radio v-model="radio" label="1">Option A</el-radio>
|
||||
<el-radio v-model="radio" label="2">Option B</el-radio>
|
||||
</el-row>
|
||||
<el-row class="demo-line">
|
||||
<el-radio-group v-model="radio1">
|
||||
<el-radio-button label="New York"></el-radio-button>
|
||||
<el-radio-button label="Washington"></el-radio-button>
|
||||
<el-radio-button label="Los Angeles"></el-radio-button>
|
||||
<el-radio-button label="Chicago"></el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-row>
|
||||
<el-row class="demo-line">
|
||||
<el-radio v-model="radio2" label="1" border>Option A</el-radio>
|
||||
<el-radio v-model="radio2" label="2" border>Option B</el-radio>
|
||||
</el-row>
|
||||
<h4>Checkbox</h4>
|
||||
<el-row class="demo-line">
|
||||
<el-checkbox v-model="checked">Option</el-checkbox>
|
||||
</el-row>
|
||||
<el-row class="demo-line">
|
||||
<el-checkbox-group v-model="checked1">
|
||||
<el-checkbox-button v-for="city in ['Shanghai', 'Beijing', 'Guangzhou', 'Shenzhen']" :label="city" :key="city">{{city}}</el-checkbox-button>
|
||||
</el-checkbox-group>
|
||||
</el-row>
|
||||
<el-row class="demo-line">
|
||||
<el-checkbox v-model="checked2" label="Option1" border></el-checkbox>
|
||||
</el-row>
|
||||
<h4>Input</h4>
|
||||
<el-row style="width: 180px">
|
||||
<el-input placeholder="Please input" v-model="input"></el-input>
|
||||
</el-row>
|
||||
<h4>InputNumber</h4>
|
||||
<el-row>
|
||||
<el-input-number v-model="inputNumber" :min="1" :max="10"></el-input-number>
|
||||
</el-row>
|
||||
<h4>Select</h4>
|
||||
<el-row>
|
||||
<el-select v-model="selectValue" placeholder="Select">
|
||||
<el-option
|
||||
v-for="item in selectOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-row>
|
||||
<h4>Cascader</h4>
|
||||
<el-row>
|
||||
<el-cascader :options="cascadeOptions" v-model="cascaderValue"></el-cascader>
|
||||
</el-row>
|
||||
<h4>Switch</h4>
|
||||
<el-row>
|
||||
<el-switch v-model="switchValue"></el-switch>
|
||||
<el-switch
|
||||
style="margin-left: 40px"
|
||||
v-model="switchValue"
|
||||
active-text="Pay by month"
|
||||
inactive-text="Pay by year">
|
||||
</el-switch>
|
||||
</el-row>
|
||||
<h4>Slider</h4>
|
||||
<el-row style="width: 380px">
|
||||
<el-slider v-model="slider"></el-slider>
|
||||
</el-row>
|
||||
<h4>DatePicker</h4>
|
||||
<el-row>
|
||||
<el-date-picker v-model="datePicker" type="date"></el-date-picker>
|
||||
</el-row>
|
||||
<h4>Rate</h4>
|
||||
<el-row>
|
||||
<el-rate class="demo-line" v-model="rate"></el-rate>
|
||||
<el-rate
|
||||
class="demo-line"
|
||||
v-model="rate"
|
||||
show-score
|
||||
text-color="#ff9900"
|
||||
score-template="{value} points">
|
||||
</el-rate>
|
||||
</el-row>
|
||||
<h4>Transfer</h4>
|
||||
<el-row>
|
||||
<el-transfer v-model="transfer" filterable :data="transferData">
|
||||
<el-button class="transfer-footer" slot="left-footer" size="small">Operation</el-button>
|
||||
<el-button class="transfer-footer" slot="right-footer" size="small">Operation</el-button>
|
||||
</el-transfer>
|
||||
</el-row>
|
||||
<h4>Table</h4>
|
||||
<el-row>
|
||||
<el-table :data="tableData" style="width: 70%">
|
||||
<el-table-column prop="date" label="Date" width="180"></el-table-column>
|
||||
<el-table-column prop="name" label="Name" width="180"></el-table-column>
|
||||
<el-table-column prop="address" label="Address"></el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
<h4>Tag</h4>
|
||||
<el-row>
|
||||
<el-tag class="demo-item" closable>Tag One</el-tag>
|
||||
<el-tag class="demo-item" closable type="success">Tag Two</el-tag>
|
||||
<el-tag class="demo-item" closable type="info">Tag Three</el-tag>
|
||||
<el-tag class="demo-item" closable type="warning">Tag Four</el-tag>
|
||||
<el-tag class="demo-item" closable type="danger">Tag Five</el-tag>
|
||||
</el-row>
|
||||
<h4>Progress</h4>
|
||||
<el-row style="width: 380px">
|
||||
<el-progress :percentage="20"></el-progress>
|
||||
<el-progress :percentage="60" status="exception"></el-progress>
|
||||
<el-progress :percentage="100" status="success"></el-progress>
|
||||
</el-row>
|
||||
<h4>Tree</h4>
|
||||
<el-row style="width: 380px">
|
||||
<el-tree :data="treeData" :props="defaultTreeProps" ></el-tree>
|
||||
</el-row>
|
||||
<h4>Pagination</h4>
|
||||
<el-row>
|
||||
<el-pagination layout="prev, pager, next" :total="1000"></el-pagination>
|
||||
</el-row>
|
||||
<h4>Badge</h4>
|
||||
<el-row>
|
||||
<el-badge :value="12" class="demo-item">
|
||||
<el-button size="small">comments</el-button>
|
||||
</el-badge>
|
||||
<el-badge :value="3" class="demo-item">
|
||||
<el-button size="small">replies</el-button>
|
||||
</el-badge>
|
||||
<el-badge :value="1" class="demo-item" type="primary">
|
||||
<el-button size="small">comments</el-button>
|
||||
</el-badge>
|
||||
<el-badge :value="2" class="demo-item" type="warning">
|
||||
<el-button size="small">replies</el-button>
|
||||
</el-badge>
|
||||
</el-row>
|
||||
<h4>Alert</h4>
|
||||
<el-row style="width: 380px;">
|
||||
<el-alert class="demo-item" title="success alert" type="success" show-icon></el-alert>
|
||||
<el-alert class="demo-item" title="info alert" type="info" close-text="Gotcha" show-icon></el-alert>
|
||||
<el-alert class="demo-item" title="warning alert" type="warning" show-icon></el-alert>
|
||||
<el-alert
|
||||
class="demo-item"
|
||||
title="error alert"
|
||||
type="error"
|
||||
description="more text description"
|
||||
show-icon>
|
||||
</el-alert>
|
||||
</el-row>
|
||||
<h4>Loading</h4>
|
||||
<el-row>
|
||||
<el-table :data="tableData" style="width: 90%" v-loading="true">
|
||||
<el-table-column prop="date" label="Date" width="180"></el-table-column>
|
||||
<el-table-column prop="name" label="Name" width="180"></el-table-column>
|
||||
<el-table-column prop="address" label="Address"></el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
<h4>Message</h4>
|
||||
<el-row>
|
||||
<div role="alert" class="demo-item el-message el-message--success el-message-fade-leave-active el-message-fade-leave-to" style="top: 0;left: 0;width: 100px; opacity: 1; position: relative;transform: none;"><i class="el-message__icon el-icon-success"></i><p class="el-message__content">Congrats, this is a success message.</p><!----></div>
|
||||
<div role="alert" class="demo-item el-message el-message--warning el-message-fade-leave-active el-message-fade-leave-to" style="top: 0;left: 0;width: 100px; opacity: 1; position: relative;transform: none;"><i class="el-message__icon el-icon-warning"></i><p class="el-message__content">Warning, this is a warning message.</p><!----></div>
|
||||
<div role="alert" class="demo-item el-message el-message--info el-message-fade-leave-active el-message-fade-leave-to" style="top: 0;left: 0;width: 100px; opacity: 1; position: relative;transform: none;"><i class="el-message__icon el-icon-info"></i><p class="el-message__content">This is a message.</p><!----></div>
|
||||
<div role="alert" class="demo-item el-message el-message--error is-closable el-message-fade-leave-active el-message-fade-leave-to" style="top: 0;left: 0;width: 100px; opacity: 1; position: relative;transform: none;"><i class="el-message__icon el-icon-error"></i><p class="el-message__content">Oops, this is a error message.</p><i class="el-message__closeBtn el-icon-close"></i></div>
|
||||
</el-row>
|
||||
<h4>MessageBox</h4>
|
||||
<el-row>
|
||||
<div class="el-message-box"><div class="el-message-box__header"><div class="el-message-box__title"><!----><span>Warning</span></div><button type="button" aria-label="Close" class="el-message-box__headerbtn"><i class="el-message-box__close el-icon-close"></i></button></div><div class="el-message-box__content"><div class="el-message-box__status el-icon-warning"></div><div class="el-message-box__message"><p>This will permanently delete the file. Continue?</p></div><div class="el-message-box__input" style="display: none;"><div class="el-input"><!----><input type="text" autocomplete="off" placeholder="" class="el-input__inner"><!----><!----><!----></div><div class="el-message-box__errormsg" style="visibility: hidden;"></div></div></div><div class="el-message-box__btns"><button type="button" class="el-button el-button--default el-button--small"><!----><!----><span>
|
||||
Cancel
|
||||
</span></button><button type="button" class="el-button el-button--default el-button--small el-button--primary "><!----><!----><span>
|
||||
OK
|
||||
</span></button></div></div>
|
||||
</el-row>
|
||||
<h4>Notification</h4>
|
||||
<el-row>
|
||||
<div role="alert" class="el-notification right" style="position: relative; left: 0;"><!----><div class="el-notification__group"><span class="el-notification__title">Notification</span><div class="el-notification__content"><div>This is a message </div></div><div class="el-notification__closeBtn el-icon-close"></div></div></div>
|
||||
</el-row>
|
||||
<h4>Menu</h4>
|
||||
<el-row>
|
||||
<el-menu :default-active="menu" class="el-menu-demo" mode="horizontal">
|
||||
<el-menu-item index="1">Processing Center</el-menu-item>
|
||||
<el-submenu index="2">
|
||||
<template slot="title">Workspace</template>
|
||||
<el-menu-item index="2-1">item one</el-menu-item>
|
||||
<el-menu-item index="2-2">item two</el-menu-item>
|
||||
<el-menu-item index="2-3">item three</el-menu-item>
|
||||
<el-submenu index="2-4">
|
||||
<template slot="title">item four</template>
|
||||
<el-menu-item index="2-4-1">item one</el-menu-item>
|
||||
<el-menu-item index="2-4-2">item two</el-menu-item>
|
||||
<el-menu-item index="2-4-3">item three</el-menu-item>
|
||||
</el-submenu>
|
||||
</el-submenu>
|
||||
<el-menu-item index="3" disabled>Info</el-menu-item>
|
||||
<el-menu-item index="4">
|
||||
<a href="https://www.ele.me" target="_blank">Orders</a>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
<el-menu
|
||||
default-active="2"
|
||||
class="demo-line"
|
||||
>
|
||||
<el-submenu index="1">
|
||||
<template slot="title">
|
||||
<i class="el-icon-location"></i>
|
||||
<span>Navigator One</span>
|
||||
</template>
|
||||
<el-menu-item-group title="Group One">
|
||||
<el-menu-item index="1-1">item one</el-menu-item>
|
||||
<el-menu-item index="1-2">item one</el-menu-item>
|
||||
</el-menu-item-group>
|
||||
<el-menu-item-group title="Group Two">
|
||||
<el-menu-item index="1-3">item three</el-menu-item>
|
||||
</el-menu-item-group>
|
||||
<el-submenu index="1-4">
|
||||
<template slot="title">item four</template>
|
||||
<el-menu-item index="1-4-1">item one</el-menu-item>
|
||||
</el-submenu>
|
||||
</el-submenu>
|
||||
<el-menu-item index="2">
|
||||
<i class="el-icon-menu"></i>
|
||||
<span>Navigator Two</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="3" disabled>
|
||||
<i class="el-icon-document"></i>
|
||||
<span>Navigator Three</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="4">
|
||||
<i class="el-icon-setting"></i>
|
||||
<span>Navigator Four</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-row>
|
||||
<h4>Tabs</h4>
|
||||
<el-row>
|
||||
<el-tabs v-model="tab" class="demo-item">
|
||||
<el-tab-pane label="User" name="first">User</el-tab-pane>
|
||||
<el-tab-pane label="Config" name="second">Config</el-tab-pane>
|
||||
<el-tab-pane label="Role" name="third">Role</el-tab-pane>
|
||||
<el-tab-pane label="Task" name="fourth">Task</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-tabs type="card" class="demo-item">
|
||||
<el-tab-pane label="User">User</el-tab-pane>
|
||||
<el-tab-pane label="Config">Config</el-tab-pane>
|
||||
<el-tab-pane label="Role">Role</el-tab-pane>
|
||||
<el-tab-pane label="Task">Task</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-row>
|
||||
<h4>Dialog</h4>
|
||||
<el-row>
|
||||
<div role="dialog" aria-modal="true" aria-label="Tips" class="el-dialog" style="margin: 0"><div class="el-dialog__header"><span class="el-dialog__title">Tips</span><button type="button" aria-label="Close" class="el-dialog__headerbtn"><i class="el-dialog__close el-icon el-icon-close"></i></button></div><div class="el-dialog__body"><span>This is a message</span> </div><div class="el-dialog__footer"><span class="dialog-footer"><button type="button" class="el-button el-button--default"><!----><!----><span>Cancel</span></button> <button type="button" class="el-button el-button--primary"><!----><!----><span>Confirm</span></button></span></div></div>
|
||||
</el-row>
|
||||
<h4>Tooltip</h4>
|
||||
<el-row>
|
||||
<div role="tooltip" x-placement="top" class="el-tooltip__popper is-dark" style="position: relative; width: 40px;text-align: center;">Dark<div x-arrow="" class="popper__arrow"></div>
|
||||
</div>
|
||||
<div role="tooltip" x-placement="top" class="el-tooltip__popper is-light" style="margin-top: 10px;position: relative; width: 40px;text-align: center;">Light<div x-arrow="" class="popper__arrow"></div>
|
||||
</div>
|
||||
</el-row>
|
||||
<h4>Popover</h4>
|
||||
<el-row>
|
||||
<div role="tooltip" x-placement="top" id="el-popover-2936" aria-hidden="true" class="el-popover el-popper el-popover--plain" tabindex="0" style="width: 200px; position: relative; "><div class="el-popover__title">Title</div>this is content, this is content, this is content<div x-arrow="" class="popper__arrow"></div></div>
|
||||
</el-row>
|
||||
<h4>Card</h4>
|
||||
<el-row>
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>Card name</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-row>
|
||||
<h4>Carousel</h4>
|
||||
<el-row>
|
||||
<el-carousel height="150px">
|
||||
<el-carousel-item v-for="item in 4" :key="item">
|
||||
<h3>{{ item }}</h3>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</el-row>
|
||||
<h4>Collapse</h4>
|
||||
<el-row>
|
||||
<el-collapse v-model="collapse">
|
||||
<el-collapse-item title="Consistent" name="1">
|
||||
<div>Consistent with real life: in line with the process and logic of real life, and comply with languages and habits that the users are used to;</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="Feedback" name="2">
|
||||
<div>Operation feedback: enable the users to clearly perceive their operations by style updates and interactive effects;</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-row>
|
||||
<h4>Avatar</h4>
|
||||
<el-row class="demo-line avatar-demo">
|
||||
<el-avatar icon="el-icon-user-solid"/>
|
||||
<el-avatar> avatar </el-avatar>
|
||||
<el-avatar shape="square" fit="contain" :src="avatarData.url"></el-avatar>
|
||||
<el-avatar size="large"> large </el-avatar>
|
||||
<el-avatar size="medium"> medium </el-avatar>
|
||||
<el-avatar size="small"> small </el-avatar>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
radio: '1',
|
||||
radio1: 'Washington',
|
||||
radio2: '1',
|
||||
checked: true,
|
||||
checked1: ['Shanghai'],
|
||||
checked2: true,
|
||||
input: 'Element',
|
||||
inputNumber: 1,
|
||||
selectOptions: [
|
||||
{
|
||||
value: 'Option1',
|
||||
label: 'Option1'
|
||||
},
|
||||
{
|
||||
value: 'Option2',
|
||||
label: 'Option2'
|
||||
},
|
||||
{
|
||||
value: 'Option3',
|
||||
label: 'Option3'
|
||||
},
|
||||
{
|
||||
value: 'Option4',
|
||||
label: 'Option4'
|
||||
},
|
||||
{
|
||||
value: 'Option5',
|
||||
label: 'Option5'
|
||||
}
|
||||
],
|
||||
selectValue: '',
|
||||
cascadeOptions: [
|
||||
{
|
||||
value: 'guide',
|
||||
label: 'Guide',
|
||||
children: [
|
||||
{
|
||||
value: 'disciplines',
|
||||
label: 'Disciplines',
|
||||
children: [
|
||||
{
|
||||
value: 'consistency',
|
||||
label: 'Consistency'
|
||||
},
|
||||
{
|
||||
value: 'feedback',
|
||||
label: 'Feedback'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: 'resource',
|
||||
label: 'Resource',
|
||||
children: [
|
||||
{
|
||||
value: 'axure',
|
||||
label: 'Axure Components'
|
||||
},
|
||||
{
|
||||
value: 'sketch',
|
||||
label: 'Sketch Templates'
|
||||
},
|
||||
{
|
||||
value: 'docs',
|
||||
label: 'Design Documentation'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
cascaderValue: [],
|
||||
switchValue: true,
|
||||
slider: 28,
|
||||
datePicker: '',
|
||||
rate: null,
|
||||
transferData: (() => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
key: i,
|
||||
label: `Option ${i}`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
})(),
|
||||
transfer: [1, 4],
|
||||
tableData: [
|
||||
{
|
||||
date: '2016-05-03',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
},
|
||||
{
|
||||
date: '2016-05-02',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
},
|
||||
{
|
||||
date: '2016-05-04',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
},
|
||||
{
|
||||
date: '2016-05-01',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
}
|
||||
],
|
||||
menu: '1',
|
||||
tab: 'first',
|
||||
collapse: ['1'],
|
||||
treeData: [{
|
||||
label: 'Level one 1',
|
||||
children: [{
|
||||
label: 'Level two 1-1',
|
||||
children: [{
|
||||
label: 'Level three 1-1-1'
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
label: 'Level one 2',
|
||||
children: [{
|
||||
label: 'Level two 2-1',
|
||||
children: [{
|
||||
label: 'Level three 2-1-1'
|
||||
}]
|
||||
}, {
|
||||
label: 'Level two 2-2',
|
||||
children: [{
|
||||
label: 'Level three 2-2-1'
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
label: 'Level one 3',
|
||||
children: [{
|
||||
label: 'Level two 3-1',
|
||||
children: [{
|
||||
label: 'Level three 3-1-1'
|
||||
}]
|
||||
}, {
|
||||
label: 'Level two 3-2',
|
||||
children: [{
|
||||
label: 'Level three 3-2-1'
|
||||
}]
|
||||
}]
|
||||
}],
|
||||
defaultTreeProps: {
|
||||
children: 'children',
|
||||
label: 'label'
|
||||
},
|
||||
avatarData: {
|
||||
url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
16
examples/components/theme/constant.js
Normal file
16
examples/components/theme/constant.js
Normal file
@@ -0,0 +1,16 @@
|
||||
export const DEFAULT_THEME_CONFIG = {
|
||||
global: {},
|
||||
local: {}
|
||||
};
|
||||
|
||||
export const ELEMENT_THEME_USER_CONFIG = 'ELEMENT_THEME_USER_CONFIG';
|
||||
|
||||
export const ELEMENT_THEME_PREVIEW_CONFIG = 'ELEMENT_THEME_PREVIEW_CONFIG';
|
||||
|
||||
export const ACTION_DOWNLOAD_THEME = 'ELEMENT_THEME_ACTION_DOWNLOAD';
|
||||
|
||||
export const ACTION_APPLY_THEME = 'ELEMENT_THEME_ACTION_ALLPY_CSS';
|
||||
|
||||
export const ACTION_COMPONECT_SELECT = 'ELEMENT_THEME_ACTION_COMPONECT_SELECT';
|
||||
|
||||
export const ACTION_USER_CONFIG_UPDATE = 'ELEMENT_THEME_USER_CONFIG_UPDATE';
|
||||
65
examples/components/theme/loader/ajax.js
Normal file
65
examples/components/theme/loader/ajax.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const defaultError = 'Server Error 500';
|
||||
const defaultTimeout = 'Request Timeout';
|
||||
const xhr = (method, url, data = null, cb) => {
|
||||
return new window.Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
const doReject = (xhr) => {
|
||||
reject(xhr.response || xhr.statusText || defaultError);
|
||||
};
|
||||
xhr.open(method, url);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.timeout = 10000;
|
||||
if (cb) cb(xhr);
|
||||
xhr.onload = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
|
||||
let response = xhr.response;
|
||||
const type = xhr.getResponseHeader('Content-Type');
|
||||
if (type.indexOf('zip') > -1) {
|
||||
let filename = 'style.zip';
|
||||
const disposition = xhr.getResponseHeader('content-disposition');
|
||||
if (disposition && disposition.indexOf('attachment') !== -1) {
|
||||
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
|
||||
var matches = filenameRegex.exec(disposition);
|
||||
if (matches != null && matches[1]) {
|
||||
filename = matches[1].replace(/['"]/g, '');
|
||||
}
|
||||
}
|
||||
var blob = new Blob([response], { type });
|
||||
var zipUrl = URL.createObjectURL(blob);
|
||||
var link = document.createElement('a');
|
||||
link.href = zipUrl;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
resolve(response);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
response = JSON.parse(xhr.response);
|
||||
} catch (e) {}
|
||||
resolve(response);
|
||||
} else {
|
||||
doReject(xhr);
|
||||
}
|
||||
} else {
|
||||
doReject(xhr);
|
||||
}
|
||||
};
|
||||
xhr.onerror = () => {
|
||||
doReject(xhr);
|
||||
};
|
||||
xhr.ontimeout = () => {
|
||||
xhr.abort();
|
||||
reject(defaultTimeout);
|
||||
};
|
||||
xhr.send(JSON.stringify(data));
|
||||
});
|
||||
};
|
||||
|
||||
export const post = (url, data, cb) => {
|
||||
return xhr('POST', url, data, cb);
|
||||
};
|
||||
|
||||
export const get = (url) => {
|
||||
return xhr('GET', url);
|
||||
};
|
||||
24
examples/components/theme/loader/api.js
Normal file
24
examples/components/theme/loader/api.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import Element from 'main/index.js';
|
||||
import { post, get } from './ajax';
|
||||
|
||||
const { version } = Element;
|
||||
|
||||
const hostList = {
|
||||
local: 'http://localhost:3008/',
|
||||
alpha: 'https://element-api.ar.elenet.me/element/theme/',
|
||||
production: 'https://element-api.ele.me/element/theme/'
|
||||
};
|
||||
|
||||
const host = hostList[process.env.FAAS_ENV] || hostList.production;
|
||||
|
||||
export const getVars = () => {
|
||||
return get(`${host}getVariable?version=${version}`);
|
||||
};
|
||||
|
||||
export const getTestEle = () => {
|
||||
return get(`${hostList.alpha}getVariable`);
|
||||
};
|
||||
|
||||
export const updateVars = (data, cb) => {
|
||||
return post(`${host}updateVariable?version=${version}`, data, cb);
|
||||
};
|
||||
62
examples/components/theme/loader/docStyle.vue
Normal file
62
examples/components/theme/loader/docStyle.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<script>
|
||||
const ORIGINAL_THEME = '#409EFF';
|
||||
import { get as ajaxGet } from './ajax.js';
|
||||
import { updateDomHeadStyle } from '../utils.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
docs: '', // content of docs css
|
||||
theme: ORIGINAL_THEME,
|
||||
asyncCb: true
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updateDocStyle(e, cb) {
|
||||
const val = e.global['$--color-primary'] || ORIGINAL_THEME;
|
||||
const oldVal = this.theme;
|
||||
const getHandler = (variable, id) => {
|
||||
return () => {
|
||||
let newStyle = this.updateStyle(this[variable], ORIGINAL_THEME, val);
|
||||
updateDomHeadStyle(id, newStyle);
|
||||
this.asyncCb && cb();
|
||||
};
|
||||
};
|
||||
const docsHandler = getHandler('docs', 'docs-style');
|
||||
if (!this.docs) {
|
||||
const links = [].filter.call(document.querySelectorAll('link'), link => {
|
||||
return /docs\..+\.css/.test(link.href || '');
|
||||
});
|
||||
if (links[0]) {
|
||||
this.getCSSString(links[0].href, docsHandler, 'docs');
|
||||
} else {
|
||||
this.asyncCb = false;
|
||||
}
|
||||
} else {
|
||||
docsHandler();
|
||||
}
|
||||
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, oldVal, val);
|
||||
});
|
||||
this.theme = val;
|
||||
!this.asyncCb && cb();
|
||||
},
|
||||
updateStyle(style, oldColor, newColor) {
|
||||
return style.replace(new RegExp(oldColor, 'ig'), newColor);
|
||||
},
|
||||
getCSSString(url, callback, variable) {
|
||||
ajaxGet(url).then((res) => {
|
||||
this[variable] = res;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
106
examples/components/theme/loader/index.vue
Normal file
106
examples/components/theme/loader/index.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<script>
|
||||
import bus from '../../../bus.js';
|
||||
import Loading from './loading';
|
||||
import DocStyle from './docStyle';
|
||||
import { updateVars } from './api.js';
|
||||
import { updateDomHeadStyle } from '../utils.js';
|
||||
import {
|
||||
ACTION_APPLY_THEME,
|
||||
ACTION_DOWNLOAD_THEME,
|
||||
ACTION_USER_CONFIG_UPDATE
|
||||
} from '../constant.js';
|
||||
import {
|
||||
loadUserThemeFromLocal,
|
||||
loadPreviewFromLocal
|
||||
} from '../localstorage.js';
|
||||
import { getActionDisplayName } from '../../theme-configurator/utils/utils';
|
||||
|
||||
export default {
|
||||
mixins: [Loading, DocStyle],
|
||||
mounted() {
|
||||
this.checkLocalThemeConfig();
|
||||
bus.$on(ACTION_APPLY_THEME, val => {
|
||||
this.userConfig = val;
|
||||
this.onAction();
|
||||
});
|
||||
bus.$on(ACTION_DOWNLOAD_THEME, (themeConfig, themeName) => {
|
||||
this.onDownload(themeConfig, themeName);
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userConfig: {},
|
||||
lastApply: 0
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
applyStyle(res, time) {
|
||||
if (time < this.lastApply) return;
|
||||
this.updateDocs(() => {
|
||||
updateDomHeadStyle('chalk-style', res);
|
||||
});
|
||||
this.lastApply = time;
|
||||
},
|
||||
onDownload(themeConfig, themeName) {
|
||||
this.triggertProgressBar(true);
|
||||
updateVars(
|
||||
Object.assign({}, themeConfig, { download: true }),
|
||||
xhr => {
|
||||
xhr.responseType = 'blob';
|
||||
}
|
||||
).then()
|
||||
.catch((err) => {
|
||||
this.onError(err);
|
||||
})
|
||||
.then(() => {
|
||||
this.triggertProgressBar(false);
|
||||
});
|
||||
ga('send', 'event', 'ThemeConfigurator', 'Download', themeName);
|
||||
},
|
||||
onAction() {
|
||||
this.triggertProgressBar(true);
|
||||
const time = +new Date();
|
||||
updateVars(this.userConfig)
|
||||
.then(res => {
|
||||
this.applyStyle(res, time);
|
||||
})
|
||||
.catch(err => {
|
||||
this.onError(err);
|
||||
})
|
||||
.then(() => {
|
||||
this.triggertProgressBar(false);
|
||||
});
|
||||
},
|
||||
onError(err) {
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(err).message;
|
||||
} catch (e) {
|
||||
message = err;
|
||||
}
|
||||
this.$message.error(message);
|
||||
},
|
||||
triggertProgressBar(isLoading) {
|
||||
bus.$emit('user-theme-config-loading', isLoading);
|
||||
},
|
||||
updateDocs(cb) {
|
||||
window.userThemeConfig = JSON.parse(JSON.stringify(this.userConfig));
|
||||
bus.$emit(ACTION_USER_CONFIG_UPDATE, this.userConfig);
|
||||
this.updateDocStyle(this.userConfig, cb);
|
||||
},
|
||||
checkLocalThemeConfig() {
|
||||
// load user local theme
|
||||
const previewConfig = loadPreviewFromLocal();
|
||||
if (previewConfig.type === 'user') {
|
||||
const userConfig = loadUserThemeFromLocal();
|
||||
this.$message(getActionDisplayName('load-local-theme-config'));
|
||||
const config = userConfig.filter(theme => (theme.name === previewConfig.name));
|
||||
if (config && config[0]) {
|
||||
this.userConfig = JSON.parse(config[0].theme);
|
||||
this.onAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
35
examples/components/theme/loader/loading/index.vue
Normal file
35
examples/components/theme/loader/loading/index.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<style>
|
||||
.loadingClass {
|
||||
z-index: 0!important;
|
||||
.el-loading-spinner {
|
||||
top: 0%;
|
||||
margin-top: 30%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
import bus from '../../../../bus.js';
|
||||
import './progress.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
count: 0
|
||||
};
|
||||
},
|
||||
created() {
|
||||
bus.$on('user-theme-config-loading', val => {
|
||||
if (val) {
|
||||
this.count++;
|
||||
if (this.count > 1) return;
|
||||
this.$bar.start();
|
||||
} else {
|
||||
this.count--;
|
||||
if (this.count) return;
|
||||
this.$bar.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
6
examples/components/theme/loader/loading/progress.js
Normal file
6
examples/components/theme/loader/loading/progress.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
import ProgressBar from './progress.vue';
|
||||
|
||||
Vue.prototype.$bar = new Vue(ProgressBar).$mount();
|
||||
|
||||
document.body.appendChild(Vue.prototype.$bar.$el);
|
||||
108
examples/components/theme/loader/loading/progress.vue
Normal file
108
examples/components/theme/loader/loading/progress.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div class="progress" :style="{
|
||||
'width': percent+'%',
|
||||
'height': height,
|
||||
'background-color': canSuccess? successColor() : failedColor(),
|
||||
'opacity': show ? 1 : 0
|
||||
}"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
percent: 0,
|
||||
show: false,
|
||||
canSuccess: true,
|
||||
duration: 3000,
|
||||
height: '2px'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
successColor() {
|
||||
return this.userSelectColor()['$--color-primary'] || '#409EFF';
|
||||
},
|
||||
failedColor() {
|
||||
return this.userSelectColor()['$--color-danger'] || '#F56C6C';
|
||||
},
|
||||
userSelectColor() {
|
||||
return window.userThemeConfig && window.userThemeConfig.global || {}
|
||||
},
|
||||
start () {
|
||||
this.show = true
|
||||
this.canSuccess = true
|
||||
if (this._timer) {
|
||||
clearInterval(this._timer)
|
||||
this.percent = 0
|
||||
}
|
||||
// Code from Nuxt.js!
|
||||
this._cut = 10000 / Math.floor(this.duration)
|
||||
this._timer = setInterval(() => {
|
||||
this.increase(this._cut * Math.random())
|
||||
if (this.percent >= 90) {
|
||||
this.percent = 90;
|
||||
}
|
||||
}, 100)
|
||||
return this
|
||||
},
|
||||
set (num) {
|
||||
this.show = true
|
||||
this.canSuccess = true
|
||||
this.percent = Math.floor(num)
|
||||
return this
|
||||
},
|
||||
get () {
|
||||
return Math.floor(this.percent)
|
||||
},
|
||||
increase (num) {
|
||||
this.percent = this.percent + Math.floor(num)
|
||||
return this
|
||||
},
|
||||
decrease (num) {
|
||||
this.percent = this.percent - Math.floor(num)
|
||||
return this
|
||||
},
|
||||
finish () {
|
||||
this.percent = 100
|
||||
this.hide()
|
||||
return this
|
||||
},
|
||||
pause () {
|
||||
clearInterval(this._timer)
|
||||
return this
|
||||
},
|
||||
hide () {
|
||||
clearInterval(this._timer)
|
||||
this._timer = null
|
||||
setTimeout(() => {
|
||||
this.show = false
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.percent = 0
|
||||
}, 200)
|
||||
})
|
||||
}, 500)
|
||||
return this
|
||||
},
|
||||
fail () {
|
||||
this.canSuccess = false
|
||||
return this
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.progress {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
height: 2px;
|
||||
width: 0%;
|
||||
transition: width 0.2s, opacity 0.4s;
|
||||
opacity: 1;
|
||||
z-index: 999999;
|
||||
}
|
||||
</style>
|
||||
38
examples/components/theme/localstorage.js
Normal file
38
examples/components/theme/localstorage.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
ELEMENT_THEME_PREVIEW_CONFIG,
|
||||
ELEMENT_THEME_USER_CONFIG
|
||||
} from './constant';
|
||||
|
||||
export const saveToLocal = (key, value) => {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
};
|
||||
|
||||
export const loadFromLocal = (key) => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(key));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const savePreviewToLocal = (value) => {
|
||||
saveToLocal(ELEMENT_THEME_PREVIEW_CONFIG, value);
|
||||
};
|
||||
|
||||
export const loadPreviewFromLocal = () => {
|
||||
return loadFromLocal(ELEMENT_THEME_PREVIEW_CONFIG) || {};
|
||||
};
|
||||
|
||||
export const removePreviewFromLocal = () => {
|
||||
return localStorage.removeItem(ELEMENT_THEME_PREVIEW_CONFIG);
|
||||
};
|
||||
|
||||
export const saveUserThemeToLocal = (value) => {
|
||||
saveToLocal(ELEMENT_THEME_USER_CONFIG, value);
|
||||
};
|
||||
|
||||
export const loadUserThemeFromLocal = () => {
|
||||
return loadFromLocal(ELEMENT_THEME_USER_CONFIG);
|
||||
};
|
||||
|
||||
406
examples/components/theme/theme-card.vue
Normal file
406
examples/components/theme/theme-card.vue
Normal file
@@ -0,0 +1,406 @@
|
||||
<style lang="scss">
|
||||
.theme-card-item {
|
||||
user-select: none;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
height: 90%;
|
||||
margin: 25px 0;
|
||||
box-shadow: 0 0 1px 0 #666;
|
||||
&.is-hidden {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
}
|
||||
.upload {
|
||||
cursor: pointer;
|
||||
background: #f5f7fa;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items:center;
|
||||
.upload-action {
|
||||
width: 40%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
color: #606266;
|
||||
img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
span {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.preview {
|
||||
position: relative;
|
||||
height: 70%;
|
||||
width: 100%;
|
||||
.line {
|
||||
height: 50%;
|
||||
}
|
||||
.line-2 {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
.line-4 {
|
||||
width: 25%;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
.action {
|
||||
transition: all .3s;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
.action-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #000;
|
||||
opacity: 0.4;
|
||||
}
|
||||
.action-block {
|
||||
position: absolute;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
left: 25%;
|
||||
top: 25%;
|
||||
}
|
||||
.action-item {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 30%;
|
||||
color: #eee;
|
||||
&:hover {
|
||||
color: #fff;
|
||||
.circle {
|
||||
border-color: #fff;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
height: 50%;
|
||||
font-size: 22px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content:center;
|
||||
align-items:center;
|
||||
img {
|
||||
width: 130%;
|
||||
}
|
||||
}
|
||||
.name {
|
||||
font-size: 12px;
|
||||
height: 50%;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content:center;
|
||||
align-items:center;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
.action-item-right {
|
||||
margin-left: 40%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.info {
|
||||
height: 30%;
|
||||
line-height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.info-center {
|
||||
width: 100%;
|
||||
}
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: #303133;
|
||||
padding: 0 12px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.right {
|
||||
float: right;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
.more {
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.description {
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
&.is-upload {
|
||||
box-shadow: none;
|
||||
border: 1px dashed #DCDFE6;
|
||||
}
|
||||
&.is-upload:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 10px 0 #999;
|
||||
.action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<section class="theme-card-item" :class="{'is-hidden': !config || !config.name, 'is-upload': isUpload}">
|
||||
<template v-if="isUpload">
|
||||
<div class="upload" @click="uploadClick">
|
||||
<div class="upload-action">
|
||||
<img src="../../assets/images/icon-upload.svg"/>
|
||||
<span>{{getActionDisplayName('upload-theme')}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="el-upload__input"
|
||||
type="file"
|
||||
ref="input"
|
||||
@change="uploadAction"
|
||||
accept="application/json"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="preview">
|
||||
<div class="line">
|
||||
<span class="line-2" :style="{background: mainColor}"></span>
|
||||
<span class="line-2" :style="{background: textPrimaryColor}"></span>
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="line-4" :style="{background: mainColor50}"></span>
|
||||
<span class="line-4" :style="{background: mainColor80}"></span>
|
||||
<span class="line-4" :style="{background: borderBaseColor}"></span>
|
||||
<span class="line-4" :style="{background: textSecondaryColor}"></span>
|
||||
</div>
|
||||
<div class="action">
|
||||
<div class="action-mask"></div>
|
||||
<div class="action-block">
|
||||
<div
|
||||
class="action-item"
|
||||
:class="index && 'action-item-right'"
|
||||
v-for="(item, index) in actionArray"
|
||||
:key="index"
|
||||
@click="iconClick(item.action)"
|
||||
>
|
||||
<div class="icon">
|
||||
<img :src="item.icon"/>
|
||||
<span class="circle"></span>
|
||||
</div>
|
||||
<div class="name">
|
||||
<span>{{item.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="info-center">
|
||||
<div class="title">
|
||||
<span>{{config.name}}</span>
|
||||
<span class="right" v-if="isOfficial">by {{config.author}}</span>
|
||||
<span class="right more" v-else>
|
||||
<el-dropdown @command="actionClick">
|
||||
<i class="el-icon-more"></i>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="rename">{{getActionDisplayName('rename-theme')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="copy">{{getActionDisplayName('copy-theme')}}</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
command="delete"
|
||||
style="color: #F56C6C;"
|
||||
>
|
||||
{{getActionDisplayName('delete-theme')}}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</span>
|
||||
</div>
|
||||
<div class="description" v-if="isOfficial">{{getActionDisplayName(getDescriptionKey(config.name))}} </div>
|
||||
<div class="description" v-else>{{getActionDisplayName('last-modified')}} {{formatDate(config.update)}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bus from '../../bus';
|
||||
import {
|
||||
DEFAULT_THEME_CONFIG,
|
||||
ACTION_DOWNLOAD_THEME
|
||||
} from './constant.js';
|
||||
import { savePreviewToLocal } from './localstorage';
|
||||
import { tintColor } from '../../color.js';
|
||||
import dateUtil from 'element-ui/src/utils/date';
|
||||
import { getActionDisplayName } from '../theme-configurator/utils/utils';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
config: Object,
|
||||
type: String,
|
||||
base: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
from: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
deleteVisible: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getActionDisplayName(key) {
|
||||
return getActionDisplayName(key);
|
||||
},
|
||||
getDescriptionKey(name) {
|
||||
return name ? `description-${name.toLowerCase()}` : '';
|
||||
},
|
||||
formatDate(timestamp) {
|
||||
if (!timestamp) return '';
|
||||
return dateUtil.format(new Date(timestamp), 'yyyy-MM-dd HH:mm');
|
||||
},
|
||||
uploadClick() {
|
||||
this.$refs.input.value = null;
|
||||
this.$refs.input.click();
|
||||
},
|
||||
uploadAction(ev) {
|
||||
const files = ev.target.files;
|
||||
if (!files) return;
|
||||
var reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const jsonString = e.target.result;
|
||||
const jsonObject = JSON.parse(jsonString);
|
||||
if (!jsonObject.global || !jsonObject.local) {
|
||||
return this.$message.error('JSON format error');
|
||||
}
|
||||
this.$emit('action', 'upload', jsonString);
|
||||
} catch (e) {
|
||||
this.$message.error('Upload error');
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
reader.readAsText(files[0]);
|
||||
},
|
||||
actionClick(e) {
|
||||
this.$emit('action', e, this.config);
|
||||
},
|
||||
iconClick(e) {
|
||||
switch (e) {
|
||||
case 'preview':
|
||||
case 'edit':
|
||||
if (this.from) {
|
||||
this.$emit('action', e, this.config);
|
||||
return;
|
||||
}
|
||||
const { name, theme } = this.config;
|
||||
savePreviewToLocal({
|
||||
type: this.type,
|
||||
name,
|
||||
theme
|
||||
});
|
||||
this.$router.push({
|
||||
name: `theme-preview-${this.$route.meta.lang}`,
|
||||
params: {
|
||||
refer: 'theme'
|
||||
}
|
||||
});
|
||||
this.$nextTick(() => {
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
break;
|
||||
case 'download':
|
||||
bus.$emit(ACTION_DOWNLOAD_THEME, this.theme, this.config.name);
|
||||
break;
|
||||
default:
|
||||
this.$emit('action', e, this.config);
|
||||
return;
|
||||
}
|
||||
},
|
||||
deleteUserTheme() {
|
||||
this.deleteVisible = false;
|
||||
this.$emit('action', 'delete', this.config);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isUpload() {
|
||||
return this.type === 'upload';
|
||||
},
|
||||
theme() {
|
||||
if (this.config.theme) {
|
||||
return JSON.parse(this.config.theme);
|
||||
}
|
||||
return DEFAULT_THEME_CONFIG;
|
||||
},
|
||||
mainColor() {
|
||||
return this.theme.global['$--color-primary'] || '#1989FA';
|
||||
},
|
||||
mainColor50() {
|
||||
return tintColor(this.mainColor, 0.5);
|
||||
},
|
||||
mainColor80() {
|
||||
return tintColor(this.mainColor, 0.8);
|
||||
},
|
||||
textPrimaryColor() {
|
||||
return this.theme.global['$--color-text-primary'] || '#303133';
|
||||
},
|
||||
borderBaseColor() {
|
||||
return this.theme.global['$--border-color-base'] || '#DCDFE6';
|
||||
},
|
||||
textSecondaryColor() {
|
||||
return this.theme.global['$--color-text-secondary'] || '#909399';
|
||||
},
|
||||
isOfficial() {
|
||||
return this.type === 'official';
|
||||
},
|
||||
actionArray() {
|
||||
if (this.isOfficial) {
|
||||
return [
|
||||
{
|
||||
icon: require('../../assets/images/icon-check.png'),
|
||||
name: getActionDisplayName('theme-check'),
|
||||
action: 'preview'
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/images/icon-copy.png'),
|
||||
name: getActionDisplayName('theme-copy'),
|
||||
action: 'copy'
|
||||
}
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
icon: require('../../assets/images/icon-edit.png'),
|
||||
name: getActionDisplayName('theme-edit'),
|
||||
action: 'edit'
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/images/icon-download.png'),
|
||||
name: getActionDisplayName('download-theme'),
|
||||
action: 'download'
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
15
examples/components/theme/theme-list.js
Normal file
15
examples/components/theme/theme-list.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export const themeList = [
|
||||
{
|
||||
name: 'Element',
|
||||
author: 'Element',
|
||||
theme: '{"global":{"$--color-primary":"#409EFF"},"local":{}}'
|
||||
}
|
||||
];
|
||||
|
||||
export const eleThemeList = [
|
||||
{
|
||||
name: 'Kiwi',
|
||||
author: 'Element',
|
||||
theme: '{"global":{"$--color-primary":"#1989FA","$--color-success":"#5CB87A","$--color-warning":"#E6A23C","$--color-danger":"#F56C6C","$--color-info":"#8896B3","$--font-size-small":"13px","$--font-size-base":"14px","$--font-size-medium":"16px","$--font-size-large":"22px","$--font-size-extra-large":"28px","$--font-line-height-secondary":"20px"},"local":{"$--button-primary-background-color":"$--color-primary","$--switch-font-size":"14px","$--datepicker-active-color":"$--color-primary","$--tooltip-border-color":"$--border-color-base","$--tooltip-color":"#FFFFFF","$--tooltip-padding":"10px","$--collapse-header-font-size":"14px","$--collapse-content-font-size":"14px","$--collapse-content-font-color":"$--color-text-regular","$--radio-input-background-color":"#FFFFFF","$--radio-icon-color":"$--color-warning","$--radio-button-checked-background-color":"$--color-primary","$--input-border-radius":"$--border-radius-base","$--select-input-focus-border-color":"$--color-primary","$--select-font-size":"14px","$--select-option-selected-font-color":"$--color-primary","$--select-input-font-size":"14px","$--select-option-height":"34px","$--slider-main-background-color":"$--color-primary","$--tag-font-color":"$--color-primary","$--tag-default-hover-background-color":"$--color-primary","$--message-warning-font-color":"$--color-warning","$--alert-title-font-size":"14px","$--alert-icon-size":"14px","$--alert-icon-large-size":"22px","$--alert-close-customed-font-size":"12px","$--notification-title-font-size":"16px","$--notification-content-font-size":"14px","$--menu-item-font-color":"$--color-primary"}}'
|
||||
}
|
||||
];
|
||||
24
examples/components/theme/utils.js
Normal file
24
examples/components/theme/utils.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export const isEmptyObject = (obj) => (JSON.stringify(obj) === '{}');
|
||||
|
||||
export const getThemeConfigObject = (config) => {
|
||||
try {
|
||||
const conf = JSON.parse(config);
|
||||
const { global, local } = conf;
|
||||
if (!isEmptyObject(global) || !isEmptyObject(local)) {
|
||||
return conf;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateDomHeadStyle = (id, styleContent) => {
|
||||
let styleTag = document.getElementById(id);
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style');
|
||||
styleTag.setAttribute('id', id);
|
||||
document.head.appendChild(styleTag);
|
||||
}
|
||||
styleTag.innerText = styleContent.replace(/@font-face{[^}]+}/, '');
|
||||
};
|
||||
Reference in New Issue
Block a user