This commit is contained in:
zhuoda
2020-01-11 09:10:29 +08:00
parent e55e1b2f33
commit 215556f73a
608 changed files with 7 additions and 3 deletions

View File

@@ -0,0 +1,67 @@
<template>
<div class="active-plate-main">
<ul class="active-list">
<li class="item" v-for="item in infoList" :key="item.title">
<p class="num" :style="{color:item.color}"><CountTo :end="item.count">{{item.count}}</CountTo></p>
<p class="desc">{{item.title}}</p>
</li>
</ul>
</div>
</template>
<script>
import CountTo from '_c/count-to';
export default {
name: 'activePlate',
components: {
CountTo
},
props: {
// 需要展示的数据集合
infoList: {
type: Array,
require: true
}
}
};
</script>
<style lang="less">
.active-plate-main {
width: 100%;
height: 130px;
.active-list {
display: flex;
list-style: none;
padding-top:15px;
.item {
position: relative;
flex: 1;
text-align: center;
.num {
font-size: 42px;
font-weight: bold;
font-family: sans-serif;
}
.desc {
font-size: 16px;
}
&::after {
position: absolute;
top:18px;
right: 0;
content: "";
display: block;
width: 1px;
height: 56px;
background: #e7eef0;
}
&:nth-last-of-type(1) {
&::after {
background: none;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,73 @@
<template>
<div ref="dom" class="charts chart-bar"></div>
</template>
<script>
import echarts from 'echarts';
import tdTheme from './theme.json';
import { on, off } from '@/lib/util';
echarts.registerTheme('tdTheme', tdTheme);
export default {
// 柱状图
name: 'chartBar',
props: {
// map类型 keys 为图表x轴 values 为图表y轴数据
value: {
type: Object,
require: true
},
// 标题
text: {
type: String,
require: false,
default: ''
},
// 子标题
subtext: {
type: String,
require: false,
default: ''
}
},
data () {
return {
dom: null
};
},
methods: {
resize () {
this.dom.resize();
}
},
mounted () {
this.$nextTick(() => {
let xAxisData = Object.keys(this.value);
let seriesData = Object.values(this.value);
let option = {
title: {
text: this.text,
subtext: this.subtext,
x: 'center'
},
xAxis: {
type: 'category',
data: xAxisData
},
yAxis: {
type: 'value'
},
series: [{
data: seriesData,
type: 'bar'
}]
};
this.dom = echarts.init(this.$refs.dom, 'tdTheme');
this.dom.setOption(option);
on(window, 'resize', this.resize);
});
},
beforeDestroy () {
off(window, 'resize', this.resize);
}
};
</script>

View File

@@ -0,0 +1,3 @@
import ChartPie from './pie.vue';
import ChartBar from './bar.vue';
export { ChartPie, ChartBar };

View File

@@ -0,0 +1,85 @@
<template>
<div ref="dom" class="charts chart-pie"></div>
</template>
<script>
import echarts from 'echarts';
import tdTheme from './theme.json';
import { on, off } from '@/lib/util';
echarts.registerTheme('tdTheme', tdTheme);
export default {
// 饼图
name: 'chartPie',
props: {
// 数据集合
value: {
type: Array,
require: true
},
// 标题
text: {
type: String,
require: true,
default: ''
},
// 子标题
subtext: {
type: String,
require: true,
default: ''
}
},
data () {
return {
dom: null
};
},
methods: {
resize () {
this.dom.resize();
}
},
mounted () {
this.$nextTick(() => {
let legend = this.value.map(_ => _.name);
let option = {
title: {
text: this.text,
subtext: this.subtext,
x: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
data: legend
},
series: [
{
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: this.value,
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
this.dom = echarts.init(this.$refs.dom, 'tdTheme');
this.dom.setOption(option);
on(window, 'resize', this.resize);
});
},
beforeDestroy () {
off(window, 'resize', this.resize);
}
};
</script>

View File

@@ -0,0 +1,490 @@
{
"color": [
"#2d8cf0",
"#19be6b",
"#ff9900",
"#E46CBB",
"#9A66E4",
"#ed3f14"
],
"backgroundColor": "rgba(0,0,0,0)",
"textStyle": {},
"title": {
"textStyle": {
"color": "#516b91"
},
"subtextStyle": {
"color": "#93b7e3"
}
},
"line": {
"itemStyle": {
"normal": {
"borderWidth": "2"
}
},
"lineStyle": {
"normal": {
"width": "2"
}
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true
},
"radar": {
"itemStyle": {
"normal": {
"borderWidth": "2"
}
},
"lineStyle": {
"normal": {
"width": "2"
}
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true
},
"bar": {
"itemStyle": {
"normal": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
},
"emphasis": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
}
}
},
"pie": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"scatter": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"boxplot": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"parallel": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"sankey": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"funnel": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"gauge": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"candlestick": {
"itemStyle": {
"normal": {
"color": "#edafda",
"color0": "transparent",
"borderColor": "#d680bc",
"borderColor0": "#8fd3e8",
"borderWidth": "2"
}
}
},
"graph": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"lineStyle": {
"normal": {
"width": 1,
"color": "#aaa"
}
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true,
"color": [
"#2d8cf0",
"#19be6b",
"#f5ae4a",
"#9189d5",
"#56cae2",
"#cbb0e3"
],
"label": {
"normal": {
"textStyle": {
"color": "#eee"
}
}
}
},
"map": {
"itemStyle": {
"normal": {
"areaColor": "#f3f3f3",
"borderColor": "#516b91",
"borderWidth": 0.5
},
"emphasis": {
"areaColor": "rgba(165,231,240,1)",
"borderColor": "#516b91",
"borderWidth": 1
}
},
"label": {
"normal": {
"textStyle": {
"color": "#000"
}
},
"emphasis": {
"textStyle": {
"color": "rgb(81,107,145)"
}
}
}
},
"geo": {
"itemStyle": {
"normal": {
"areaColor": "#f3f3f3",
"borderColor": "#516b91",
"borderWidth": 0.5
},
"emphasis": {
"areaColor": "rgba(165,231,240,1)",
"borderColor": "#516b91",
"borderWidth": 1
}
},
"label": {
"normal": {
"textStyle": {
"color": "#000"
}
},
"emphasis": {
"textStyle": {
"color": "rgb(81,107,145)"
}
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"toolbox": {
"iconStyle": {
"normal": {
"borderColor": "#999"
},
"emphasis": {
"borderColor": "#666"
}
}
},
"legend": {
"textStyle": {
"color": "#999999"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "#ccc",
"width": 1
},
"crossStyle": {
"color": "#ccc",
"width": 1
}
}
},
"timeline": {
"lineStyle": {
"color": "#8fd3e8",
"width": 1
},
"itemStyle": {
"normal": {
"color": "#8fd3e8",
"borderWidth": 1
},
"emphasis": {
"color": "#8fd3e8"
}
},
"controlStyle": {
"normal": {
"color": "#8fd3e8",
"borderColor": "#8fd3e8",
"borderWidth": 0.5
},
"emphasis": {
"color": "#8fd3e8",
"borderColor": "#8fd3e8",
"borderWidth": 0.5
}
},
"checkpointStyle": {
"color": "#8fd3e8",
"borderColor": "rgba(138,124,168,0.37)"
},
"label": {
"normal": {
"textStyle": {
"color": "#8fd3e8"
}
},
"emphasis": {
"textStyle": {
"color": "#8fd3e8"
}
}
}
},
"visualMap": {
"color": [
"#516b91",
"#59c4e6",
"#a5e7f0"
]
},
"dataZoom": {
"backgroundColor": "rgba(0,0,0,0)",
"dataBackgroundColor": "rgba(255,255,255,0.3)",
"fillerColor": "rgba(167,183,204,0.4)",
"handleColor": "#a7b7cc",
"handleSize": "100%",
"textStyle": {
"color": "#333"
}
},
"markPoint": {
"label": {
"normal": {
"textStyle": {
"color": "#eee"
}
},
"emphasis": {
"textStyle": {
"color": "#eee"
}
}
}
}
}

View File

@@ -0,0 +1,52 @@
<template>
<Icons :is="iconType" :type="iconName" :color="iconColor" :size="iconSize"/>
</template>
<script>
import Icons from '_c/icons';
export default {
name: 'CommonIcon',
components: { Icons },
props: {
// 图标名 eg: icon-name
type: {
type: String,
required: true
},
// 图标颜色
color: {
type: String,
required: false
},
// 图标尺寸
size: {
type: Number,
required: false
}
},
computed: {
iconType () {
return this.type.indexOf('_') === 0 ? 'Icons' : 'Icon';
},
iconName () {
return this.iconType === 'Icons' ? this.getCustomIconName(this.type) : this.type;
},
iconSize () {
return this.size || (this.iconType === 'Icons' ? 12 : undefined);
},
iconColor () {
return this.color || '';
}
},
methods: {
// 返回图标名
getCustomIconName (iconName) {
return iconName.slice(1);
}
}
};
</script>
<style>
</style>

View File

@@ -0,0 +1,2 @@
import CommonIcon from './common-icon.vue';
export default CommonIcon;

View File

@@ -0,0 +1,198 @@
<template>
<div class="count-to-wrapper">
<slot name="left"/>
<p class="content-outer">
<span :class="['count-to-count-text', countClass]" :id="counterId">{{ init }}</span>
<i :class="['count-to-unit-text', unitClass]">{{ unitText }}</i>
</p>
<slot name="right"/>
</div>
</template>
<script>
import CountUp from 'countup';
import './index.less';
export default {
name: 'countTo',
props: {
/**
* @description 默认值
*/
init: {
type: Number,
required: false,
default: 0
},
/**
* @description 起始值,即动画开始前显示的数值
*/
startVal: {
type: Number,
required: false,
default: 0
},
/**
* @description 结束值,即动画结束后显示的数值
*/
end: {
type: Number,
required: true
},
/**
* @description 保留几位小数
*/
decimals: {
type: Number,
required: false,
default: 0
},
/**
* @description 分隔整数和小数的符号,默认是小数点
*/
decimal: {
type: String,
required: false,
default: '.'
},
/**
* @description 动画持续的时间,单位是秒
*/
duration: {
type: Number,
required: false,
default: 2
},
/**
* @description 动画延迟开始的时间,单位是秒
*/
delay: {
type: Number,
required: false,
default: 0
},
/**
* @description 是否禁用easing动画效果
*/
uneasing: {
type: Boolean,
required: false,
default: false
},
/**
* @description 是否使用分组,分组后每三位会用一个符号分隔
*/
usegroup: {
type: Boolean,
required: false,
default: false
},
/**
* @description 用于分组(usegroup)的符号
*/
separator: {
type: String,
required: false,
default: ','
},
/**
* @description 是否简化显示设为true后会使用unit单位来做相关省略
*/
simplify: {
type: Boolean,
required: false,
default: false
},
/**
* @description 自定义单位,如[3, 'K+'], [6, 'M+']即大于3位数小于6位数的用k+来做省略
* 1000即显示为1K+
*/
unit: {
type: Array,
required: false,
default () {
return [[3, 'K+'], [6, 'M+'], [9, 'B+']];
}
},
// 数值样式
countClass: {
type: String,
required: false,
default: ''
},
// 单位样式
unitClass: {
type: String,
required: false,
default: ''
}
},
data () {
return {
// CountUp 实例
counter: null,
// 单位描述
unitText: ''
};
},
computed: {
// 唯一标识
counterId () {
return `count_to_${this._uid}`;
}
},
watch: {
end (newVal) {
let endVal = this.getValue(newVal);
this.counter.update(endVal);
}
},
mounted () {
this.$nextTick(() => {
let endVal = this.getValue(this.end);
this.counter = new CountUp(this.counterId, this.startVal, endVal, this.decimals, this.duration, {
useEasing: !this.uneasing,
useGrouping: this.useGroup,
separator: this.separator,
decimal: this.decimal
});
setTimeout(() => {
if (!this.counter.error) this.counter.start();
}, this.delay);
});
},
methods: {
getHandleVal (val, len) {
return {
endVal: parseInt(val / Math.pow(10, this.unit[len - 1][0])),
unitText: this.unit[len - 1][1]
};
},
transformValue (val) {
let len = this.unit.length;
let res = {
endVal: 0,
unitText: ''
};
if (val < Math.pow(10, this.unit[0][0])) res.endVal = val;
else {
for (let i = 1; i < len; i++) {
if (val >= Math.pow(10, this.unit[i - 1][0]) && val < Math.pow(10, this.unit[i][0])) res = this.getHandleVal(val, i);
}
}
if (val > Math.pow(10, this.unit[len - 1][0])) res = this.getHandleVal(val, len);
return res;
},
getValue (val) {
let res = 0;
if (this.simplify) {
let { endVal, unitText } = this.transformValue(val);
this.unitText = unitText;
res = endVal;
} else {
res = val;
}
return res;
}
}
};
</script>

View File

@@ -0,0 +1,2 @@
import countTo from './count-to.vue';
export default countTo;

View File

@@ -0,0 +1,10 @@
@prefix: ~"count-to";
.@{prefix}-wrapper{
.content-outer{
display: inline-block;
.@{prefix}-unit-text{
font-style: normal;
}
}
}

View File

@@ -0,0 +1,77 @@
<template>
<div class="editor-wrapper">
<div :id="editorId"></div>
</div>
</template>
<script>
import Editor from 'wangeditor';
import 'wangeditor/release/wangEditor.min.css';
import { oneOf } from '@/lib/util';
export default {
name: 'Editor',
props: {
// 缓存Html内容
value: {
type: String,
default: ''
},
/**
* 绑定的值的类型, enum: ['html', 'text']
*/
valueType: {
type: String,
default: 'html',
validator: (val) => {
return oneOf(val, ['html', 'text']);
}
},
/**
* @description 设置change事件触发时间间隔
*/
changeInterval: {
type: Number,
default: 200
},
/**
* @description 是否开启本地存储
*/
cache: {
type: Boolean,
default: true
}
},
computed: {
// 唯一id
editorId () {
return `editor${this._uid}`;
}
},
mounted () {
this.editor = new Editor(`#${this.editorId}`);
this.editor.customConfig.onchange = (html) => {
let text = this.editor.txt.text();
if (this.cache) localStorage.editorCache = html;
this.$emit('input', this.valueType === 'html' ? html : text);
this.$emit('on-change', html, text);
};
this.editor.customConfig.onchangeTimeout = this.changeInterval;
// create这个方法一定要在所有配置项之后调用
this.editor.create();
// 如果本地有存储加载本地存储内容
let html = this.value || localStorage.editorCache;
if (html) this.editor.txt.html(html);
},
methods: {
setHtml (val) {
this.editor.txt.html(val);
}
}
};
</script>
<style lang="less">
.editor-wrapper * {
z-index: 100 !important;
}
</style>

View File

@@ -0,0 +1,2 @@
import Editor from './editor.vue';
export default Editor;

View File

@@ -0,0 +1,38 @@
<template>
<i :class="`iconfont icon-${type}`" :style="styles"></i>
</template>
<script>
export default {
name: 'Icons',
props: {
// 图标名
type: {
type: String,
required: true
},
// 图标颜色
color: {
type: String,
default: '#5c6b77'
},
// 图标尺寸
size: {
type: Number,
default: 16
}
},
computed: {
styles () {
return {
fontSize: `${this.size}px`,
color: this.color
};
}
}
};
</script>
<style>
</style>

View File

@@ -0,0 +1,2 @@
import Icons from './icons.vue';
export default Icons;

View File

@@ -0,0 +1,2 @@
import ABackTop from './index.vue';
export default ABackTop;

View File

@@ -0,0 +1,130 @@
<template>
<div :class="classes" :style="styles" @click="back">
<slot>
<div :class="innerClasses">
<i class="ivu-icon ivu-icon-ios-arrow-up"></i>
</div>
</slot>
</div>
</template>
<script>
import { on, off } from '@/lib/util';
const prefixCls = 'ivu-back-top';
export default {
name: 'ABackTop',
props: {
// 允许出现返回头部高度
height: {
type: Number,
default: 400
},
// 返回头部按钮 距离底部距离
bottom: {
type: Number,
default: 30
},
// 返回头部按钮 距离窗口右侧距离
right: {
type: Number,
default: 30
},
// 滚动速度
duration: {
type: Number,
default: 1000
},
// 可视区
container: {
type: null,
default: window
}
},
data () {
return {
// 是否显示返回顶部按钮
backTop: false
};
},
mounted () {
// window.addEventListener('scroll', this.handleScroll, false)
// window.addEventListener('resize', this.handleScroll, false)
on(this.containerEle, 'scroll', this.handleScroll);
on(this.containerEle, 'resize', this.handleScroll);
},
beforeDestroy () {
// window.removeEventListener('scroll', this.handleScroll, false)
// window.removeEventListener('resize', this.handleScroll, false)
off(this.containerEle, 'scroll', this.handleScroll);
off(this.containerEle, 'resize', this.handleScroll);
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-show`]: this.backTop
}
];
},
styles () {
return {
bottom: `${this.bottom}px`,
right: `${this.right}px`
};
},
innerClasses () {
return `${prefixCls}-inner`;
},
containerEle () {
return this.container === window ? window : document.querySelector(this.container);
}
},
methods: {
handleScroll () {
this.backTop = this.containerEle.scrollTop >= this.height;
},
back () {
let target = typeof this.container === 'string' ? this.containerEle : (document.documentElement || document.body);
const sTop = target.scrollTop;
this.scrollTop(this.containerEle, sTop, 0, this.duration);
this.$emit('on-click');
},
// scrollTop animation
scrollTop (el, from = 0, to, duration = 500, endCallback) {
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = (
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
return window.setTimeout(callback, 1000 / 60);
}
);
}
const difference = Math.abs(from - to);
const step = Math.ceil(difference / duration * 50);
const scroll = (start, end, step) => {
if (start === end) {
endCallback && endCallback();
return;
}
let d = (start + step > end) ? end : start + step;
if (start > end) {
d = (start - step < end) ? end : start - step;
}
if (el === window) {
window.scrollTo(d, d);
} else {
el.scrollTop = d;
}
window.requestAnimationFrame(() => scroll(d, end, step));
};
scroll(from, to, step);
}
}
};
</script>

View File

@@ -0,0 +1,92 @@
<template>
<div v-if="showFullScreenBtn" class="full-screen-btn-con">
<Tooltip :content="value ? '退出全屏' : '全屏'" placement="bottom">
<Icon @click.native="handleChange" :type="value ? 'md-contract' : 'md-expand'" :size="16"></Icon>
</Tooltip>
</div>
</template>
<script>
export default {
name: 'Fullscreen',
props: {
// 当前全屏状态
value: {
type: Boolean,
default: false
}
},
computed: {
showFullScreenBtn () {
return window.navigator.userAgent.indexOf('MSIE') < 0;
}
},
mounted () {
let isFullscreen = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen;
isFullscreen = !!isFullscreen;
document.addEventListener('fullscreenchange', () => {
this.$emit('input', !this.value);
this.$emit('on-change', !this.value);
});
document.addEventListener('mozfullscreenchange', () => {
this.$emit('input', !this.value);
this.$emit('on-change', !this.value);
});
document.addEventListener('webkitfullscreenchange', () => {
this.$emit('input', !this.value);
this.$emit('on-change', !this.value);
});
document.addEventListener('msfullscreenchange', () => {
this.$emit('input', !this.value);
this.$emit('on-change', !this.value);
});
this.$emit('input', isFullscreen);
},
methods: {
handleFullscreen () {
let main = document.body;
if (this.value) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
} else {
if (main.requestFullscreen) {
main.requestFullscreen();
} else if (main.mozRequestFullScreen) {
main.mozRequestFullScreen();
} else if (main.webkitRequestFullScreen) {
main.webkitRequestFullScreen();
} else if (main.msRequestFullscreen) {
main.msRequestFullscreen();
}
}
},
handleChange () {
this.handleFullscreen();
}
}
};
</script>
<style lang="less">
.full-screen-btn-con .ivu-tooltip-rel {
height: 52px;
line-height: 52px;
padding:0 12px;
&:hover {
background: #f7f7f8;
cursor: pointer;
}
cursor: pointer;
i {
cursor: pointer;
color:#909ca4;
}
}
</style>

View File

@@ -0,0 +1,2 @@
import Fullscreen from './fullscreen.vue';
export default Fullscreen;

View File

@@ -0,0 +1,4 @@
.custom-bread-crumb{
display: inline-block;
vertical-align: top;
}

View File

@@ -0,0 +1,44 @@
<template>
<div class="custom-bread-crumb">
<Breadcrumb :style="{fontSize: `${fontSize}px`}">
<BreadcrumbItem v-for="item in list" :to="item.to" :key="`bread-crumb-${item.name}`">
{{ showTitle(item) }}
</BreadcrumbItem>
</Breadcrumb>
</div>
</template>
<script>
import { showTitle } from '@/lib/menu-func';
import './custom-bread-crumb.less';
export default {
name: 'CustomBreadCrumb',
props: {
// 面包屑集合
list: {
type: Array,
default: () => []
},
// 面包屑显示字体大小
fontSize: {
type: Number,
default: 14
},
// 是否显示图标
showIcon: {
type: Boolean,
default: false
}
},
methods: {
showTitle (item) {
return showTitle(item, this);
},
isCustomIcon (iconName) {
return iconName.indexOf('_') === 0;
},
getCustomIconName (iconName) {
return iconName.slice(1);
}
}
};
</script>

View File

@@ -0,0 +1,2 @@
import customBreadCrumb from './custom-bread-crumb.vue';
export default customBreadCrumb;

View File

@@ -0,0 +1,14 @@
.header-bar{
width: 100%;
height: 100%;
position: relative;
.custom-content-con{
float: right;
height: auto;
padding-right: 20px;
// line-height: 64px;
& > *{
float: right;
}
}
}

View File

@@ -0,0 +1,39 @@
<template>
<div class="header-bar">
<SiderTrigger :collapsed="collapsed" icon="icon iconfont icondaohangzhedie" @on-change="handleCollpasedChange"/>
<CustomBreadCrumb show-icon style="margin-left: 30px;" :list="breadCrumbList"/>
<div class="custom-content-con">
<slot></slot>
</div>
</div>
</template>
<script>
import SiderTrigger from './sider-trigger';
import CustomBreadCrumb from './custom-bread-crumb';
import './header-bar.less';
export default {
name: 'HeaderBar',
components: {
SiderTrigger,
CustomBreadCrumb
},
props: {
// 折叠状态
collapsed: {
type: Boolean,
require: false
}
},
computed: {
// 面包屑集合
breadCrumbList () {
return this.$store.state.app.breadCrumbList;
}
},
methods: {
handleCollpasedChange (state) {
this.$emit('on-coll-change', state);
}
}
};
</script>

View File

@@ -0,0 +1,2 @@
import HeaderBar from './header-bar';
export default HeaderBar;

View File

@@ -0,0 +1,2 @@
import siderTrigger from './sider-trigger.vue';
export default siderTrigger;

View File

@@ -0,0 +1,21 @@
.trans{
transition: transform .2s ease;
}
@size: 40px;
.sider-trigger-a{
padding: 6px;
width: @size;
height: @size;
display: inline-block;
text-align: center;
color: #5c6b77;
margin-top: 10px;
i{
.trans;
vertical-align: top;
}
&.collapsed i{
transform: rotateZ(180deg);
.trans;
}
}

View File

@@ -0,0 +1,35 @@
<template>
<a @click="handleChange" type="text" :class="['sider-trigger-a', collapsed ? 'collapsed' : '']">
<Icon :type="icon" :size="size"/>
</a>
</template>
<script>
export default {
name: 'SiderTrigger',
props: {
// 折叠状态
collapsed: {
type: Boolean,
require: false
},
// 图标名称
icon: {
type: String,
default: 'navicon-round'
},
// 图标尺寸
size: {
type: Number,
default: 20
}
},
methods: {
handleChange () {
this.$emit('on-change', !this.collapsed);
}
}
};
</script>
<style lang="less">
@import "./sider-trigger.less";
</style>

View File

@@ -0,0 +1,2 @@
import Language from './language.vue';
export default Language;

View File

@@ -0,0 +1,54 @@
<template>
<div>
<Dropdown trigger="click" @on-click="selectLang">
<a href="javascript:void(0)">
{{ title }}
<Icon :size="18" type="md-arrow-dropdown"/>
</a>
<DropdownMenu slot="list">
<DropdownItem v-for="(value, key) in localList" :name="key" :key="`lang-${key}`">{{ value }}</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</template>
<script>
export default {
name: 'Language',
props: {
lang: {
type: String,
require: false
}
},
data () {
return {
langList: {
'zh-CN': '语言',
'zh-TW': '語言',
'en-US': 'Lang'
},
localList: {
'zh-CN': '中文简体',
'zh-TW': '中文繁体',
'en-US': 'English'
}
};
},
watch: {
lang (lang) {
this.$i18n.locale = lang;
}
},
computed: {
title () {
return this.langList[this.lang];
}
},
methods: {
selectLang (name) {
this.$emit('on-lang-change', name);
}
}
};
</script>

View File

@@ -0,0 +1,371 @@
<template>
<div class="notice">
<Tooltip content="消息" placement="bottom">
<Badge :count="noticeNumber" class-name="demo-badge-alone">
<Icon @click="openNotice" class="demo-badge" size="18" type="icon iconfont iconnews"></Icon>
</Badge>
</Tooltip>
<div :class="modalOpen?'show':''" class="notice-main">
<div :class="modalOpen?'show':''" @click="modalOpen=false" class="notice-bg"></div>
<div :class="modalOpen?'show':''" class="notice-box">
<div class="header">
<div class="item">消息通知({{noticeNumber}})</div>
<Icon @click="modalClose" class="close" size="24" type="ios-close" />
</div>
<div class="notice-list" ref="noticeList">
<div :key="item.id" @click.stop="getDetail(item)" class="item" v-for="item in noticeList">
<img alt src="@/assets/images/message.png" />
<div class="info">
<p class="title">{{item.title}}</p>
<p class="time">{{item.updateTime}}</p>
</div>
</div>
<InfiniteLoading @infinite="scroll" ref="infiniteLoading">
<span slot="no-more">暂时没有新消息啦</span>
</InfiniteLoading>
</div>
</div>
<div :class="detailModalOpen?'show':''" class="notice-detail">
<div class="title">
<div class="item">{{noticeDetail.title}}</div>
<Icon @click="detailModalOpen=false" class="close" type="md-close" />
</div>
<div class="detail">{{noticeDetail.content}}</div>
<p class="time">{{noticeDetail.updateTime}}</p>
</div>
</div>
</div>
</template>
<script>
import { noticeApi } from '@/api/notice';
import { socketBaseUrl } from '@/lib/http';
import InfiniteLoading from 'vue-infinite-loading';
export default {
name: 'Notice',
components: {
InfiniteLoading
},
data() {
return {
loading: false,
// Websocket心跳间隔
heartTimer: null,
// 是否展示消息列表
modalOpen: false,
socketBaseUrl,
// websocket 实例
socket: null,
// 是否展示消息详情
detailModalOpen: false,
searchData: {
pageSize: 10,
pageNum: 1
},
noticeDetail: {
title: '',
content: '',
updateTime: ''
}
};
},
mounted() {
this.initWebSocket();
},
computed: {
// 消息集合
noticeList() {
return this.$store.state.notice.noticeList;
},
// 消息数量
noticeNumber() {
return this.$store.state.notice.noticeNumber;
},
userInfo() {
return this.$store.state.user.userLoginInfo;
}
},
methods: {
initWebSocket() {
this.socket = new WebSocket(
`${this.socketBaseUrl}webSocket/${this.userInfo.id}`
);
this.socket.onopen = this.websocketOnopen;
this.socket.onerror = this.websocketOnerror;
this.socket.onmessage = this.websocketOnmessage;
this.socket.onclose = this.websocketClose;
},
// socket连接成功
websocketOnopen() {
this.heartTimer = setInterval(() => {
this.heartbeat();
}, 5000);
this.$once('hook:beforeDestroy', () => {
clearInterval(this.heartTimer);
this.$store.commit('restNotice');
});
},
// socket连接出错
websocketOnerror() {
console.log('socket出错啦');
},
// socket收到消息
websocketOnmessage(e) {
this.$store.commit('updateNoticeNum', Number(e.data));
},
// socket关闭
websocketClose() {
console.log('socket掉线啦');
},
// 心跳包
heartbeat() {
let data = {
jsonStr: '',
messageType: 3
};
let subStr = {
employeeId: this.userInfo.id
};
data.jsonStr = JSON.stringify(subStr);
this.socket.send(JSON.stringify(data));
},
async getDetail(item) {
try {
let result = await noticeApi.getNoticeDetail(item.id);
this.noticeDetail = result.data;
} catch (error) {
//TODO zhuoda sentry
console.error(e);
}
if (this.detailModalOpen) {
this.detailModalOpen = false;
setTimeout(() => {
this.detailModalOpen = true;
}, 100);
} else {
this.detailModalOpen = true;
}
try {
let result = await noticeApi.addNoticeRecord(item.id);
} catch (error) {
//TODO zhuoda sentry
console.error(e);
}
},
scroll($state) {
this.getNoticeList($state);
},
async getNoticeList($state) {
const result = await noticeApi.getNoticeUnreadList({
...this.searchData
});
// 请求通知成功,全局更改消息数量
this.$store.commit('updateNoticeNum', result.data.total);
$state && $state.loaded();
if (!result.data.list.length) {
$state && $state.complete();
} else {
this.searchData.pageNum++;
}
this.$store.commit('updateNotice', result.data.list);
},
openNotice() {
this.searchData.pageNum = 1;
this.$store.commit('restNotice');
this.getNoticeList();
this.modalOpen = true;
},
modalClose() {
this.getNoticeList();
this.detailModalOpen = false;
this.modalOpen = false;
}
}
};
</script>
<style lang='less'>
.notice {
&:hover {
background: #f7f7f8;
cursor: pointer;
}
}
.demo-badge {
margin: 0 10px;
border-radius: 6px;
display: inline-block;
color: #909ca4;
font-size: 18px;
cursor: pointer;
}
.demo-badge-alone {
background: #f47f92 !important;
top: 5px !important;
right: 15px !important;
}
.notice-main {
position: fixed;
z-index: -100;
opacity: 0;
width: 100vw;
height: 100vh;
transition: all 0.5s;
top: 0;
left: 0;
&.show {
z-index: 993;
opacity: 1;
}
}
.notice-bg {
position: fixed;
z-index: -100;
opacity: 0;
width: 100vw;
height: 100vh;
transition: all 0.5s;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.2);
&.show {
z-index: 994;
opacity: 1;
}
}
.notice-box {
position: fixed;
right: 0;
bottom: -650px;
background: #fff;
z-index: 997;
width: 336px;
height: 650px;
transition: all 0.5s;
box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.1) !important;
border: none !important;
overflow: auto;
&.show {
bottom: 0;
}
.header {
position: relative;
height: 50px;
border-bottom: 1px solid #eee;
.close {
cursor: pointer;
position: absolute;
right: 25px;
top: 15px;
}
.item {
margin: 0 20px;
line-height: 50px;
text-align: center;
font-size: 14px;
color: #333;
// border-bottom: 1px #e8e8e8 solid;
}
}
.notice-list {
height: calc(~'100% - 50px') !important;
padding-top: 10px;
overflow: auto;
.no-data {
text-align: center;
}
.item {
position: relative;
height: 70px;
display: flex;
padding: 0 20px;
align-content: center;
align-items: center;
cursor: pointer;
border-bottom: 1px #e8e8e8 solid;
&:hover {
background: #f4f4f4;
&::before {
position: absolute;
left: 0;
content: '';
width: 2px;
height: 100%;
background: #5cb85c;
}
}
img {
width: 40px;
height: 40px;
}
.info {
height: 100%;
display: flex;
flex-direction: column;
flex: 1;
padding: 0 10px;
align-content: flex-start;
justify-content: center;
.title {
line-height: 1.8;
font-size: 14px;
font-weight: bold;
}
.time {
line-height: 1.5;
color: #999;
}
}
}
}
}
.notice-detail {
position: fixed;
z-index: 995;
border-right: 1px #e8e8e8 solid;
bottom: 0;
right: -336px;
width: 400px;
height: 650px;
background: #fff;
opacity: 0;
transition: all 0.5s;
&.show {
right: 336px;
opacity: 1;
}
.title {
height: 50px;
line-height: 50px;
text-align: center;
font-size: 14px;
border-bottom: 1px solid #eee;
.close {
cursor: pointer;
position: absolute;
right: 25px;
top: 12px;
}
.item {
margin: 0 20px;
line-height: 50px;
text-align: center;
font-size: 14px;
color: #009688;
// border-bottom: 1px #e8e8e8 solid;
}
}
.detail {
font-size: 14px;
padding: 20px;
line-height: 20px;
}
.time {
padding: 0 20px;
color: #999;
text-align: right;
}
}
</style>

View File

@@ -0,0 +1,84 @@
<template>
<Dropdown ref="dropdown"
@on-click="handleClick"
:class="hideTitle ? '' : 'collased-menu-dropdown'"
:transfer="hideTitle"
:placement="placement"
transfer-class-name="menu-dropdown">
<a class="drop-menu-a"
type="text"
@mouseover="handleMousemove($event, children)"
:style="{textAlign: !hideTitle ? 'left' : ''}">
<CommonIcon :size="rootIconSize"
:color="textColor"
:type="parentItem.icon" />
<span class="menu-title"
v-if="!hideTitle">{{ showTitle(parentItem) }}</span>
<Icon style="float: right;"
v-if="!hideTitle"
type="ios-arrow-forward"
:size="16" />
</a>
<DropdownMenu ref="dropdown"
slot="list">
<template v-for="child in children">
<CollapsedMenu v-if="showChildren(child)"
:icon-size="iconSize"
:parent-item="child"
:key="`drop-${child.name}`"/>
<DropdownItem v-else
:key="`drop-${child.name}`"
:name="child.name">
<CommonIcon :size="iconSize"
:type="child.icon" />
<span class="menu-title">{{ showTitle(child) }}</span>
</DropdownItem>
</template>
</DropdownMenu>
</Dropdown>
</template>
<script>
import mixin from './mixin';
import itemMixin from './item-mixin';
import { findNodeUpperByClasses } from '@/lib/menu-func';
export default {
name: 'CollapsedMenu',
mixins: [ mixin, itemMixin ],
props: {
// 是否隐藏标题
hideTitle: {
type: Boolean,
default: false
},
// 图标尺寸
rootIconSize: {
type: Number,
default: 16
}
},
data () {
return {
// 下拉菜单出现的位置,
// 可选值为top,top-start,top-end,bottom,bottom-start,bottom-end,left,left-start,left-end,
// right,right-start,right-end, 2.12.0 版本开始支持自动识别
placement: 'right-end'
};
},
mounted () {
let dropdown = findNodeUpperByClasses(this.$refs.dropdown.$el, ['ivu-select-dropdown', 'ivu-dropdown-transfer']);
if (dropdown) dropdown.style.overflow = 'visible';
},
methods: {
handleClick (name) {
this.$emit('on-click', name);
},
handleMousemove (event, children) {
const { pageY } = event;
const height = children.length * 38;
const isOverflow = pageY + height < window.innerHeight;
this.placement = isOverflow ? 'right-start' : 'right-end';
}
}
};
</script>

View File

@@ -0,0 +1,2 @@
import SideMenu from './side-menu.vue';
export default SideMenu;

View File

@@ -0,0 +1,30 @@
export default {
props: {
// 父文件
parentItem: {
type: Object,
default: () => { }
},
// 主题
theme: {
type: String,
require: false
},
// 图标尺寸
iconSize: {
type: Number,
require: false
}
},
computed: {
parentName () {
return this.parentItem.name;
},
children () {
return this.parentItem.children;
},
textColor () {
return this.theme === 'dark' ? '#fff' : '#495060';
}
}
};

View File

@@ -0,0 +1,18 @@
import CommonIcon from '_c/common-icon';
import { showTitle } from '@/lib/menu-func';
export default {
components: {
CommonIcon
},
methods: {
showTitle (item) {
return showTitle(item, this);
},
showChildren (item) {
return item.children && (item.children.length > 1 || (item.meta && item.meta.showAlways));
},
getNameOrHref (item, children0) {
return item.href ? `isTurnByHref_${item.href}` : (children0 ? item.children[0].name : item.name);
}
}
};

View File

@@ -0,0 +1,35 @@
<template>
<Submenu :name="`${parentName}`">
<template slot="title">
<CommonIcon :type="parentItem.icon || ''" />
<span>{{ showTitle(parentItem) }}</span>
</template>
<template v-for="item in children">
<template v-if="item.children && item.children.length > 0">
<side-menu-item :key="`menu-${item.name}`" :parent-item="item" v-if="showChildren(item)"></side-menu-item>
<MenuItem :key="`menu-${item.children[0].name}`" :name="getNameOrHref(item, true)" v-else>
<CommonIcon
:key="`menu-${item.children[0].name}-common-icon`"
:type="item.children[0].icon || ''"
/>
<span :key="`menu-${item.children[0].name}-span`">{{ showTitle(item.children[0]) }}</span>
</MenuItem>
</template>
<template v-else>
<side-menu-item :key="`menu-${item.name}`" :parent-item="item" v-if="showChildren(item)"></side-menu-item>
<MenuItem :key="`menu-${item.name}`" :name="getNameOrHref(item)" v-else>
<CommonIcon :key="`menu-${item.name}-common-icon`" :type="item.icon || ''" />
<span :key="`menu-${item.name}-span`">{{ showTitle(item) }}</span>
</MenuItem>
</template>
</template>
</Submenu>
</template>
<script>
import mixin from './mixin';
import itemMixin from './item-mixin';
export default {
name: 'SideMenuItem',
mixins: [mixin, itemMixin]
};
</script>

View File

@@ -0,0 +1,39 @@
.side-menu-wrapper{
user-select: none;
.menu-collapsed{
padding-top: 10px;
.ivu-dropdown{
.ivu-dropdown-rel a{
width: 100%;
}
}
.ivu-tooltip{
width: 100%;
.ivu-tooltip-rel{
width: 100%;
}
.ivu-tooltip-popper .ivu-tooltip-content{
.ivu-tooltip-arrow{
border-right-color: #fff;
}
.ivu-tooltip-inner{
background: #fff;
color: #495060;
}
}
}
}
a.drop-menu-a{
display: inline-block;
padding: 6px 15px;
width: 100%;
text-align: center;
color: #495060;
}
}
.menu-title{
padding-left: 6px;
}

View File

@@ -0,0 +1,187 @@
<template>
<div class="side-menu-wrapper">
<slot></slot>
<Menu
:accordion="accordion"
:active-name="activeName"
:open-names="openedNames"
:theme="theme"
@on-select="handleSelect"
ref="menu"
v-show="!collapsed"
width="auto"
>
<template v-for="item in menuList">
<template v-if="item.children && item.children.length === 1">
<side-menu-item :key="`menu-${item.name}`" :parent-item="item" v-if="showChildren(item)"></side-menu-item>
<menu-item
:key="`menu-${item.children[0].name}`"
:name="getNameOrHref(item, true)"
v-else
>
<common-icon :type="item.children[0].icon || ''" />
<span>{{ showTitle(item.children[0]) }}</span>
</menu-item>
</template>
<template v-else>
<side-menu-item :key="`menu-${item.name}`" :parent-item="item" v-if="showChildren(item)"></side-menu-item>
<menu-item :key="`menu-${item.name}`" :name="getNameOrHref(item)" v-else>
<common-icon :type="item.icon || ''" />
<span>{{ showTitle(item) }}</span>
</menu-item>
</template>
</template>
</Menu>
<div :list="menuList" class="menu-collapsed" v-show="collapsed">
<template v-for="item in menuList">
<CollapsedMenu
:icon-size="iconSize"
:key="`drop-menu-${item.name}`"
:parent-item="item"
:root-icon-size="rootIconSize"
:theme="theme"
@on-click="handleSelect"
hide-title
v-if="item.children && item.children.length > 1"
></CollapsedMenu>
<Tooltip
:content="showTitle(item.children && item.children[0] ? item.children[0] : item)"
:key="`drop-menu-${item.name}`"
placement="right"
transfer
v-else
>
<a
:style="{textAlign: 'center'}"
@click="handleSelect(getNameOrHref(item, true))"
class="drop-menu-a"
>
<CommonIcon
:color="textColor"
:size="rootIconSize"
:type="item.icon || (item.children && item.children[0].icon)"
/>
</a>
</Tooltip>
</template>
</div>
</div>
</template>
<script>
import SideMenuItem from './side-menu-item.vue';
import CollapsedMenu from './collapsed-menu.vue';
import { getUnion } from '@/lib/util';
import mixin from './mixin';
export default {
name: 'SideMenu',
mixins: [mixin],
components: {
SideMenuItem,
CollapsedMenu
},
props: {
// 菜单集合
menuList: {
type: Array,
default() {
return [];
}
},
// 显示状态 如果是折叠状态就不显示
collapsed: {
type: Boolean
},
// 主题
theme: {
type: String,
default: 'dark'
},
// 父图标尺寸
rootIconSize: {
type: Number,
default: 16
},
// 图标尺寸
iconSize: {
type: Number,
default: 16
},
// 是否开启手风琴模式,开启后每次至多展开一个子菜单
accordion: {
type: Boolean,
require: false
},
// 激活菜单的 name 值
activeName: {
type: String,
default: ''
},
// 展开的 Submenu 的 name 集合
openNames: {
type: Array,
default: () => []
}
},
data() {
return {
openedNames: []
};
},
computed: {
textColor() {
return this.theme === 'dark' ? '#fff' : '#495060';
}
},
watch: {
activeName(name) {
if (this.accordion) {
this.openedNames = this.getOpenedNamesByActiveName(name);
} else {
this.openedNames = getUnion(
this.openedNames,
this.getOpenedNamesByActiveName(name)
);
}
},
openNames(newNames) {
this.openedNames = newNames;
},
openedNames() {
this.$nextTick(() => {
this.$refs.menu.updateOpened();
});
}
},
mounted() {
this.openedNames = getUnion(
this.openedNames,
this.getOpenedNamesByActiveName(name)
);
},
methods: {
updateActiveName(name){
this.$nextTick(() => {
this.$refs.menu.updateOpened();
this.$refs.menu.updateActiveName(name);
});
},
handleSelect(name) {
this.$emit('on-select', name);
},
// 从激活菜单的名称中获取打开的菜单
getOpenedNamesByActiveName(name) {
return this.$route.matched
.map(item => item.name)
.filter(item => item !== name);
},
updateOpenName(name) {
if (name === this.$config.homeName) this.openedNames = [];
else this.openedNames = this.getOpenedNamesByActiveName(name);
}
}
};
</script>
<style lang="less">
@import './side-menu.less';
</style>

View File

@@ -0,0 +1,2 @@
import TagsNav from './tags-nav.vue';
export default TagsNav;

View File

@@ -0,0 +1,136 @@
.no-select{
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.size{
height: 100%;
}
.tags-nav{
position: relative;
margin:0 auto;
// border-top: 1px solid #F0F0F0;
// border-bottom: 1px solid #F0F0F0;
background: #f4f6f8;
padding:0 20px;
.no-select;
.size;
.close-con{
position: absolute;
right: 0;
top: 5px;
height: 30px;
width: 42px;
background: #f3f5f7;
text-align: center;
z-index: 10;
margin-top:5px;
border-top: 1px solid #F5F7FC;
border-bottom: 1px solid #F5F7FC;
i{
color:#666666;
}
button{
margin-top:3px;
}
}
.btn-con{
margin-top:10px;
position: absolute;
top: 0px;
height: 30px;
line-height: 30px;
// padding:0 10px;
background: #f3f5f7;
z-index: 10;
button{
position: relative;
top:-1px;
line-height: 14px;
text-align: center;
color:#666666;
}
&.left-btn{
left: 0px;
border: 1px solid #F5F7FC;
}
&.right-btn{
right: 42px;
border: 1px solid #F5F7FC;
}
}
.scroll-outer{
position: absolute;
left: 52px;
right: 61px;
top: 0;
bottom: 0;
padding:10px 0;
.scroll-body{
display: inline-block;
position: absolute;
box-shadow: border-box;
overflow: visible;
white-space: nowrap;
transition: left .3s ease;
.ivu-tag{
height:34px;
margin:0;
}
.ivu-tag{
// min-width:10px;
height:30px;
line-height: 30px;
border-radius: 3px;
padding:0 10px;
background: #fff!important;
border:none;
text-align: center;
color:#1C2B36;
font-size: 14px;
margin:0 5px;
span{
color:#1C2B36!important;
padding:0 5px;
}
i{
color:#1C2B36!important;
font-size: 4px;
}
}
.ivu-tag-primary{
transition: background .2s ease;
background: #2D8CF0!important;
span,i{
color:#fff!important;
}
}
}
}
.contextmenu {
position: absolute;
margin: 0;
padding: 5px 0;
background: #fff;
z-index: 1000;
list-style-type: none;
border-radius: 3px;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .1);
li {
margin: 0;
padding: 5px 15px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
}

View File

@@ -0,0 +1,256 @@
<template>
<div class="tags-nav">
<div class="close-con">
<Dropdown transfer @on-click="handleTagsOption">
<Button size="small" type="text">
<Icon :size="14" type="icon iconfont iconguanbianniu" />
</Button>
<DropdownMenu slot="list">
<DropdownItem name="close-all">关闭所有</DropdownItem>
<DropdownItem name="close-others">关闭其他</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
<ul
v-show="visible"
:style="{left: contextMenuLeft + 'px', top: contextMenuTop + 'px'}"
class="contextmenu"
>
<li v-for="(item, key) of menuList" @click="handleTagsOption(key)" :key="key">{{item}}</li>
</ul>
<div class="btn-con left-btn">
<Button type="text" @click="handleScroll(240)">
<Icon :size="14" type="icon iconfont iconxiangzuojiantou" />
</Button>
</div>
<div class="btn-con right-btn">
<Button type="text" @click="handleScroll(-240)">
<Icon :size="14" type="icon iconfont iconxiangyoujiantou" />
</Button>
</div>
<div
class="scroll-outer"
ref="scrollOuter"
@DOMMouseScroll="handlescroll"
@mousewheel="handlescroll"
>
<div ref="scrollBody" class="scroll-body" :style="{left: tagBodyLeft + 'px'}">
<transition-group name="taglist-moving-animation">
<Tag
v-for="(item, index) in list"
ref="tagsPageOpened"
:key="`tag-nav-${index}`"
:name="item.name"
:data-route-item="item"
@on-close="handleClose(item)"
@click.native="handleClick(item)"
:closable="item.name !== $config.homeName"
:color="isCurrentTag(item) ? 'primary' : 'default'"
@contextmenu.prevent.native="contextMenu(item, $event)"
>{{ showTitleInside(item) }}</Tag>
</transition-group>
</div>
</div>
</div>
</template>
<script>
import { showTitle, routeEqual } from '@/lib/menu-func';
import beforeClose from '@/router/before-close';
export default {
name: 'TagsNav',
props: {
// 当前激活路由
value: {
type: Object,
require: false
},
// 数据集合
list: {
type: Array,
default () {
return [];
}
}
},
data () {
return {
tagBodyLeft: 0,
rightOffset: 40,
outerPadding: 4,
contextMenuLeft: 0,
contextMenuTop: 0,
visible: false,
menuList: {
others: '关闭其他',
all: '关闭所有'
}
};
},
computed: {
// 当前激活路由
currentRouteObj () {
const { name, params, query } = this.value;
return { name, params, query };
}
},
watch: {
$route (to) {
this.getTagElementByRoute(to);
},
visible (value) {
if (value) {
document.body.addEventListener('click', this.closeMenu);
} else {
document.body.removeEventListener('click', this.closeMenu);
}
}
},
mounted () {
setTimeout(() => {
this.getTagElementByRoute(this.$route);
}, 200);
},
methods: {
handlescroll (e) {
let type = e.type;
let delta = 0;
if (type === 'DOMMouseScroll' || type === 'mousewheel') {
delta = e.wheelDelta ? e.wheelDelta : -(e.detail || 0) * 40;
}
this.handleScroll(delta);
},
// 左右切换
handleScroll (offset) {
const outerWidth = this.$refs.scrollOuter.offsetWidth;
const bodyWidth = this.$refs.scrollBody.offsetWidth;
if (offset > 0) {
this.tagBodyLeft = Math.min(0, this.tagBodyLeft + offset);
} else {
if (outerWidth < bodyWidth) {
if (this.tagBodyLeft < -(bodyWidth - outerWidth)) {
this.tagBodyLeft = this.tagBodyLeft;
} else {
this.tagBodyLeft = Math.max(
this.tagBodyLeft + offset,
outerWidth - bodyWidth - 35
);
}
} else {
this.tagBodyLeft = 0;
}
}
},
handleTagsOption (type) {
if (type.includes('all')) {
// 关闭所有除了home
let res = this.list.filter(item => item.name === this.$config.homeName);
this.$emit('on-close', res, 'all');
} else if (type.includes('others')) {
// 关闭除当前页和home页的其他页
let res = this.list.filter(
item =>
routeEqual(this.currentRouteObj, item) ||
item.name === this.$config.homeName
);
this.$emit('on-close', res, 'others', this.currentRouteObj);
setTimeout(() => {
this.getTagElementByRoute(this.currentRouteObj);
}, 100);
}
},
handleClose (current) {
if (
current.meta &&
current.meta.beforeCloseName &&
current.meta.beforeCloseName in beforeClose
) {
new Promise(beforeClose[current.meta.beforeCloseName]).then(close => {
if (close) {
this.close(current);
}
});
} else {
this.close(current);
}
},
close (route) {
let res = this.list.filter(item => !routeEqual(route, item));
this.$emit('on-close', res, undefined, route);
},
// 点击标签页
handleClick (item) {
// 标记从tag跳转 不清除缓存和刷新页面
if (item.params) {
item.params.isRefresh = false;
} else {
item.params = { isRefresh: false };
}
this.$emit('input', item);
},
showTitleInside (item) {
return showTitle(item, this);
},
isCurrentTag (item) {
return routeEqual(this.currentRouteObj, item);
},
moveToView(tag) {
const outerWidth = this.$refs.scrollOuter.offsetWidth - 50;
const bodyWidth = this.$refs.scrollBody.offsetWidth;
let padding = 35;
if (bodyWidth < outerWidth) {
this.tagBodyLeft = 0;
} else if (tag.offsetLeft < -this.tagBodyLeft) {
// 标签在可视区域左侧
this.tagBodyLeft = -tag.offsetLeft + this.outerPadding + padding;
} else if (
tag.offsetLeft > -this.tagBodyLeft &&
tag.offsetLeft + tag.offsetWidth < -this.tagBodyLeft + outerWidth
) {
// 标签在可视区域
this.tagBodyLeft = Math.min(
0,
outerWidth -
tag.offsetWidth -
tag.offsetLeft -
this.outerPadding -
padding
);
} else {
// 标签在可视区域右侧
this.tagBodyLeft = -(
tag.offsetLeft -
(outerWidth - this.outerPadding - tag.offsetWidth - padding)
);
}
},
getTagElementByRoute (route) {
this.$nextTick(() => {
this.refsTag = this.$refs.tagsPageOpened;
this.refsTag.forEach((item, index) => {
if (routeEqual(route, item.$attrs['data-route-item'])) {
let tag = this.refsTag[index].$el;
this.moveToView(tag);
}
});
});
},
contextMenu (item, e) {
if (item.name === this.$config.homeName) {
return;
}
this.visible = true;
const offsetLeft = this.$el.getBoundingClientRect().left;
this.contextMenuLeft = e.clientX - offsetLeft + 10;
this.contextMenuTop = e.clientY - 64;
},
closeMenu () {
this.visible = false;
}
}
};
</script>
<style lang="less">
@import "./tags-nav.less";
</style>

View File

@@ -0,0 +1,2 @@
import User from './user.vue';
export default User;

View File

@@ -0,0 +1,25 @@
.user-avatar-dropdown{
padding-left:20px;
color:#909ca4;
.dropdown-arrows{
margin:0 10px;
}
.head{
position: relative;
top:-2px;
}
}
.user{
&-avatar-dropdown{
cursor: pointer;
display: inline-block;
// height: 64px;
vertical-align: middle;
// line-height: 64px;
.ivu-badge-dot{
top: 16px;
}
}
}

View File

@@ -0,0 +1,131 @@
<template>
<div class="user-avatar-dropdown">
<Dropdown @on-click="handleClick">
<label style="font-size: 14px;margin-left:6px;">{{loginInfo.actualName}}</label>
<Icon :size="6" class="dropdown-arrows" type="icon iconfont iconxiangxiala"></Icon>
<Avatar :src="loginInfo.header||defaultIcon" class="head" icon="ios-person" size="small" />
<DropdownMenu slot="list">
<DropdownItem name="updatePassword">修改密码</DropdownItem>
<DropdownItem name="logout">退出登录</DropdownItem>
</DropdownMenu>
</Dropdown>
<Modal
:closable="false"
:mask-closable="false"
@on-cancel="editModal=false"
@on-ok="editSure"
title="修改密码"
v-model="editModal"
>
<Form :label-width="100" :model="formValidate" :rules="ruleValidate" ref="formValidate">
<FormItem label="密码:" prop="oldPwd">
<Input placeholder="请输入原密码" type="password" v-model="formValidate.oldPwd" />
</FormItem>
<FormItem label="新密码:" prop="pwd">
<Input placeholder="请输入新密码" type="password" v-model="formValidate.pwd" />
</FormItem>
<FormItem label="确认密码:" prop="pwdAgain">
<Input placeholder="请再次输入新密码" type="password" v-model="formValidate.pwdAgain" />
</FormItem>
</Form>
</Modal>
</div>
</template>
<script>
import './user.less';
import { mapActions } from 'vuex';
import { employeeApi } from '@/api/employee';
import cookie from '@/lib/cookie';
import { loginApi } from '@/api/login';
export default {
name: 'User',
props: {
// 未读消息数
messageUnreadCount: {
type: Number,
default: 0
}
},
data() {
// 当前登录人信息
let loginInfo = this.$store.state.user.userLoginInfo;
return {
loginInfo: loginInfo,
defaultIcon: require('@/assets/images/default_icon.png'),
editModal: false,
formValidate: {},
ruleValidate: {
oldPwd: [
{
required: true,
message: '请输入密码',
trigger: 'blur'
}
],
pwd: [
{
required: true,
message: '请输入新密码',
trigger: 'blur'
}
],
pwdAgain: [
{
required: true,
message: '请输入确认密码',
trigger: 'blur'
}
]
}
};
},
methods: {
logout() {
this.$Spin.show();
let token = cookie.getToken();
localStorage.clear();
cookie.clearToken();
loginApi.logout(token);
location.reload();
},
updatePassword() {
this.editModal = true;
},
handleClick(name) {
switch (name) {
case 'logout':
this.logout();
break;
case 'updatePassword':
this.updatePassword();
break;
}
},
async savePassword() {
this.$Spin.show();
let result = await employeeApi.updatePwd(this.formValidate);
this.$Message.success('修改密码成功');
this.$refs['formValidate'].resetFields();
this.editModal = false;
this.logout();
},
editSure() {
this.$refs['formValidate'].validate(valid => {
if (valid) {
if (this.formValidate.passwordAgain !== this.formValidate.loginPwd) {
return this.$Message.error('两次输入密码不一致,请重新输入!!');
}
this.savePassword();
}
});
}
}
};
</script>
<style>
.arrow-icon {
margin: 0 5px;
}
</style>

View File

@@ -0,0 +1,2 @@
import Main from './main.vue';
export default Main;

View File

@@ -0,0 +1,129 @@
//滚动条样式
.scrollbar(@width: 2px){
&::-webkit-scrollbar { width: @width; height: 8px; }
&::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box-shadow: inset 0 0 5px rgb(185, 185, 185); background: #c7c5c8; }
&::-webkit-scrollbar-track { border-radius: 0; background: #ddd; }
}
.no-scrollbar{
&::-webkit-scrollbar {display:none}
}
.main{
.scrollbar,
.ivu-table-overflowX,
.content-wrapper,
.w-e-text{
.scrollbar;
}
iframe body{
.scrollbar;
}
.logo-con{
padding: 19px 0 15px 20px;
&.collapsed{
padding: 19px 0 15px;
text-align: center;
}
}
.menu-dropdown{
margin-left: 200px;
}
.search-bar{
padding:0 20px;
margin-bottom: 20px;
&.collapsed{
padding:0 12px;
i{
width: 40px;
}
}
input{
width: 100%;
height:36px;
background: #152a3a;
color:#44505c;
font-size: 14px;
outline: none;
&::placeholder{
color:#44505c;
}
&:focus{
border:none;
outline: none;
background: #fff;
}
border:none;
}
i{
line-height: 36px;
color:#44505c;
}
}
.header-con{
background: #fff;
padding: 0 10px;
width: 100%;
}
.main-layout-con{
height: 100%;
overflow: hidden;
}
.main-content-con{
height: ~"calc(100% - 60px)";
overflow: hidden;
}
.tag-nav-wrapper{
padding: 0;
height:50px;
// background:#F0F0F0;
}
.content-wrapper{
padding: 0 18px 18px;
height: ~"calc(100% - 80px)";
overflow: auto;
}
.left-sider{
.ivu-layout-sider-children{
overflow-y: scroll;
margin-right: -18px;
}
}
}
.ivu-menu-light.ivu-menu-vertical .ivu-menu-item-active:not(.ivu-menu-submenu):after{
right: inherit; left: 0;
}
.ivu-menu-item > i{
margin-right: 12px !important;
}
.ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i {
margin-right: 8px !important;
}
.collased-menu-dropdown{
width: 100%;
margin: 0;
line-height: normal;
padding: 7px 0 6px 16px;
clear: both;
font-size: 12px !important;
white-space: nowrap;
list-style: none;
cursor: pointer;
transition: background 0.2s ease-in-out;
&:hover{
background: rgba(100, 100, 100, 0.1);
}
& * {
color: #515a6e;
}
.ivu-menu-item > i{
margin-right: 12px !important;
}
.ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i {
margin-right: 8px !important;
}
}
.ivu-select-dropdown.ivu-dropdown-transfer{
max-height: 400px;
}

View File

@@ -0,0 +1,443 @@
<template>
<Layout class="main" style="height: 100%">
<Sider
:collapsed-width="60"
:style="{overflow: 'hidden'}"
:width="256"
class="left-sider"
collapsible
hide-trigger
v-model="collapsed"
>
<SideMenu
:active-name="$route.name"
:collapsed="collapsed"
:menu-list="menuList"
@on-select="turnToPage"
accordion
ref="sideMenu"
>
<!-- 需要放在菜单上面的内容如Logo写在side-menu标签内部如下 -->
<div :class="{'collapsed':collapsed}" class="logo-con">
<img :src="maxLogo" key="max-logo" v-show="!collapsed" />
<img :src="minLogo" key="min-logo" v-show="collapsed" />
</div>
<div :class="{'collapsed':collapsed}" class="search-bar">
<div class="search-box">
<Input placeholder="搜索" v-model="searchKeyWord">
<Icon @click="collapsed=false" slot="prefix" type="ios-search" />
</Input>
<div class="searchMenu" v-if="searchKeyWord">
<ul>
<li
:key="index"
@click="toRoute(item.name)"
v-for="(item, index) in searchListResult"
>{{item.title}}</li>
<li
@click="searchKeyWord = ''"
class="noData"
v-if="searchListResult.length == 0"
>未检索到数据</li>
</ul>
</div>
</div>
</div>
</SideMenu>
</Sider>
<Layout>
<Header class="header-con">
<HeaderBar :collapsed="collapsed" @on-coll-change="handleCollapsedChange">
<User :message-unread-count="unreadCount" />
<language
:lang="local"
@on-lang-change="setLocal"
style="margin-right: 10px;"
v-if="$config.useI18n"
/>
<Notice />
<Fullscreen style="margin-right: 10px;" v-model="isFullscreen" />
</HeaderBar>
</Header>
<Content class="main-content-con">
<Layout class="main-layout-con">
<div class="tag-nav-wrapper">
<TagsNav
:list="tagNavList"
:value="$route"
@input="handleClick"
@on-close="handleCloseTag"
/>
</div>
<Content class="content-wrapper">
<transition mode="out-in" name="fade-transform">
<keep-alive :include="keepAliveIncludes">
<router-view :key="key" />
</keep-alive>
</transition>
<ABackTop :bottom="80" :height="100" :right="50" container=".content-wrapper"></ABackTop>
</Content>
</Layout>
</Content>
</Layout>
</Layout>
</template>
<script>
import ABackTop from './components/a-back-top';
import SideMenu from './components/side-menu';
import HeaderBar from './components/header-bar';
import TagsNav from './components/tags-nav';
import Notice from './components/notice/notice';
import User from './components/user';
import Fullscreen from './components/fullscreen';
import Language from './components/language';
import { mapMutations, mapActions, mapGetters } from 'vuex';
import { getNewTagList, routeEqual, getShowMenu } from '@/lib/menu-func';
import { routers } from '@/router/routers';
import minLogo from '@/assets/images/logo-min.png';
import maxLogo from '@/assets/images/logo.png';
import { loginApi } from '@/api/login';
import './main.less';
export default {
name: 'Main',
components: {
SideMenu,
HeaderBar,
Notice,
Language,
TagsNav,
Fullscreen,
User,
ABackTop
},
data() {
return {
// 是否折叠
collapsed: false,
minLogo,
maxLogo,
// 是否全屏
isFullscreen: false,
// 缓存的路由:
includes: [],
searchKeyWord: '',
searchList: [],
searchListResult: [],
menuList: []
};
},
computed: {
...mapGetters(['errorCount', 'userMenuPrivilege']),
tagNavList() {
return this.$store.state.app.tagNavList;
},
tagRouter() {
return this.$store.state.app.tagRouter;
},
keepAliveIncludes() {
return this.$store.state.app.keepAliveIncludes;
},
cacheList() {
const list = [
'ParentView',
...(this.tagNavList.length
? this.tagNavList
.filter(item => !(item.meta && item.meta.noKeepAlive))
.map(item => item.name)
: [])
];
return list;
},
local() {
return this.$store.state.app.local;
},
hasReadErrorPage() {
return this.$store.state.app.hasReadErrorPage;
},
unreadCount() {
return this.$store.state.user.unreadCount;
},
key() {
return this.$route.name;
}
},
watch: {
searchKeyWord(val) {
if (val) {
this.searchListResult = this.searchList.filter(
item => item.title.indexOf(val) >= 0
);
}
},
$route(newRoute) {
const { name, query, params, meta } = newRoute;
this.addTag({
route: {
name,
query,
params,
meta
},
type: 'push'
});
this.setBreadCrumb(newRoute);
// this.pushKeepAliveIncludes(newRoute);
this.setTagNavList(getNewTagList(this.tagNavList, newRoute));
this.$refs.sideMenu.updateOpenName(newRoute.name);
// 如果param参数 存在 noKeepAlive切位true
let isParamNoKeepAlive = params && params.noKeepAlive === true;
// 如果query参数 存在 noKeepAlive切位true
let isQueryNoKeepAlive = query && query.noKeepAlive === true;
// 如果router meta 存在 noKeepAlive
let isMetaNoKeepAlive = meta && meta.noKeepAlive === true;
//如果存在noKeepAlive且已经缓存了需要去掉
if (isParamNoKeepAlive || isMetaNoKeepAlive || isQueryNoKeepAlive) {
// 去掉keep-alive
this.deleteKeepAliveIncludes(name);
return;
}
//默认缓存住所有
this.pushKeepAliveIncludes(name);
}
},
mounted() {
/**
* @description 初始化设置面包屑导航和标签导航
*/
this.setTagNavList();
this.setHomeRoute(routers);
this.setCollapsed();
this.setBreadCrumb(this.$route);
//初始化左侧菜单
this.initSideMenu();
const { name, params, query, meta } = this.$route;
this.addTag({
route: {
name,
params,
query,
meta
}
});
// 设置初始语言
this.setLocal(this.$i18n.locale);
// 如果当前打开页面不在标签栏中跳到homeName页
if (!this.tagNavList.find(item => item.name === this.$route.name)) {
this.$router.push({
name: this.$config.homeName
});
}
},
methods: {
...mapMutations([
'setBreadCrumb',
'setTagNavList',
'pushKeepAliveIncludes',
'clearKeepAliveIncludes',
'deleteKeepAliveIncludes',
'deleteOtherKeepAliveIncludes',
'addTag',
'setLocal',
'setHomeRoute',
'closeTag'
]),
...mapActions(['handleLogin']),
initSideMenu() {
//如果是登录跳转过来
if (this.$store.state.user.isUpdatePrivilege) {
this.$Spin.show();
this.buildMenuTree();
this.$refs.sideMenu.updateActiveName(this.$route.name);
this.$Spin.hide();
} else {
//如果页面刷新,需要重新获取权限
(async () => {
this.$Spin.show();
let sessionResult = await loginApi.getSession();
//设置权限
this.$store.commit(
'setUserPrivilege',
sessionResult.data.privilegeList
);
this.buildMenuTree();
//刷新以后手动更新左侧菜单打开和选中
this.$refs.sideMenu.updateActiveName(this.$route.name);
this.$Spin.hide();
})();
}
},
buildMenuTree() {
let privilegeTree = [];
for (const router of routers) {
//过滤非菜单
if (!router.meta.hideInMenu) {
//判断是否有权限
if (
this.$store.state.user.privilegeMenuKeyList.indexOf(router.name) !==
-1
) {
let menu = {
name: router.name,
meta: router.meta,
icon: _.isUndefined(router.meta.icon) ? '' : router.meta.icon,
children: []
};
privilegeTree.push(menu);
//存在孩子节点,开始递归
if (router.children && router.children.length > 0) {
this.recursion(router.children, menu);
}
}
}
}
this.menuList = privilegeTree;
},
recursion(children, parentMenu) {
for (const router of children) {
//过滤非菜单
if (!router.meta.hideInMenu) {
let menu = {
name: router.name,
meta: router.meta,
icon: _.isUndefined(router.meta.icon) ? '' : router.meta.icon,
children: []
};
this.searchList.push({
name: router.name,
title: router.meta.title
});
parentMenu.children.push(menu);
//存在孩子节点,开始递归
if (router.children && router.children.length > 0) {
this.recursion(router.children, menu);
}
}
}
},
// 自适应左侧导航宽度
setCollapsed() {
let setWidth = () => {
let width = document.body.offsetWidth;
if (width < 1340) {
this.collapsed = true;
} else {
this.collapsed = false;
}
};
setWidth();
window.onresize = () => setWidth();
},
turnToPage(route) {
let { name, params, query } = {};
if (typeof route === 'string') name = route;
else {
name = route.name;
params = route.params;
query = route.query;
}
if (name.indexOf('isTurnByHref_') > -1) {
window.open(name.split('_')[1]);
return;
}
this.$router.push({
name,
params,
query
});
},
handleCollapsedChange(state) {
this.collapsed = state;
},
handleCloseTag(res, type, route) {
let keepRouter = route ? route.name : null;
if (type !== 'others') {
if (type === 'all') {
this.turnToPage(this.$config.homeName);
this.clearKeepAliveIncludes(this.$config.homeName);
} else {
//如果是关闭单个tag则从keepAliveIncludes将关闭的那个移除掉
this.deleteKeepAliveIncludes(route.name);
if (routeEqual(this.$route, route)) {
this.closeTag(route);
}
}
} else {
// 如果是关闭其他others则只会保留本页面和home页面
this.deleteOtherKeepAliveIncludes(this.$route.name);
}
this.setTagNavList(res);
},
handleClick(item) {
this.turnToPage(item);
},
// 跳转路由
toRoute(name) {
this.$router.push({ name });
this.searchKeyWord = '';
}
}
};
</script>
<style lang="less" scoped>
/*fade*/
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.28s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
/*fade-transform*/
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all 0.5s;
}
.fade-transform-enter {
opacity: 0;
transform: translateX(-30px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px);
}
.search-box {
position: relative;
z-index: 999;
.searchMenu {
position: absolute;
z-index: 3;
width: 100%;
background: rgba(255, 255, 255, 0.95);
border-radius: 3px;
padding-left: 20px;
padding-top: 4px;
padding-bottom: 4px;
font-size: 12px;
left: 0;
margin-top: 1px;
ul,
li {
padding: 0;
margin: 0;
list-style: none;
}
li {
line-height: 28px;
cursor: pointer;
&:hover {
color: #3399ff;
}
&.noData {
color: #999;
}
}
}
}
</style>

View File

@@ -0,0 +1,34 @@
<template>
<div style="text-align:center">
<img src="http://smartadmin.1024lab.net/image/smart-admin-log-400@400.png"/>
<p style="text-align:left;font-size:16px;margin-top:-40px">
SmartAdmin由河南·洛阳 <a href="https://www.1024lab.net/" target="_blank">1024创新实验室</a>团队研发的一套互联网企业级的通用型中后台解决方案使用SpringBoot和Vue前后端分离
</p>
<br/>
<p style="text-align:left;font-size:16px">
我们开源一套漂亮的代码和一套整洁的代码规范让大家在这浮躁的代码世界里感受到一股把代码写好的清流同时又让开发者节省大量的时间减少加班快乐工作热爱生活
</p>
<br/>
<p style="text-align:left;font-size:20px">
<font color="#DC143C"><strong>你的star 是我们最强的动力感谢支持 (^_^) &nbsp;&nbsp;&nbsp;&nbsp; </strong></font>
<a target="_blank" href="https://github.com/1024-lab/smart-admin">
<img style="width: 82px;height: 20px;margin-right: 10px" alt="" src="https://img.shields.io/github/stars/1024-lab/smart-admin.svg?logo=github&style=social">
</a>
</p>
</div>
</template>
<script>
export default {
name: 'Ad',
props: {
},
computed: {
},
mounted () {
},
methods: {
}
};
</script>

View File

@@ -0,0 +1,2 @@
import Ad from './ad.vue';
export default Ad;

View File

@@ -0,0 +1,102 @@
<template>
<div class="tables-edit-outer">
<div v-if="!isEditting" class="tables-edit-con">
<span class="value-con">{{ value }}</span>
<Button
v-if="editable"
@click="startEdit"
class="tables-edit-btn"
style="padding: 2px 4px;"
type="text"
>
<Icon type="md-create"></Icon>
</Button>
</div>
<div v-else class="tables-editting-con">
<Input :value="value" @input="handleInput" class="tables-edit-input"/>
<Button @click="saveEdit" style="padding: 6px 4px;" type="text">
<Icon type="md-checkmark"></Icon>
</Button>
<Button @click="canceltEdit" style="padding: 6px 4px;" type="text">
<Icon type="md-close"></Icon>
</Button>
</div>
</div>
</template>
<script>
export default {
name: 'TablesEdit',
props: {
// 当前编辑的单元格值
value: {
type: [String, Number],
require: true
},
// 当前编辑的单元格id
edittingCellId: {
type: String,
require: true
},
// 表格数据
params: {
type: Object,
require: true
},
// 表格里全局设置的是否可编辑
editable: {
type: Boolean,
require: true
}
},
computed: {
// 判断是否处于编辑状态
isEditting () {
return this.edittingCellId === `editting-${this.params.index}-${this.params.column.key}`;
}
},
methods: {
handleInput (val) {
this.$emit('input', val);
},
startEdit () {
this.$emit('on-start-edit', this.params);
},
saveEdit () {
this.$emit('on-save-edit', this.params);
},
canceltEdit () {
this.$emit('on-cancel-edit', this.params);
}
}
};
</script>
<style lang="less">
.tables-edit-outer {
height: 100%;
.tables-edit-con {
position: relative;
height: 100%;
.value-con {
vertical-align: middle;
}
.tables-edit-btn {
position: absolute;
right: 10px;
top: 0;
display: none;
}
&:hover {
.tables-edit-btn {
display: inline-block;
}
}
}
.tables-editting-con {
.tables-edit-input {
width: ~"calc(100% - 60px)";
}
}
}
</style>

View File

@@ -0,0 +1,70 @@
// 验证
const validate = {
operation: (params) => {
if (params.operation === 0) {
return false;
}
return true;
},
audit: (params) => {
if (params.audit === 0) {
return false;
}
return true;
}
};
const btns = {
// 删除:需要判定
delete: (h, params, vm) => {
let disabledFlag = false;
return h('Tooltip', {
props: {
content: '删除',
placement: 'top',
transfer: true
}
}, [h('Button', {
props: {
type: 'error',
size: 'small',
icon: 'md-trash',
disabled: disabledFlag
},
style: {
marginRight: '5px'
},
on: {
click: () => {
vm.$emit('on-delete', params);
}
}
})]);
},
edit: (h, params, vm) => {
let disabledFlag = false;
return h('Tooltip', {
props: {
content: '编辑',
placement: 'top',
transfer: true
}
}, [h('Button', {
props: {
type: 'primary',
size: 'small',
icon: 'md-trash',
disabled: disabledFlag
},
style: {
marginRight: '5px'
},
on: {
click: () => {
vm.$emit('on-edit', params);
}
}
})]);
}
};
export default btns;

View File

@@ -0,0 +1,2 @@
import Tables from './tables.vue';
export default Tables;

View File

@@ -0,0 +1,17 @@
.search-con{
padding: 10px 0;
.search{
&-col{
display: inline-block;
width: 200px;
}
&-input{
display: inline-block;
width: 200px;
margin-left: 2px;
}
&-btn{
margin-left: 2px;
}
}
}

View File

@@ -0,0 +1,342 @@
<template>
<div>
<Table
ref="tablesMain"
:data="insideTableData"
:columns="insideColumns"
:stripe="stripe"
:border="border"
:show-header="showHeader"
:width="width"
:height="height"
:loading="loading"
:disabled-hover="disabledHover"
:highlight-row="highlightRow"
:row-class-name="rowClassName"
:size="size"
:no-data-text="noDataText"
:no-filtered-data-text="noFilteredDataText"
@on-current-change="onCurrentChange"
@on-select="onSelect"
@on-select-cancel="onSelectCancel"
@on-select-all="onSelectAll"
@on-selection-change="onSelectionChange"
@on-sort-change="onSortChange"
@on-filter-change="onFilterChange"
@on-row-click="onRowClick"
@on-row-dblclick="onRowDblclick"
@on-expand="onExpand"
>
<slot name="header" slot="header"></slot>
<slot name="footer" slot="footer"></slot>
<slot name="loading" slot="loading"></slot>
</Table>
<Page v-if="pageShow"
:total="total"
:current="pageNumber"
:page-size="pageSize"
:show-elevator="showElevator"
:show-sizer="showSizer"
:show-total="showTotal"
:page-size-opts='pageOpts'
style="margin:24px 0;text-align:right;"
@on-change="pageChange" @on-page-size-change="pageSizeChange"></Page>
</div>
</template>
<script>
import TablesEdit from './edit.vue';
import handleBtns from './handle-btns';
import './index.less';
export default {
name: 'Tables',
props: {
// table 表格数据 集合类型
value: {
type: Array,
default () {
return [];
}
},
// 是否显示页码
pageShow: {
type: Boolean,
default: false
},
// 表头
columns: {
type: Array,
default () {
return [];
}
},
// 每页数量
pageSize: {
type: Number,
default: 10
},
// 显示电梯,可以快速切换到某一页
showElevator: {
type: Boolean,
default: true
},
// table页码
pageNumber: {
type: Number,
default: 1
},
// 显示分页用来改变page-size
showSizer: {
type: Boolean,
default: true
},
// 显示总数
showTotal: {
type: Boolean,
default: true
},
// 总数
total: {
type: Number,
default: 0
},
// 表格尺寸,可选值为 large、small、default 或者不填
size: {
type: String,
default: null
},
// 表格宽度,单位 px
width: {
type: [Number, String]
},
// 表格高度,单位 px设置后如果表格内容大于此值会固定表头
height: {
type: [Number, String]
},
// 是否显示间隔斑马纹
stripe: {
type: Boolean,
default: false
},
// 是否显示纵向边框
border: {
type: Boolean,
default: false
},
// 是否显示表头
showHeader: {
type: Boolean,
default: true
},
// 是否支持高亮选中的行,即单选
highlightRow: {
type: Boolean,
default: false
},
// 行的 className 的回调方法传入参数row当前行数据 ,index当前行的索引
rowClassName: {
type: Function,
default () {
return '';
}
},
// 数据为空时显示的提示内容
noDataText: {
type: String,
default: ''
},
// 筛选数据为空时显示的提示内容
noFilteredDataText: {
type: String,
default: ''
},
// 禁用鼠标悬停时的高亮
disabledHover: {
type: Boolean
},
// 表格是否加载中
loading: {
type: Boolean,
default: false
},
/**
* @description 全局设置是否可编辑
*/
editable: {
type: Boolean,
default: false
},
/**
* @description 是否可搜索
*/
searchable: {
type: Boolean,
default: false
},
/**
* @description 搜索控件所在位置,'top' / 'bottom'
*/
searchPlace: {
type: String,
default: 'top'
}
},
/**
* Events
* @on-start-edit 返回值 {Object} 同iview中render函数中的params对象 { row, index, column }
* @on-cancel-edit 返回值 {Object} 同上
* @on-save-edit 返回值 {Object} 除上面三个参数外还有一个value: 修改后的数据
*/
data () {
return {
// 分页时每页数量选择
pageOpts: [10, 20, 30, 50, 100],
// 表格 每列的配置 如名称,宽度
insideColumns: [],
// 表格数据
insideTableData: [],
// 正在编辑的单元格id
edittingCellId: '',
// 编辑内容
edittingText: '',
// 搜索内容
searchValue: '',
// 搜索关键词
searchKey: ''
};
},
watch: {
columns (columns) {
this.handleColumns(columns);
this.setDefaultSearchKey();
},
value (val) {
this.handleTableData();
if (this.searchable) this.handleSearch();
}
},
mounted () {
this.handleColumns(this.columns);
this.setDefaultSearchKey();
this.handleTableData();
},
methods: {
// 页码改变
pageChange (e) {
this.$emit('on-change', e);
},
// 每页条数改变
pageSizeChange (e) {
this.$emit('on-page-size-change', e);
},
suportEdit (item, index) {
item.render = (h, params) => {
return h(TablesEdit, {
props: {
params: params,
value: this.insideTableData[params.index][params.column.key],
edittingCellId: this.edittingCellId,
editable: this.editable
},
on: {
'input': val => {
this.edittingText = val;
},
'on-start-edit': (params) => {
this.edittingCellId = `editting-${params.index}-${params.column.key}`;
this.$emit('on-start-edit', params);
},
'on-cancel-edit': (params) => {
this.edittingCellId = '';
this.$emit('on-cancel-edit', params);
},
'on-save-edit': (params) => {
this.value[params.row.initRowIndex][params.column.key] = this.edittingText;
this.$emit('input', this.value);
this.$emit('on-save-edit', Object.assign(params, { value: this.edittingText }));
this.edittingCellId = '';
}
}
});
};
return item;
},
surportHandle (item) {
let options = item.options || [];
let insideBtns = [];
options.forEach(item => {
if (handleBtns[item]) insideBtns.push(handleBtns[item]);
});
let btns = item.button ? [].concat(insideBtns, item.button) : insideBtns;
item.render = (h, params) => {
params.tableData = this.value;
return h('div', btns.map(item => item(h, params, this)));
};
return item;
},
// 设置表格列头
handleColumns (columns) {
this.insideColumns = columns.map((item, index) => {
let res = item;
if (res.editable) { res = this.suportEdit(res, index); }
if (res.key === 'handle') { res = this.surportHandle(res); }
return res;
});
},
// 设置默认搜索关键词
setDefaultSearchKey () {
this.searchKey = this.columns[0].key !== 'handle' ? this.columns[0].key : (this.columns.length > 1 ? this.columns[1].key : '');
},
handleClear (e) {
if (e.target.value === '') this.insideTableData = this.value;
},
handleSearch () {
this.insideTableData = this.value.filter(item => item[this.searchKey].indexOf(this.searchValue) > -1);
},
handleTableData () {
this.insideTableData = this.value.map((item, index) => {
let res = item;
res.initRowIndex = index;
return res;
});
},
exportCsv (params) {
this.$refs.tablesMain.exportCsv(params);
},
clearCurrentRow () {
this.$refs.talbesMain.clearCurrentRow();
},
onCurrentChange (currentRow, oldCurrentRow) {
this.$emit('on-current-change', currentRow, oldCurrentRow);
},
onSelect (selection, row) {
this.$emit('on-select', selection, row);
},
onSelectCancel (selection, row) {
this.$emit('on-select-cancel', selection, row);
},
onSelectAll (selection) {
this.$emit('on-select-all', selection);
},
onSelectionChange (selection) {
this.$emit('on-selection-change', selection);
},
onSortChange (column, key, order) {
this.$emit('on-sort-change', column, key, order);
},
onFilterChange (row) {
this.$emit('on-filter-change', row);
},
onRowClick (row, index) {
this.$emit('on-row-click', row, index);
},
onRowDblclick (row, index) {
this.$emit('on-row-dblclick', row, index);
},
onExpand (row, status) {
this.$emit('on-expand', row, status);
}
}
};
</script>