element-ui/examples/components/theme/theme-card.vue

406 lines
10 KiB
Vue
Raw Permalink Normal View History

2021-06-07 11:56:04 +08:00
<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>