Commit febdd3bb authored by iamkun's avatar iamkun Committed by Zhi Cun

chore: init theme (#14508)

* chore: init theme

* chore: update faas config
parent 2604c619
......@@ -172,6 +172,9 @@ const webpackConfig = {
]),
new ProgressBarPlugin(),
new VueLoaderPlugin(),
new webpack.DefinePlugin({
'process.env.FAAS_ENV': JSON.stringify(process.env.FAAS_ENV)
}),
new webpack.LoaderOptionsPlugin({
vue: {
compilerOptions: {
......
export const tintColor = (c, tint) => {
const color = c.replace('#', '');
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 }`;
}
};
......@@ -17,6 +17,7 @@
.container {
height: 100%;
box-sizing: border-box;
border-bottom: 1px solid #DCDFE6;
}
.nav-lang-spe {
......@@ -122,23 +123,24 @@
a {
text-decoration: none;
color: #888;
color: #1989FA;
opacity: 0.5;
display: block;
padding: 0 22px;
&.active,
&:hover {
color: #333;
opacity: 1;
}
&.active::after {
content: '';
display: inline-block;
position: absolute;
bottom: 15px;
left: calc(50% - 7px);
width: 14px;
height: 4px;
bottom: 0;
left: calc(50% - 15px);
width: 30px;
height: 2px;
background: #409EFF;
}
}
......@@ -356,7 +358,8 @@
<!--theme picker-->
<li class="nav-item nav-theme-switch" v-show="isComponentPage">
<theme-picker></theme-picker>
<theme-configurator :key="lang" v-if="showThemeConfigurator"></theme-configurator>
<theme-picker v-else></theme-picker>
</li>
</ul>
</div>
......@@ -365,9 +368,12 @@
</template>
<script>
import ThemePicker from './theme-picker.vue';
import ThemeConfigurator from './theme-configurator';
import AlgoliaSearch from './search.vue';
import compoLang from '../i18n/component.json';
import Element from 'main/index.js';
import bus from '../bus';
const { version } = Element;
export default {
......@@ -389,6 +395,7 @@
components: {
ThemePicker,
ThemeConfigurator,
AlgoliaSearch
},
......@@ -404,6 +411,10 @@
},
isComponentPage() {
return /^component/.test(this.$route.name);
},
showThemeConfigurator() {
const host = location.hostname;
return host.match('localhost') || host.match('elenet');
}
},
......@@ -441,6 +452,17 @@
};
xhr.open('GET', '/versions.json');
xhr.send();
let primaryLast = '#409EFF';
bus.$on('user-theme-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>
<script>
const ORIGINAL_THEME = '#409EFF';
import { get as ajaxGet } from './utils/ajax';
import { updateDomHeadStyle } from './utils/utils.js';
export default {
data() {
return {
docs: '', // content of docs css
theme: ORIGINAL_THEME
};
},
methods: {
updateDocStyle(e) {
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);
};
};
const docsHandler = getHandler('docs', 'docs-style');
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, oldVal, val);
});
this.theme = val;
},
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>
<template>
<div class="action-area">
<div class="action-area-main">
<div class="action-button">
<el-button type="warning" @click.stop="onReset">{{getActionDisplayName("reset-theme")}}</el-button>
</div>
<div class="action-button">
<el-button
type="primary"
:loading=downloading
@click.stop="onDownload">
{{getActionDisplayName("download-theme")}}
</el-button>
</div>
</div>
</div>
</template>
<style>
.action-area {
width: 24%;
max-width: 400px;
position: fixed;
right: 0;
bottom: 0;
padding-right: 1%;
}
@media (min-width: 1600px) {
.action-area {
padding-right: calc((100% - 1600px) / 2);
}
}
.action-area-main {
opacity: .95;
background: #F5F7FA;
height: 70px;
bottom: 0;
width: 97%;
box-sizing: border-box;
margin: 0 .5% 0 5%;
}
.action-button {
width: 50%;
text-align: center;
display: inline-block;
padding-top: 15px;
}
</style>
<script>
import { getActionDisplayName } from './utils/utils.js';
export default {
data() {
return {
downloading: false
};
},
methods: {
onReset() {
this.$parent.onReset();
},
getActionDisplayName(key) {
return getActionDisplayName(key);
},
onDownload() {
this.downloading = true;
this.$parent.onDownload()
.then()
.catch((err) => {
this.$parent.onError(err);
})
.then(() => {
this.downloading = false;
});
}
}
};
</script>
\ No newline at end of file
<template>
<section class="config" :key="displayName">
<div class="config-label">
{{displayName}}
</div>
<div class="config-content">
<theme-input
v-if="isGlobal"
:val="value"
@change="onChange"
></theme-input>
<el-select
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>
\ No newline at end of file
<template>
<section class="config" :key="displayName">
<div class="config-label">
{{displayName}}
<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%;
}
.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>
\ No newline at end of file
import ColorPicker from './src/main';
/* istanbul ignore next */
ColorPicker.install = function(Vue) {
Vue.component(ColorPicker.name, ColorPicker);
};
export default ColorPicker;
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));
}
}
}
};
<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>
<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>
<div class="color-list-item-value">
{{item.info.value}}
</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;
}
.color-list-item:hover {
background: #efefef;
}
.color-list-item-ball {
height: 20px;
width: 20px;
margin-top: 2px;
margin-left: 5px;
border-radius: 100%;
display: inline-block;
}
.color-list-item-label {
margin-left: 15px;
font-size: 13px;
line-height: 24px;
display: inline-block;
vertical-align: super;
width: 30%;
}
.color-list-item-value {
margin-left: 15px;
font-size: 13px;
line-height: 24px;
display: inline-block;
vertical-align: super;
}
</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>
\ No newline at end of file
<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>
<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"
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>
<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>
\ No newline at end of file
<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>
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);
}
});
}
<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>
<template>
<section class="config" :key="displayName">
<div class="config-label">
{{displayName}}
</div>
<div class="config-content">
<div class="content-80">
<el-input
:value=displayValue
readonly
slot="reference"
@click.native="onInputClick"
></el-input>
</div>
<div class="content-20">
<color-picker
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>
\ No newline at end of file
<template>
<section class="config" :key="displayName">
<div class="config-label">
{{displayName}}
</div>
<div class="config-content">
<el-select
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>
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>
\ No newline at end of file
<template>
<section class="config" :key="displayName">
<div class="config-label">
{{displayName}}
</div>
<div class="config-content">
<el-select
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>
const defaultFontSize = [
'12px',
'13px',
'14px',
'16px',
'18px',
'20px',
'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>
\ No newline at end of file
<template>
<section class="config" :key="displayName">
<div class="config-label">
{{displayName}}
</div>
<div class="config-content">
<el-select
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>
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>
\ No newline at end of file
<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>
\ No newline at end of file
<style>
.config {
padding: 5px 0;
}
.config-label {
color: #606266;;
font-size: 14px;
padding-bottom: 5px;
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);
},
isGlobal() {
return !this.config.value.startsWith('$');
}
},
methods: {
onChange(value) {
this.$emit('onChange', {
key: this.config.key,
value
});
}
}
};
</script>
\ No newline at end of file
<template>
<section class="config" :key="displayName">
<div class="config-label">
{{displayName}}
</div>
<div class="config-content">
<theme-input
:val="value"
@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>
\ No newline at end of file
<template>
<div>
<el-button
round
type="primary"
size="mini"
@click.stop="showConfigurator"
>{{getActionDisplayName("theme-editor")}}</el-button>
<transition name="fade">
<div v-show="visible" class="configurator" ref="configurator">
<div
class="main-configurator"
ref="mainConfigurator"
>
<div v-if="currentConfig">
<main-panel
:currentConfig="currentConfig"
:defaultConfig="defaultConfig"
:userConfig="userConfig"
:globalValue="globalValue"
@onChange="userConfigChange"
></main-panel>
</div>
<div v-if="init && !currentConfig" class="no-config">
<img src="../../assets/images/theme-no-config.png" alt>
<span>{{getActionDisplayName("no-config")}}</span>
</div>
<download-area></download-area>
</div>
</div>
</transition>
</div>
</template>
<style>
.configurator {
height: 100%;
width: 24%;
max-width: 400px;
position: fixed;
overflow-y: auto;
top: 79px;
right: 0;
bottom: 0;
background: #fff;
color: #666;
line-height: 24px;
padding-right: 1%;
}
.configurator .main-configurator {
height: 100%;
overflow: auto;
background: #F5F7FA;
border: 1px solid #EBEEF5;
box-shadow: 0 2px 10px 0 rgba(0,0,0,0.06);
border-radius: 8px;
width: 97%;
box-sizing: border-box;
margin: 0 .5% 0 5%;
}
.no-config {
margin-top: 130px;
text-align: center;
img {
width: 50%;
display: block;
margin: 0 auto;
}
span {
margin-top: 10px;
display: block;
color: #909399;
font-size: 14px;
}
}
.fade-enter,.fade-leave-to {
transform:translateX(100%);
}
.fade-enter-active ,.fade-leave-active {
transition:all 0.3s ease;
}
@media (min-width: 1600px) {
.configurator {
padding-right: calc((100% - 1600px) / 2);
}
}
</style>
<script>
import bus from '../../bus';
import { getVars, updateVars } from './utils/api.js';
import mainPanel from './main';
import {
filterConfigType,
filterGlobalValue,
updateDomHeadStyle,
getActionDisplayName
} from './utils/utils.js';
import DocStyle from './docStyle';
import Loading from './loading';
import DownloadArea from './download';
export default {
components: {
mainPanel,
DownloadArea
},
data() {
return {
init: false,
visible: false,
defaultConfig: null,
currentConfig: null,
userConfig: {
global: {},
local: {}
},
lastApply: 0
};
},
mixins: [DocStyle, Loading],
computed: {
globalValue() {
return filterGlobalValue(this.defaultConfig, this.userConfig);
}
},
methods: {
getActionDisplayName(key) {
return getActionDisplayName(key);
},
showConfigurator() {
this.visible = !this.visible;
bus.$emit('user-theme-config-visible', this.visible);
window.userThemeConfigVisible = Boolean(this.visible);
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(err);
})
.then(() => {
setTimeout(() => {
if (defaultConfig) {
this.defaultConfig = defaultConfig;
this.filterCurrentConfig();
this.init = true;
}
loading.close();
}, 300); // action after transition
});
});
},
filterCurrentConfig() {
this.currentConfig = this.defaultConfig.find((config) => {
return config.name === this.$route.path.split('/').pop().toLowerCase();
});
},
userConfigChange(e) {
this.$set(this.userConfig[filterConfigType(this.currentConfig.name)], e.key, e.value);
this.onAction();
},
applyStyle(res, time) {
if (time < this.lastApply) return;
this.updateDocs();
updateDomHeadStyle('chalk-style', res);
this.lastApply = time;
},
onDownload() {
return updateVars(Object.assign({}, this.userConfig, {download: true}), (xhr) => {
xhr.responseType = 'blob';
});
},
onReset() {
this.userConfig = {
global: {},
local: {}
};
this.onAction();
},
onAction() {
this.triggerComponentLoading(true);
const time = +new Date();
updateVars(this.userConfig)
.then((res) => {
this.applyStyle(res, time);
})
.catch((err) => {
this.onError(err);
})
.then(() => {
this.triggerComponentLoading(false);
});
},
onError(err) {
let message;
try {
message = JSON.parse(err).message;
} catch (e) {
message = err;
}
this.$message.error(message);
},
triggerComponentLoading(val) {
bus.$emit('user-theme-config-loading', val);
},
updateDocs() {
window.userThemeConfig = JSON.parse(JSON.stringify(this.userConfig));
bus.$emit('user-theme-config-update', this.userConfig);
this.updateDocStyle(this.userConfig);
}
},
watch: {
'$route.path'() {
this.defaultConfig && this.filterCurrentConfig();
if (!this.$refs.mainConfigurator) return;
this.$nextTick(() => {
this.$refs.mainConfigurator.scrollTop = 0;
});
}
}
};
</script>
\ No newline at end of file
<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>
<template>
<div class="main">
<!-- <span>{{configName}}</span> -->
<div v-for="(config, key) in configByOrder" :key="key">
<span
v-if="showCategory(config.category, key + 1)"
class="category-name"
>
{{getCategoryDisplayName(config.category)}}
</span>
<component
:is="editorComponent(config.type)"
:componentName=configName
:config=config
:userConfig=userConfigByType
:golbalValue=globalValue
@onChange=onChange
>
</component>
</div>
</div>
</template>
<style>
.main {
margin-bottom: 140px;
padding: 10px;
}
.category-name {
color: #C0C4CC;
font-size: 18px;
display: block;
margin: 8px 0 4px 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, getCategoryDisplayName } 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: {
getCategoryDisplayName(key) {
return getCategoryDisplayName(key);
},
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: {}
};
}
};
</script>
\ No newline at end of file
import Vue from 'vue';
import ProgressBar from './progress.vue';
Vue.prototype.$bar = new Vue(ProgressBar).$mount();
document.body.appendChild(Vue.prototype.$bar.$el);
<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>
\ No newline at end of file
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);
};
import Element from 'main/index.js';
import { post, get } from './ajax';
const { version } = Element;
const hostList = {
local: 'http://localhost:3008/',
alpha: 'https://ssr.alpha.elenet.me/element-theme-server/',
production: 'https://ssr.elenet.me/element-theme-server/'
};
const host = hostList[process.env.FAAS_ENV] || hostList.production;
export const getVars = () => {
return get(`${host}getVariable?version=${version}`);
};
export const updateVars = (data, cb) => {
return post(`${host}updateVariable?version=${version}`, data, cb);
};
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(', ');
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 getStyleDisplayName = (config, componentName) => {
const displayNameMap = getNameFromI18N('display-name');
if (config.name !== '[]') {
const langIndex = {'zh-CN': '0', 'es': 2, 'fr-FR': 3}[getLang()] || 1;
const nameArr = config.name.replace(/\[?\]?/g, '').split(',');
return nameArr[langIndex] || nameArr[1];
}
let displayName = config.key.replace(`$--${componentName}-`, '').replace();
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;
};
export const getCategoryDisplayName = (key) => {
return getNameFromI18N('category')[key] || key;
};
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{[^}]+}/, '');
};
<script>
import bus from '../../bus';
const varMap = {
'$--box-shadow-light': 'boxShadowLight',
'$--box-shadow-base': 'boxShadowBase',
'$--border-radius-base': 'borderRadiusBase',
'$--border-radius-small': 'borderRadiusSmall'
};
const original = {
boxShadowLight: '0 2px 12px 0 rgba(0, 0, 0, 0.1)',
boxShadowBase: '0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)',
borderRadiusBase: '4px',
borderRadiusSmall: '2px'
}
export default {
created() {
bus.$on('user-theme-config-update', this.setGlobal);
},
mounted() {
this.setGlobal();
},
methods: {
setGlobal() {
if (window.userThemeConfig) {
this.global = window.userThemeConfig.global;
}
}
},
data() {
return {
global: {},
boxShadowLight: '',
boxShadowBase: '',
borderRadiusBase: '',
borderRadiusSmall: ''
}
},
watch: {
global: {
immediate: true,
handler(value) {
Object.keys(varMap).forEach((c) => {
if (value[c]) {
this[varMap[c]] = value[c]
} else {
this[varMap[c]] = original[varMap[c]]
}
});
}
}
}
}
</script>
<style>
.demo-border .text {
width: 15%;
}
.demo-border .line {
width: 70%;
}
.demo-border .line div{
width: 100%;
height: 0;
border: 1px solid #EEE;
}
.demo-border .line .dashed{
border: 2px dashed #EEE;
}
.demo-shadow {
height: 100px;
width: 50%;
border: 1px solid #eee;
}
.demo-shadow-text {
line-height: 50px;
color: #666;
font-size: 14px;
}
.demo-radius .title{
color: #666;
font-size: 18px;
margin: 10px 0;
}
.demo-radius .value{
color: #333;
font-size: 16px;
margin: 10px 0;
}
.demo-radius .radius {
height: 60px;
width: 70%;
border: 1px solid #D7DAE2;
border-radius: 0;
margin-top: 20px;
}
.demo-radius .radius-30 {
border-radius: 30px;
}
</style>
## Border
We standardize the borders that can be used in buttons, cards, pop-ups and other components.
### Border
There are few border styles to choose.
<table class="demo-border">
<tbody>
<tr>
<td class="text">Name</td>
<td class="text">Thickness</td>
<td class="line">Demo</td>
</tr>
<tr>
<td class="text">Solid</td>
<td class="text">1px</td>
<td class="line">
<div></div>
</td>
</tr>
<tr>
<td class="text">Dashed</td>
<td class="text">2px</td>
<td class="line">
<div class="dashed"></div>
</td>
</tr>
</tbody>
</table>
### Radius
There are few radius styles to choose.
<el-row :gutter="12" class="demo-radius">
<el-col :span="6" :xs="{span: 12}">
<div class="title">No Radius</div>
<div class="value">border-radius: 0px</div>
<div class="radius"></div>
</el-col>
<el-col :span="6" :xs="{span: 12}">
<div class="title">Small Radius</div>
<div class="value">border-radius: {{borderRadiusSmall}}</div>
<div
class="radius"
:style="{ borderRadius: borderRadiusSmall }"
></div>
</el-col>
<el-col :span="6" :xs="{span: 12}">
<div class="title">Large Radius</div>
<div class="value">border-radius: {{borderRadiusBase}}</div>
<div
class="radius"
:style="{ borderRadius: borderRadiusBase }"
></div>
</el-col>
<el-col :span="6" :xs="{span: 12}">
<div class="title">Round Radius</div>
<div class="value">border-radius: 30px</div>
<div class="radius radius-30"></div>
</el-col>
</el-row>
### Shadow
There are few shaodw styles to choose.
<div
class="demo-shadow"
:style="{ boxShadow: boxShadowBase }"
></div>
<span class="demo-shadow-text">Basic Shaodw box-shadow: {{boxShadowBase}}</span>
<div
class="demo-shadow"
:style="{ boxShadow: boxShadowLight }"
></div>
<span class="demo-shadow-text">Light Shadow box-shadow: {{boxShadowLight}}</span>
This diff is collapsed.
<script>
import bus from '../../bus';
const varMap = [
'$--font-size-extra-large',
'$--font-size-large',
'$--font-size-medium',
'$--font-size-base',
'$--font-size-small',
'$--font-size-extra-small'
];
const original = {
'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'
}
export default {
created() {
bus.$on('user-theme-config-update', this.setGlobal);
},
mounted() {
this.setGlobal();
},
methods: {
tintColor(color, tint) {
return tintColor(color, tint);
},
setGlobal() {
if (window.userThemeConfig) {
this.global = window.userThemeConfig.global;
}
}
},
data() {
return {
global: {},
'font_size_extra_large': '',
'font_size_large': '',
'font_size_medium': '',
'font_size_base': '',
'font_size_small': '',
'font_size_extra_small': ''
}
},
watch: {
global: {
immediate: true,
handler(value) {
varMap.forEach((v) => {
const key = v.replace('$--', '').replace(/-/g, '_')
if (value[v]) {
this[key] = value[v]
} else {
this[key] = original[key]
}
});
}
}
},
}
</script>
<style>
.demo-typo-box {
height: 200px;
width: 200px;
position: relative;
border: 1px solid #eaeefb;
font-size: 40px;
color: #1f2d3d;
text-align: center;
line-height: 162px;
padding-bottom: 36px;
box-sizing: border-box;
display: inline-block;
margin-right: 17px;
border-radius: 4px;
.name {
position: absolute;
bottom: 0;
width: 100%;
height: 35px;
border-top: 1px solid #eaeefb;
font-size: 14px;
color: #8492a6;
line-height: 35px;
text-align: left;
text-indent: 10px;
font-family: 'Helvetica Neue';
}
}
.demo-typo-size {
.h1 {
font-size: 20px;
}
.h2 {
font-size: 18px;
}
.h3 {
font-size: 16px;
}
.text-regular {
font-size: 14px;
}
.text-small {
font-size: 13px;
}
.text-smaller {
font-size: 12px;
}
.color-dark-light {
color: #99a9bf;
}
}
.typo-PingFang {
font-family: 'PingFang SC';
}
.typo-Hiragino {
font-family: 'Hiragino Sans GB';
.demo-term-box img{
width: 24%;
margin: 0 4% 20px 0;
}
.typo-Microsoft {
font-family: 'Microsoft YaHei';
.lineH-left {
display: inline-block;
height: 80px
}
/* 英文 */
.typo-Helvetica-Neue {
font-family: 'Helvetica Neue';
.lineH-right {
display: inline-block;
list-style: none;
padding: 0 0 0 90px;
margin: 0;
vertical-align: top;
}
.typo-Helvetica {
font-family: 'Helvetica';
.lineH-right li{
font-size: 13px;
color: #666;
height: 20px;
line-height: 20px;
}
.typo-Arial {
font-family: 'Arial';
.lineH-right li span{
padding-left: 40px;
}
</style>
......@@ -76,76 +98,85 @@
We create a font convention to ensure the best presentation across different platforms.
### Chinese Font
<div class="demo-typo-box typo-PingFang">
和畅惠风
<div class="name">PingFang SC</div>
</div>
<div class="demo-typo-box typo-Hiragino">
和畅惠风
<div class="name">Hiragino Sans GB</div>
### Font
<div class="demo-term-box">
<img src="../../assets/images/term-pingfang.png" alt="">
<img src="../../assets/images/term-hiragino.png" alt="">
<img src="../../assets/images/term-microsoft.png" alt="">
<img src="../../assets/images/term-sf.png" alt="">
<img src="../../assets/images/term-helvetica.png" alt="">
<img src="../../assets/images/term-arial.png" alt="">
</div>
<div class="demo-typo-box typo-Microsoft">
和畅惠风
<div class="name">Microsoft YaHei</div>
</div>
### English / Numberic Font
<div class="demo-typo-box typo-Helvetica-neue">
RGag
<div class="name">Helvetica Neue</div>
</div>
<div class="demo-typo-box typo-Helvetica">
RGag
<div class="name">Helvetica</div>
</div>
<div class="demo-typo-box typo-Arial">
RGag
<div class="name">Arial</div>
</div>
### Font-family
```css
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
```
### Font Convention
<table class="demo-typo-size">
<tbody>
<tr>
<td class="h1">Main Title</td>
<td class="h1">Build with Element</td>
<td class="color-dark-light">20px Extra large</td>
<tr
>
<td>Level</td>
<td>Font Size</td>
<td class="color-dark-light">Demo</td>
</tr>
<tr>
<td class="h2">Title</td>
<td class="h2">Build with Element</td>
<td class="color-dark-light">18px large</td>
<tr
:style="{ fontSize: font_size_extra_small }"
>
<td>Supplementary text</td>
<td class="color-dark-light">{{font_size_extra_small}} Extra Small</td>
<td>Build with Element</td>
</tr>
<tr>
<td class="h3">Small Title</td>
<td class="h3">Build with Element</td>
<td class="color-dark-light">16px Medium</td>
<tr
:style="{ fontSize: font_size_small }"
>
<td>Body (small)</td>
<td class="color-dark-light">{{font_size_small}} Small</td>
<td>Build with Element</td>
</tr>
<tr>
<td class="text-regular">Body</td>
<td class="text-regular">Build with Element</td>
<td class="color-dark-light">14px Small</td>
<tr
:style="{ fontSize: font_size_base }"
>
<td>Body</td>
<td class="color-dark-light">{{font_size_base}} Base</td>
<td>Build with Element</td>
</tr>
<tr>
<td class="text-small">Body (small)</td>
<td class="text-small">Build with Element</td>
<td class="color-dark-light">13px Extra Small</td>
<tr
:style="{ fontSize: font_size_medium }"
>
<td >Small Title</td>
<td class="color-dark-light">{{font_size_medium}} Medium</td>
<td>Build with Element</td>
</tr>
<tr>
<td class="text-smaller">Supplementary text</td>
<td class="text-smaller">Build with Element</td>
<td class="color-dark-light">12px Extra Extra Small</td>
<tr
:style="{ fontSize: font_size_large }"
>
<td>Title</td>
<td class="color-dark-light">{{font_size_large}} large</td>
<td>Build with Element</td>
</tr>
<tr
:style="{ fontSize: font_size_extra_large }"
>
<td>Main Title</td>
<td class="color-dark-light">{{font_size_extra_large}} Extra large</td>
<td>Build with Element</td>
</tr>
</tbody>
</table>
### Font Line Height
<div>
<img class="lineH-left" src="~examples/assets/images/typography.png" />
<ul class="lineH-right">
<li>line-height:1 <span>No line height</span></li>
<li>line-height:1.3 <span>Compact</span></li>
<li>line-height:1.5 <span>Regular</span></li>
<li>line-height:1.7 <span>Loose</span></li>
</ul>
</div>
### Font-family
```css
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
```
<script>
import bus from '../../bus';
const varMap = {
'$--box-shadow-light': 'boxShadowLight',
'$--box-shadow-base': 'boxShadowBase',
'$--border-radius-base': 'borderRadiusBase',
'$--border-radius-small': 'borderRadiusSmall'
};
const original = {
boxShadowLight: '0 2px 12px 0 rgba(0, 0, 0, 0.1)',
boxShadowBase: '0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)',
borderRadiusBase: '4px',
borderRadiusSmall: '2px'
}
export default {
created() {
bus.$on('user-theme-config-update', this.setGlobal);
},
mounted() {
this.setGlobal();
},
methods: {
setGlobal() {
if (window.userThemeConfig) {
this.global = window.userThemeConfig.global;
}
}
},
data() {
return {
global: {},
boxShadowLight: '',
boxShadowBase: '',
borderRadiusBase: '',
borderRadiusSmall: ''
}
},
watch: {
global: {
immediate: true,
handler(value) {
Object.keys(varMap).forEach((c) => {
if (value[c]) {
this[varMap[c]] = value[c]
} else {
this[varMap[c]] = original[varMap[c]]
}
});
}
}
}
}
</script>
<style>
.demo-border .text {
width: 15%;
}
.demo-border .line {
width: 70%;
}
.demo-border .line div{
width: 100%;
height: 0;
border: 1px solid #EEE;
}
.demo-border .line .dashed{
border: 2px dashed #EEE;
}
.demo-shadow {
height: 100px;
width: 50%;
border: 1px solid #eee;
}
.demo-shadow-text {
line-height: 50px;
color: #666;
font-size: 14px;
}
.demo-radius .title{
color: #666;
font-size: 18px;
margin: 10px 0;
}
.demo-radius .value{
color: #333;
font-size: 16px;
margin: 10px 0;
}
.demo-radius .radius {
height: 60px;
width: 70%;
border: 1px solid #D7DAE2;
border-radius: 0;
margin-top: 20px;
}
.demo-radius .radius-30 {
border-radius: 30px;
}
</style>
## Border
We standardize the borders that can be used in buttons, cards, pop-ups and other components.
### Border
There are few border styles to choose.
<table class="demo-border">
<tbody>
<tr>
<td class="text">Name</td>
<td class="text">Thickness</td>
<td class="line">Demo</td>
</tr>
<tr>
<td class="text">Solid</td>
<td class="text">1px</td>
<td class="line">
<div></div>
</td>
</tr>
<tr>
<td class="text">Dashed</td>
<td class="text">2px</td>
<td class="line">
<div class="dashed"></div>
</td>
</tr>
</tbody>
</table>
### Radius
There are few radius styles to choose.
<el-row :gutter="12" class="demo-radius">
<el-col :span="6" :xs="{span: 12}">
<div class="title">No Radius</div>
<div class="value">border-radius: 0px</div>
<div class="radius"></div>
</el-col>
<el-col :span="6" :xs="{span: 12}">
<div class="title">Small Radius</div>
<div class="value">border-radius: {{borderRadiusSmall}}</div>
<div
class="radius"
:style="{ borderRadius: borderRadiusSmall }"
></div>
</el-col>
<el-col :span="6" :xs="{span: 12}">
<div class="title">Large Radius</div>
<div class="value">border-radius: {{borderRadiusBase}}</div>
<div
class="radius"
:style="{ borderRadius: borderRadiusBase }"
></div>
</el-col>
<el-col :span="6" :xs="{span: 12}">
<div class="title">Round Radius</div>
<div class="value">border-radius: 30px</div>
<div class="radius radius-30"></div>
</el-col>
</el-row>
### Shadow
There are few shaodw styles to choose.
<div
class="demo-shadow"
:style="{ boxShadow: boxShadowBase }"
></div>
<span class="demo-shadow-text">Basic Shaodw box-shadow: {{boxShadowBase}}</span>
<div
class="demo-shadow"
:style="{ boxShadow: boxShadowLight }"
></div>
<span class="demo-shadow-text">Light Shadow box-shadow: {{boxShadowLight}}</span>
This diff is collapsed.
<script>
import bus from '../../bus';
const varMap = [
'$--font-size-extra-large',
'$--font-size-large',
'$--font-size-medium',
'$--font-size-base',
'$--font-size-small',
'$--font-size-extra-small'
];
const original = {
'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'
}
export default {
created() {
bus.$on('user-theme-config-update', this.setGlobal);
},
mounted() {
this.setGlobal();
},
methods: {
tintColor(color, tint) {
return tintColor(color, tint);
},
setGlobal() {
if (window.userThemeConfig) {
this.global = window.userThemeConfig.global;
}
}
},
data() {
return {
global: {},
'font_size_extra_large': '',
'font_size_large': '',
'font_size_medium': '',
'font_size_base': '',
'font_size_small': '',
'font_size_extra_small': ''
}
},
watch: {
global: {
immediate: true,
handler(value) {
varMap.forEach((v) => {
const key = v.replace('$--', '').replace(/-/g, '_')
if (value[v]) {
this[key] = value[v]
} else {
this[key] = original[key]
}
});
}
}
},
}
</script>
<style>
.demo-typo-box {
height: 200px;
width: 200px;
position: relative;
border: 1px solid #eaeefb;
font-size: 40px;
color: #1f2d3d;
text-align: center;
line-height: 162px;
padding-bottom: 36px;
box-sizing: border-box;
display: inline-block;
margin-right: 17px;
border-radius: 4px;
.name {
position: absolute;
bottom: 0;
width: 100%;
height: 35px;
border-top: 1px solid #eaeefb;
font-size: 14px;
color: #8492a6;
line-height: 35px;
text-align: left;
text-indent: 10px;
font-family: 'Helvetica Neue';
}
}
.demo-typo-size {
.h1 {
font-size: 20px;
}
.h2 {
font-size: 18px;
}
.h3 {
font-size: 16px;
}
.text-regular {
font-size: 14px;
}
.text-small {
font-size: 13px;
}
.text-smaller {
font-size: 12px;
}
.color-dark-light {
color: #99a9bf;
}
}
.typo-PingFang {
font-family: 'PingFang SC';
}
.typo-Hiragino {
font-family: 'Hiragino Sans GB';
.demo-term-box img{
width: 24%;
margin: 0 4% 20px 0;
}
.typo-Microsoft {
font-family: 'Microsoft YaHei';
.lineH-left {
display: inline-block;
height: 80px
}
/* 英文 */
.typo-Helvetica-Neue {
font-family: 'Helvetica Neue';
.lineH-right {
display: inline-block;
list-style: none;
padding: 0 0 0 90px;
margin: 0;
vertical-align: top;
}
.typo-Helvetica {
font-family: 'Helvetica';
.lineH-right li{
font-size: 13px;
color: #666;
height: 20px;
line-height: 20px;
}
.typo-Arial {
font-family: 'Arial';
.lineH-right li span{
padding-left: 40px;
}
</style>
......@@ -76,76 +98,85 @@
Creamos una convención de fuentes para asegurar la mejor presentación en diferentes plataformas.
### Fuente en chino
<div class="demo-typo-box typo-PingFang">
和畅惠风
<div class="name">PingFang SC</div>
</div>
<div class="demo-typo-box typo-Hiragino">
和畅惠风
<div class="name">Hiragino Sans GB</div>
### Fuente
<div class="demo-term-box">
<img src="../../assets/images/term-pingfang.png" alt="">
<img src="../../assets/images/term-hiragino.png" alt="">
<img src="../../assets/images/term-microsoft.png" alt="">
<img src="../../assets/images/term-sf.png" alt="">
<img src="../../assets/images/term-helvetica.png" alt="">
<img src="../../assets/images/term-arial.png" alt="">
</div>
<div class="demo-typo-box typo-Microsoft">
和畅惠风
<div class="name">Microsoft YaHei</div>
</div>
### Inglés / Fuente Numérica
<div class="demo-typo-box typo-Helvetica-neue">
RGag
<div class="name">Helvetica Neue</div>
</div>
<div class="demo-typo-box typo-Helvetica">
RGag
<div class="name">Helvetica</div>
</div>
<div class="demo-typo-box typo-Arial">
RGag
<div class="name">Arial</div>
</div>
### Font-family
```css
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
```
### Convención de fuentes
<table class="demo-typo-size">
<tbody>
<tr>
<td class="h1">Main Title</td>
<td class="h1">Build with Element</td>
<td class="color-dark-light">20px Extra large</td>
<tr
>
<td>Level</td>
<td>Font Size</td>
<td class="color-dark-light">Demo</td>
</tr>
<tr>
<td class="h2">Title</td>
<td class="h2">Build with Element</td>
<td class="color-dark-light">18px large</td>
<tr
:style="{ fontSize: font_size_extra_small }"
>
<td>Supplementary text</td>
<td class="color-dark-light">{{font_size_extra_small}} Extra Small</td>
<td>Build with Element</td>
</tr>
<tr>
<td class="h3">Small Title</td>
<td class="h3">Build with Element</td>
<td class="color-dark-light">16px Medium</td>
<tr
:style="{ fontSize: font_size_small }"
>
<td>Body (small)</td>
<td class="color-dark-light">{{font_size_small}} Small</td>
<td>Build with Element</td>
</tr>
<tr>
<td class="text-regular">Body</td>
<td class="text-regular">Build with Element</td>
<td class="color-dark-light">14px Small</td>
<tr
:style="{ fontSize: font_size_base }"
>
<td>Body</td>
<td class="color-dark-light">{{font_size_base}} Base</td>
<td>Build with Element</td>
</tr>
<tr>
<td class="text-small">Body (small)</td>
<td class="text-small">Build with Element</td>
<td class="color-dark-light">13px Extra Small</td>
<tr
:style="{ fontSize: font_size_medium }"
>
<td >Small Title</td>
<td class="color-dark-light">{{font_size_medium}} Medium</td>
<td>Build with Element</td>
</tr>
<tr>
<td class="text-smaller">Supplementary text</td>
<td class="text-smaller">Build with Element</td>
<td class="color-dark-light">12px Extra Extra Small</td>
<tr
:style="{ fontSize: font_size_large }"
>
<td>Title</td>
<td class="color-dark-light">{{font_size_large}} large</td>
<td>Build with Element</td>
</tr>
<tr
:style="{ fontSize: font_size_extra_large }"
>
<td>Main Title</td>
<td class="color-dark-light">{{font_size_extra_large}} Extra large</td>
<td>Build with Element</td>
</tr>
</tbody>
</table>
### Font Line Height
<div>
<img class="lineH-left" src="~examples/assets/images/typography.png" />
<ul class="lineH-right">
<li>line-height:1 <span>No line height</span></li>
<li>line-height:1.3 <span>Compact</span></li>
<li>line-height:1.5 <span>Regular</span></li>
<li>line-height:1.7 <span>Loose</span></li>
</ul>
</div>
### Font-family
```css
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
```
<script>
import bus from '../../bus';
const varMap = {
'$--box-shadow-light': 'boxShadowLight',
'$--box-shadow-base': 'boxShadowBase',
'$--border-radius-base': 'borderRadiusBase',
'$--border-radius-small': 'borderRadiusSmall'
};
const original = {
boxShadowLight: '0 2px 12px 0 rgba(0, 0, 0, 0.1)',
boxShadowBase: '0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)',
borderRadiusBase: '4px',
borderRadiusSmall: '2px'
}
export default {
created() {
bus.$on('user-theme-config-update', this.setGlobal);
},
mounted() {
this.setGlobal();
},
methods: {
setGlobal() {
if (window.userThemeConfig) {
this.global = window.userThemeConfig.global;
}
}
},
data() {
return {
global: {},
boxShadowLight: '',
boxShadowBase: '',
borderRadiusBase: '',
borderRadiusSmall: ''
}
},
watch: {
global: {
immediate: true,
handler(value) {
Object.keys(varMap).forEach((c) => {
if (value[c]) {
this[varMap[c]] = value[c]
} else {
this[varMap[c]] = original[varMap[c]]
}
});
}
}
}
}
</script>
<style>
.demo-border .text {
width: 15%;
}
.demo-border .line {
width: 70%;
}
.demo-border .line div{
width: 100%;
height: 0;
border: 1px solid #EEE;
}
.demo-border .line .dashed{
border: 2px dashed #EEE;
}
.demo-shadow {
height: 100px;
width: 50%;
border: 1px solid #eee;
}
.demo-shadow-text {
line-height: 50px;
color: #666;
font-size: 14px;
}
.demo-radius .title{
color: #666;
font-size: 18px;
margin: 10px 0;
}
.demo-radius .value{
color: #333;
font-size: 16px;
margin: 10px 0;
}
.demo-radius .radius {
height: 60px;
width: 70%;
border: 1px solid #D7DAE2;
border-radius: 0;
margin-top: 20px;
}
.demo-radius .radius-30 {
border-radius: 30px;
}
</style>
## Border
We standardize the borders that can be used in buttons, cards, pop-ups and other components.
### Border
There are few border styles to choose.
<table class="demo-border">
<tbody>
<tr>
<td class="text">Name</td>
<td class="text">Thickness</td>
<td class="line">Demo</td>
</tr>
<tr>
<td class="text">Solid</td>
<td class="text">1px</td>
<td class="line">
<div></div>
</td>
</tr>
<tr>
<td class="text">Dashed</td>
<td class="text">2px</td>
<td class="line">
<div class="dashed"></div>
</td>
</tr>
</tbody>
</table>
### Radius
There are few radius styles to choose.
<el-row :gutter="12" class="demo-radius">
<el-col :span="6" :xs="{span: 12}">
<div class="title">No Radius</div>
<div class="value">border-radius: 0px</div>
<div class="radius"></div>
</el-col>
<el-col :span="6" :xs="{span: 12}">
<div class="title">Small Radius</div>
<div class="value">border-radius: {{borderRadiusSmall}}</div>
<div
class="radius"
:style="{ borderRadius: borderRadiusSmall }"
></div>
</el-col>
<el-col :span="6" :xs="{span: 12}">
<div class="title">Large Radius</div>
<div class="value">border-radius: {{borderRadiusBase}}</div>
<div
class="radius"
:style="{ borderRadius: borderRadiusBase }"
></div>
</el-col>
<el-col :span="6" :xs="{span: 12}">
<div class="title">Round Radius</div>
<div class="value">border-radius: 30px</div>
<div class="radius radius-30"></div>
</el-col>
</el-row>
### Shadow
There are few shaodw styles to choose.
<div
class="demo-shadow"
:style="{ boxShadow: boxShadowBase }"
></div>
<span class="demo-shadow-text">Basic Shaodw box-shadow: {{boxShadowBase}}</span>
<div
class="demo-shadow"
:style="{ boxShadow: boxShadowLight }"
></div>
<span class="demo-shadow-text">Light Shadow box-shadow: {{boxShadowLight}}</span>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
[
{
"lang": "zh-CN",
"display-name": {
"border-color": "边框颜色",
"font-color": "文字颜色",
"background-color": "背景颜色",
"font-weight": "文字粗细",
"font-size": "文字大小",
"font-line-height": "文字行高",
"border-radius": "边框圆角",
"color": "颜色"
},
"action": {
"theme-editor": "主题编辑器",
"no-config": "暂不可编辑,敬请期待",
"reset-theme": "重置",
"download-theme": "下载"
},
"category": {
"BrandColor": "品牌颜色",
"SecondaryColor": "辅助颜色",
"FontColor": "文字颜色",
"BorderColor": "边框颜色",
"BackgroundColor": "背景颜色",
"Other": "其他",
"Color": "颜色",
"Border": "边框",
"Font": "文字",
"Radius": "边框圆角",
"Shadow": "阴影",
"FontSize": "文字大小",
"FontWeight": "文字粗细",
"LineHeight": "文字行高"
}
},
{
"lang": "en-US",
"display-name": {
"border-color": "border color",
"font-color": "font color",
"background-color": "background color",
"font-weight": "font weight",
"font-size": "font size",
"font-line-height": "font line height",
"border-radius": "border radius"
},
"action": {
"theme-editor": "Theme Editor",
"no-config": "Please stay tuned",
"reset-theme": "Reset",
"download-theme": "Download"
},
"category": {
"BrandColor": "Brand Color",
"SecondaryColor": "Secondary Color",
"FontColor": "Font Color",
"BorderColor": "Border Color",
"BackgroundColor": "Background Color",
"FontSize": "Font Size",
"FontWeight": "Font Weight",
"LineHeight": "Line Height"
}
},
{
"lang": "es",
"display-name": {
}
},
{
"lang": "fr-FR",
"display-name": {
}
}
]
......@@ -59,6 +59,10 @@
"path": "/typography",
"title": "Typography 字体"
},
{
"path": "/border",
"title": "Border 边框"
},
{
"path": "/icon",
"title": "Icon 图标"
......@@ -313,6 +317,10 @@
"path": "/typography",
"title": "Typography"
},
{
"path": "/border",
"title": "Border"
},
{
"path": "/icon",
"title": "Icon"
......@@ -567,6 +575,10 @@
"path": "/typography",
"title": "Typography"
},
{
"path": "/border",
"title": "Border"
},
{
"path": "/icon",
"title": "Icon"
......@@ -821,6 +833,10 @@
"path": "/typography",
"title": "Typography"
},
{
"path": "/border",
"title": "Border"
},
{
"path": "/icon",
"title": "Icon"
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -58,7 +58,7 @@
}
@include e(title) {
line-height: $--dialog-line-height;
line-height: $--dialog-font-line-height;
font-size: $--dialog-title-font-size;
color: $--color-text-primary;
}
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment