src: codebase wide cleanup and refactorings

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Rohit Yadav 2019-10-16 12:52:04 +05:30
parent 7dad796113
commit 8b196ed56c
53 changed files with 42 additions and 2229 deletions

View File

@ -1,102 +0,0 @@
<template>
<span>
{{ lastTime | format }}
</span>
</template>
<script>
function fixedZero (val) {
return val * 1 < 10 ? `0${val}` : val
}
export default {
name: 'CountDown',
props: {
format: {
type: Function,
default: undefined
},
target: {
type: [Date, Number],
required: true
},
onEnd: {
type: Function,
default: () => ({})
}
},
data () {
return {
dateTime: '0',
originTargetTime: 0,
lastTime: 0,
timer: 0,
interval: 1000
}
},
filters: {
format (time) {
const hours = 60 * 60 * 1000
const minutes = 60 * 1000
const h = Math.floor(time / hours)
const m = Math.floor((time - h * hours) / minutes)
const s = Math.floor((time - h * hours - m * minutes) / 1000)
return `${fixedZero(h)}:${fixedZero(m)}:${fixedZero(s)}`
}
},
created () {
this.initTime()
this.tick()
},
methods: {
initTime () {
let lastTime = 0
let targetTime = 0
this.originTargetTime = this.target
try {
if (Object.prototype.toString.call(this.target) === '[object Date]') {
targetTime = this.target
} else {
targetTime = new Date(this.target).getTime()
}
} catch (e) {
throw new Error('invalid target prop')
}
lastTime = targetTime - new Date().getTime()
this.lastTime = lastTime < 0 ? 0 : lastTime
},
tick () {
const { onEnd } = this
this.timer = setTimeout(() => {
if (this.lastTime < this.interval) {
clearTimeout(this.timer)
this.lastTime = 0
if (typeof onEnd === 'function') {
onEnd()
}
} else {
this.lastTime -= this.interval
this.tick()
}
}, this.interval)
}
},
beforeUpdate () {
if (this.originTargetTime !== this.target) {
this.initTime()
}
},
beforeDestroy () {
clearTimeout(this.timer)
}
}
</script>
<style scoped>
</style>

View File

@ -1,3 +0,0 @@
import CountDown from './CountDown'
export default CountDown

View File

@ -1,64 +0,0 @@
<script>
import Tooltip from 'ant-design-vue/es/tooltip'
import { cutStrByFullLength, getStrFullLength } from '@/components/_util/util'
/*
const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;
const TooltipOverlayStyle = {
overflowWrap: 'break-word',
wordWrap: 'break-word',
};
*/
export default {
name: 'Ellipsis',
components: {
Tooltip
},
props: {
prefixCls: {
type: String,
default: 'ant-pro-ellipsis'
},
tooltip: {
type: Boolean
},
length: {
type: Number,
required: true
},
lines: {
type: Number,
default: 1
},
fullWidthRecognition: {
type: Boolean,
default: false
}
},
methods: {
getStrDom (str, fullLength) {
return (
<span>{ cutStrByFullLength(str, this.length) + (fullLength > this.length ? '...' : '') }</span>
)
},
getTooltip (fullStr, fullLength) {
return (
<Tooltip>
<template slot="title">{ fullStr }</template>
{ this.getStrDom(fullStr, fullLength) }
</Tooltip>
)
}
},
render () {
const { tooltip, length } = this.$props
const str = this.$slots.default.map(vNode => vNode.text).join('')
const fullLength = getStrFullLength(str)
const strDom = tooltip && fullLength > length ? this.getTooltip(str, fullLength) : this.getStrDom(str, fullLength)
return (
strDom
)
}
}
</script>

View File

@ -1,3 +0,0 @@
import Ellipsis from './Ellipsis'
export default Ellipsis

View File

@ -1,57 +0,0 @@
<template>
<div class="footer-toolbar">
<div class="footer-toolbar-left">
<slot name="extra">{{ extra }}</slot>
</div>
<div class="footer-toolbar-right">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'FooterToolBar',
props: {
prefixCls: {
type: String,
default: 'reduced'
},
extra: {
type: [String, Object],
default: ''
}
}
}
</script>
<style lang="less" scoped>
.footer-toolbar {
position: fixed;
width: 100%;
bottom: 0;
right: 0;
height: 56px;
line-height: 56px;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03);
background: #fff;
border-top: 1px solid #e8e8e8;
padding: 0 24px;
z-index: 9;
&:after {
content: "";
display: block;
clear: both;
}
&-left {
float: left;
}
&-right {
float: right;
}
}
</style>

View File

@ -1,4 +0,0 @@
import FooterToolBar from './FooterToolBar'
import './index.less'
export default FooterToolBar

View File

@ -1,23 +0,0 @@
@import "../../style/variables/prefixes";
@footer-toolbar-prefix-cls: ~"@{ant-pro-prefix}-footer-toolbar";
.@{footer-toolbar-prefix-cls} {
position: fixed;
width: 100%;
bottom: 0;
right: 0;
height: 56px;
line-height: 56px;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03);
background: #fff;
border-top: 1px solid #e8e8e8;
padding: 0 24px;
z-index: 9;
&:after {
content: "";
display: block;
clear: both;
}
}

View File

@ -1,106 +0,0 @@
<template>
<div :class="[prefixCls]">
<slot name="subtitle">
<div :class="[`${prefixCls}-subtitle`]">{{ typeof subTitle === 'string' ? subTitle : subTitle() }}</div>
</slot>
<div class="number-info-value">
<span>{{ total }}</span>
<span class="sub-total">
{{ subTotal }}
<icon :type="`caret-${status}`" />
</span>
</div>
</div>
</template>
<script>
import Icon from 'ant-design-vue/es/icon'
export default {
name: 'NumberInfo',
props: {
prefixCls: {
type: String,
default: 'number-info'
},
total: {
type: Number,
required: true
},
subTotal: {
type: Number,
required: true
},
subTitle: {
type: [String, Function],
default: ''
},
status: {
type: String,
default: 'up'
}
},
components: {
Icon
},
data () {
return {}
}
}
</script>
<style lang="less" scoped>
@import '~ant-design-vue/lib/style/themes/default';
.number-info {
.number-info-subtitle {
color: @text-color-secondary;
font-size: @font-size-base;
height: 22px;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
}
.number-info-value {
margin-top: 4px;
font-size: 0;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
& > span {
color: @heading-color;
display: inline-block;
line-height: 32px;
height: 32px;
font-size: 24px;
margin-right: 32px;
}
.sub-total {
color: @text-color-secondary;
font-size: @font-size-lg;
vertical-align: top;
margin-right: 0;
i {
font-size: 12px;
transform: scale(0.82);
margin-left: 4px;
}
:global {
.anticon-caret-up {
color: @red-6;
}
.anticon-caret-down {
color: @green-6;
}
}
}
}
}
</style>

View File

@ -1,3 +0,0 @@
import NumberInfo from './NumberInfo'
export default NumberInfo

View File

@ -1,56 +0,0 @@
@import '~ant-design-vue/lib/style/themes/default';
@import '../../style/variables/prefixes';
@numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info";
.@{numberInfo-prefix-cls} {
.ant-pro-number-info-subtitle {
color: @text-color-secondary;
font-size: @font-size-base;
height: 22px;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
}
.number-info-value {
margin-top: 4px;
font-size: 0;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
& > span {
color: @heading-color;
display: inline-block;
line-height: 32px;
height: 32px;
font-size: 24px;
margin-right: 32px;
}
.sub-total {
color: @text-color-secondary;
font-size: @font-size-lg;
vertical-align: top;
margin-right: 0;
i {
font-size: 12px;
transform: scale(0.82);
margin-left: 4px;
}
:global {
.anticon-caret-up {
color: @red-6;
}
.anticon-caret-down {
color: @green-6;
}
}
}
}
}

View File

@ -1,90 +0,0 @@
<template>
<div class="result">
<div>
<a-icon :class="{ 'icon': true, 'success': isSuccess, 'error': !isSuccess }" :type="isSuccess ? 'check-circle' : 'close-circle'"/>
</div>
<div class="title" v-if="title">{{ title }}</div>
<div class="description" v-if="description">{{ description }}</div>
<div class="content" v-if="content">
<slot></slot>
</div>
<div class="action">
<slot name="action"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Result',
props: {
isSuccess: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
},
description: {
type: String,
default: ''
},
content: {
type: Boolean,
default: true
}
}
}
</script>
<style lang="less" scoped>
.result {
text-align: center;
width: 72%;
margin: 0 auto;
padding: 24px 0 8px;
.icon {
font-size: 72px;
line-height: 72px;
margin-bottom: 24px;
}
.success {
color: #52c41a;
}
.error {
color: red;
}
.title {
font-size: 24px;
color: rgba(0, 0, 0, .85);
font-weight: 500;
line-height: 32px;
margin-bottom: 16px;
}
.description {
font-size: 14px;
line-height: 22px;
color: rgba(0, 0, 0, 0.45);
margin-bottom: 24px;
}
.content {
background: #fafafa;
padding: 24px 40px;
border-radius: 2px;
text-align: left;
}
.action {
margin-top: 32px;
}
}
.mobile {
.result {
width: 100%;
margin: 0 auto;
padding: unset;
}
}
</style>

View File

@ -1,2 +0,0 @@
import Result from './Result.vue'
export default Result

View File

@ -1,124 +0,0 @@
import { Menu, Icon, Input } from 'ant-design-vue'
const { Item, ItemGroup, SubMenu } = Menu
const { Search } = Input
export default {
name: 'Tree',
props: {
dataSource: {
type: Array,
required: true
},
openKeys: {
type: Array,
default: () => []
},
search: {
type: Boolean,
default: false
}
},
created () {
this.localOpenKeys = this.openKeys.slice(0)
},
data () {
return {
localOpenKeys: []
}
},
methods: {
handlePlus (item) {
this.$emit('add', item)
},
handleTitleClick (...args) {
this.$emit('titleClick', { args })
},
renderSearch () {
return (
<Search
placeholder="input search text"
style="width: 100%; margin-bottom: 1rem"
/>
)
},
renderIcon (icon) {
return icon && (<Icon type={icon} />) || null
},
renderMenuItem (item) {
return (
<Item key={item.key}>
{ this.renderIcon(item.icon) }
{ item.title }
<a class="btn" style="width: 20px;z-index:1300" {...{ on: { click: () => this.handlePlus(item) } }}><a-icon type="plus"/></a>
</Item>
)
},
renderItem (item) {
return item.children ? this.renderSubItem(item, item.key) : this.renderMenuItem(item, item.key)
},
renderItemGroup (item) {
const childrenItems = item.children.map(o => {
return this.renderItem(o, o.key)
})
return (
<ItemGroup key={item.key}>
<template slot="title">
<span>{ item.title }</span>
<a-dropdown>
<a class="btn"><a-icon type="ellipsis" /></a>
<a-menu slot="overlay">
<a-menu-item key="1">新增</a-menu-item>
<a-menu-item key="2">合并</a-menu-item>
<a-menu-item key="3">移除</a-menu-item>
</a-menu>
</a-dropdown>
</template>
{ childrenItems }
</ItemGroup>
)
},
renderSubItem (item, key) {
const childrenItems = item.children && item.children.map(o => {
return this.renderItem(o, o.key)
})
const title = (
<span slot="title">
{ this.renderIcon(item.icon) }
<span>{ item.title }</span>
</span>
)
if (item.group) {
return this.renderItemGroup(item)
}
// titleClick={this.handleTitleClick(item)}
return (
<SubMenu key={key}>
{ title }
{ childrenItems }
</SubMenu>
)
}
},
render () {
const { dataSource, search } = this.$props
// this.localOpenKeys = openKeys.slice(0)
const list = dataSource.map(item => {
return this.renderItem(item)
})
return (
<div class="tree-wrapper">
{ search ? this.renderSearch() : null }
<Menu mode="inline" class="custom-tree" {...{ on: { click: item => this.$emit('click', item), 'update:openKeys': val => { this.localOpenKeys = val } } }} openKeys={this.localOpenKeys}>
{ list }
</Menu>
</div>
)
}
}

View File

@ -1,73 +0,0 @@
<template>
<div class="trend" :class="[ reverseColor && 'reverse-color' ]">
<span>
<slot name="term"></slot>
<span class="item-text">
<slot></slot>
</span>
</span>
<span :class="[flag]"><a-icon :type="`caret-${flag}`"/></span>
</div>
</template>
<script>
export default {
name: 'Trend',
props: {
prefixCls: {
type: String,
default: 'ant-pro-trend'
},
flag: {
type: String,
required: true
},
reverseColor: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="less" scoped>
@import "~ant-design-vue/lib/style/themes/default";
.trend {
display: inline-block;
font-size: @font-size-base;
line-height: 22px;
.up,
.down {
margin-left: 4px;
position: relative;
top: 1px;
i {
font-size: 12px;
transform: scale(0.83);
}
}
.item-text {
display: inline-block;
margin-left: 8px;
color: rgba(0,0,0,.85);
}
.up {
color: @red-6;
}
.down {
color: @green-6;
top: -1px;
}
&.reverse-color .up {
color: @green-6;
}
&.reverse-color .down {
color: @red-6;
}
}
</style>

View File

@ -1,3 +0,0 @@
import Trend from './Trend.vue'
export default Trend

View File

@ -1,43 +0,0 @@
@import '~ant-design-vue/lib/style/themes/default';
@import '../../style/variables/prefixes';
@trend-prefix-cls: ~"@{ant-pro-prefix}-trend";
.@{trend-prefix-cls} {
display: inline-block;
font-size: @font-size-base;
line-height: 22px;
.up,
.down {
margin-left: 4px;
position: relative;
top: 1px;
i {
font-size: 12px;
transform: scale(0.83);
}
}
.item-text {
display: inline-block;
margin-left: 8px;
color: rgba(0,0,0,.85);
}
.up {
color: @red-6;
}
.down {
color: @green-6;
top: -1px;
}
&.reverse-color .up {
color: @green-6;
}
&.reverse-color .down {
color: @red-6;
}
}

View File

@ -1,32 +0,0 @@
/**
* components util
*/
export function filterEmpty (children = []) {
return children.filter(c => c.tag || (c.text && c.text.trim() !== ''))
}
export const getStrFullLength = (str = '') =>
str.split('').reduce((pre, cur) => {
const charCode = cur.charCodeAt(0)
if (charCode >= 0 && charCode <= 128) {
return pre + 1
}
return pre + 2
}, 0)
export const cutStrByFullLength = (str = '', maxLength) => {
let showLength = 0
return str.split('').reduce((pre, cur) => {
const charCode = cur.charCodeAt(0)
if (charCode >= 0 && charCode <= 128) {
showLength += 1
} else {
showLength += 2
}
if (showLength <= maxLength) {
return pre + cur
}
return pre
}, '')
}

View File

@ -1,73 +0,0 @@
<template>
<div class="bar-wrapper">
<h4 class="bar-headline">{{ title }}</h4>
<v-chart
class="bar-chart"
:data="data"
:forceFit="true">
<v-tooltip />
<v-axis />
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
const data = []
for (let i = 0; i < 12; i += 1) {
data.push({
x: `${i + 1}X`,
y: Math.floor(Math.random() * 1000) + 200
})
}
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: 'Data',
min: 1,
max: 22
}]
export default {
name: 'Bar',
props: {
title: {
type: String,
default: ''
}
},
data () {
return {
data,
scale,
tooltip
}
}
}
</script>
<style lang="less" scoped>
.bar {
&-wrapper {
padding: 0 0 32px 32px;
}
&-headline {
margin-bottom: 20px;
}
&-chart {
height: 254px;
padding: 0 0 40px 50px;
}
}
</style>

View File

@ -1,76 +0,0 @@
<template>
<div>
<v-chart
class="liquid-chart"
:forceFit="true"
:height="height"
:width="width"
:data="data"
:scale="scale">
<v-tooltip />
<v-interval
:shape="['liquid-fill-gauge']"
position="transfer*value"
color=""
class="liquid-interval"
:v-style="{
lineWidth: 10
}"
:tooltip="[
'transfer*value',
(transfer, value) => {
return {
name: transfer,
value,
};
},
]"
></v-interval>
<v-guide
class="liquid-guide"
v-for="(row, index) in data"
:key="index"
type="text"
:top="true"
:position="{
gender: row.transfer,
value: 45
}"
:content="row.value + '%'"
/>
</v-chart>
</div>
</template>
<script>
export default {
name: 'Liquid',
props: {
height: {
type: Number,
default: 0
},
width: {
type: Number,
default: 0
}
}
}
</script>
<style lang="less" scoped>
.liquid {
&-graph {
padding: 0;
}
&-interval {
opacity: 0.75;
}
&-guide {
text-align: 'center';
opacity: 0.75;
}
}
</style>

View File

@ -1,73 +0,0 @@
<template>
<div class="mini-area-wrapper">
<div class="mini-area-wrapper2">
<v-chart class="mini-area-chart" :force-fit="true" :data="data">
<v-tooltip />
<v-smooth-area position="x*y" />
</v-chart>
</div>
</div>
</template>
<script>
import moment from 'moment'
const data = []
const beginDay = new Date().getTime()
for (let i = 0; i < 10; i++) {
data.push({
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
y: Math.round(Math.random() * 10)
})
}
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 22
}]
export default {
name: 'MiniArea',
data () {
return {
data,
tooltip,
scale
}
}
}
</script>
<style lang="less" scoped>
.mini-area {
&-wrapper {
position: relative;
width: 100%;
}
&-wrapper2 {
position: absolute;
bottom: -28px;
width: 100%;
height: 46px;
}
&-chart {
padding: 36px 0 18px 0;
height: 100px;
}
}
</style>

View File

@ -1,73 +0,0 @@
<template>
<div class="mini-bar-wrapper">
<div class="mini-bar-wrapper2">
<v-chart class="mini-bar-chart" :force-fit="true" :data="data">
<v-tooltip />
<v-bar position="x*y" />
</v-chart>
</div>
</div>
</template>
<script>
import moment from 'moment'
const data = []
const beginDay = new Date().getTime()
for (let i = 0; i < 10; i++) {
data.push({
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
y: Math.round(Math.random() * 10)
})
}
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 22
}]
export default {
name: 'MiniArea',
data () {
return {
data,
tooltip,
scale
}
}
}
</script>
<style lang="less" scoped>
.mini-bar {
&-wrapper {
position: relative;
width: 100%;
}
&-wrapper2 {
position: absolute;
bottom: -28px;
width: 100%;
height: 46px;
}
&-chart {
padding: 36px 0 18px 0;
height: 100px;
}
}
</style>

View File

@ -1,93 +0,0 @@
<template>
<div class="mini-progress-wrapper">
<div class="mini-progress-target target" :style="{ left: target + '%'}">
<span class="mini-progress-background"/>
<span class="mini-progress-background2"/>
</div>
<div class="mini-progress-wrapper-inner">
<div class="mini-progress-progress"></div>
</div>
</div>
</template>
<script>
export default {
name: 'MiniProgress',
props: {
target: {
type: Number,
default: 0
},
height: {
type: String,
default: '10px'
},
color: {
type: String,
default: '#13C2C2'
},
percentage: {
type: Number,
default: 0
}
}
}
</script>
<style lang="less" scoped>
@import "~ant-design-vue/lib/style/themes/default";
.mini-progress {
&-wrapper {
padding: 5px 0;
position: relative;
width: 100%;
}
&-wrapper-inner {
background-color: #f5f5f5;
position: relative;
}
&-target {
left: 0;
position: absolute;
top: 0;
bottom: 0;
}
&-background {
background-color: @cyan-6;
border-radius: 100px;
position: absolute;
top: 0;
left: 0;
height: 4px;
width: 2px;
}
&-background2 {
background-color: @cyan-6;
border-radius: 100px;
position: absolute;
top: 0;
left: 0;
height: 4px;
width: 2px;
top: auto;
bottom: 0;
}
&-progress {
background-color: @cyan-6;
width: 0%;
height: 0;
transition: all .4s cubic-bezier(.08, .82, .17, 1) 0s;
border-radius: 1px 0 0 1px;
background-color: #1890ff;
width: 0;
height: 100%;
}
}
</style>

View File

@ -1,73 +0,0 @@
<template>
<v-chart class="radar-chart" :forceFit="true" :data="data" :scale="scale">
<v-tooltip></v-tooltip>
<v-axis :dataKey="axis1Opts.dataKey" :line="axis1Opts.line" :tickLine="axis1Opts.tickLine" :grid="axis1Opts.grid" />
<v-axis :dataKey="axis2Opts.dataKey" :line="axis2Opts.line" :tickLine="axis2Opts.tickLine" :grid="axis2Opts.grid" />
<v-legend dataKey="user" marker="circle" :offset="30" />
<v-coord type="polar" radius="0.8" />
<v-line position="item*score" color="user" :size="2" />
<v-point position="item*score" color="user" :size="4" shape="circle" />
</v-chart>
</template>
<script>
const axis1Opts = {
dataKey: 'item',
line: null,
tickLine: null,
grid: {
lineStyle: {
lineDash: null
},
hideFirstLine: false
}
}
const axis2Opts = {
dataKey: 'score',
line: null,
tickLine: null,
grid: {
type: 'polygon',
lineStyle: {
lineDash: null
}
}
}
const scale = [
{
dataKey: 'score',
min: 0,
max: 80
}, {
dataKey: 'user',
alias: 'Alias'
}
]
export default {
name: 'Radar',
props: {
data: {
type: Array,
default: null
}
},
data () {
return {
axis1Opts,
axis2Opts,
scale
}
}
}
</script>
<style lang="less" scoped>
.radar {
&-chart {
height: 400px;
padding: 20 20 95 20;
}
}
</style>

View File

@ -1,74 +0,0 @@
<template>
<div class="rank-list-wrapper">
<h4>{{ title }}</h4>
<ul class="rank-list-list">
<li class="rank-list-item" :key="index" v-for="(item, index) in list">
<span class="rank-list-index" :class="index < 3 ? 'is-active' : null">{{ index + 1 }}</span>
<span class="rank-list-name">{{ item.name }}</span>
<span class="rank-list-total">{{ item.total }}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'RankList',
// ['title', 'list']
props: {
title: {
type: String,
default: ''
},
list: {
type: Array,
default: null
}
}
}
</script>
<style lang="less" scoped>
.rank-list {
margin: 25px 0 0;
padding: 0;
list-style: none;
&-wrapper {
padding: 0 32px 32px 72px;
.mobile & {
padding-left: 32px;
}
}
&-item {
margin-top: 16px;
color: rgba(0, 0, 0, .65);
font-size: 14px;
line-height: 22px;
}
&-index {
background-color: #f5f5f5;
border-radius: 20px;
display: inline-block;
font-size: 12px;
font-weight: 600;
margin-right: 24px;
height: 20px;
line-height: 20px;
width: 20px;
text-align: center;
&.is-active {
background-color: #314659;
color: #fff;
}
}
&-total {
float: right;
}
}
</style>

View File

@ -1,81 +0,0 @@
<template>
<div class="transfer-bar-wrapper">
<h4 class="transfer-bar-headline">{{ title }}</h4>
<v-chart
class="transfer-bar-chart"
:data="data"
:scale="scale"
:forceFit="true"
>
<v-tooltip />
<v-axis />
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
title: '日期(天)',
alias: '日期(天)',
min: 2
}, {
dataKey: 'y',
title: 'Title',
alias: 'Alias',
min: 1
}]
export default {
name: 'Bar',
props: {
title: {
type: String,
default: ''
}
},
data () {
return {
data: [],
scale,
tooltip
}
},
created () {
this.getMonthBar()
},
methods: {
getMonthBar () {
this.$http.get('/analysis/month-bar')
.then(res => {
this.data = res.result
})
}
}
}
</script>
<style lang="less" scoped>
.c-transfer-bar {
&-wrapper {
padding: 0 0 32px 32px;
}
&-headline {
margin-bottom: 20px;
}
&-chart {
height: 254px;
padding: 0 0 40px 50px;
}
}
</style>

View File

@ -1,86 +0,0 @@
<template class="trend">
<div class="trend-wrapper chart-trend">
{{ term }}
<span class="trend-rate">{{ rate }}%</span>
<span class="trend-icon " :class="[trend]"><a-icon :type="'caret-' + trend"/></span>
</div>
</template>
<script>
export default {
name: 'Trend',
props: {
term: {
type: String,
default: '',
required: true
},
percentage: {
type: Number,
default: null
},
type: {
type: Boolean,
default: null
},
target: {
type: Number,
default: 0
},
value: {
type: Number,
default: 0
},
fixed: {
type: Number,
default: 2
}
},
data () {
return {
trend: this.type && 'up' || 'down',
rate: this.percentage
}
},
created () {
const type = this.type === null ? this.value >= this.target : this.type
this.trend = type ? 'up' : 'down'
this.rate = (this.percentage === null ? Math.abs(this.value - this.target) * 100 / this.target : this.percentage).toFixed(this.fixed)
}
}
</script>
<style lang="less" scoped>
.trend {
&-wrapper {
display: inline-block;
font-size: 14px;
line-height: 22px;
}
&-icon {
font-size: 12px;
&.up,
&.down {
margin-left: 4px;
position: relative;
i {
font-size: 12px;
transform: scale(.83);
}
}
&.up {
color: #f5222d;
top: 1px;
}
&.down {
color: #52c41a;
top: -1px;
}
}
}
</style>

View File

@ -1,10 +0,0 @@
.antv-chart-mini {
position: relative;
width: 100%;
.chart-wrapper {
position: absolute;
bottom: -28px;
width: 100%;
}
}

View File

@ -6,7 +6,7 @@
</template> </template>
<script> <script>
import ProjectMenu from '../tools/ProjectMenu' import ProjectMenu from './ProjectMenu'
export default { export default {
name: 'Logo', name: 'Logo',

View File

@ -1,46 +0,0 @@
// chart
import Bar from '@/components/chart/Bar'
import ChartCard from '@/components/chart/ChartCard'
import Liquid from '@/components/chart/Liquid'
import MiniArea from '@/components/chart/MiniArea'
import MiniBar from '@/components/chart/MiniBar'
import MiniProgress from '@/components/chart/MiniProgress'
import Radar from '@/components/chart/Radar'
import RankList from '@/components/chart/RankList'
import TransferBar from '@/components/chart/TransferBar'
// pro components
import AvatarList from '@/components/AvatarList'
import CountDown from '@/components/CountDown'
import Ellipsis from '@/components/Ellipsis'
import FooterToolbar from '@/components/FooterToolbar'
import NumberInfo from '@/components/NumberInfo'
import DetailList from '@/components/tools/DetailList'
import Tree from '@/components/Tree/Tree'
import Trend from '@/components/Trend'
import STable from '@/components/table'
import MultiTab from '@/components/MultiTab'
import Result from '@/components/Result'
export {
AvatarList,
Bar,
ChartCard,
Liquid,
MiniArea,
MiniBar,
MiniProgress,
Radar,
RankList,
TransferBar,
Trend,
CountDown,
Ellipsis,
FooterToolbar,
NumberInfo,
DetailList,
Tree,
STable,
MultiTab,
Result
}

View File

@ -18,7 +18,7 @@
<script> <script>
import ALayoutSider from 'ant-design-vue/es/layout/Sider' import ALayoutSider from 'ant-design-vue/es/layout/Sider'
import Logo from '../tools/Logo' import Logo from '../header/Logo'
import SMenu from './index' import SMenu from './index'
import { mixin, mixinDevice } from '@/utils/mixin.js' import { mixin, mixinDevice } from '@/utils/mixin.js'

View File

@ -40,9 +40,9 @@
<script> <script>
import Breadcrumb from '@/components/widgets/Breadcrumb' import Breadcrumb from '@/components/widgets/Breadcrumb'
import Logo from '../tools/Logo' import Logo from '../header/Logo'
import SMenu from '../menu/' import SMenu from '../menu/'
import UserMenu from '../tools/UserMenu' import UserMenu from '../header/UserMenu'
import { mixin } from '@/utils/mixin.js' import { mixin } from '@/utils/mixin.js'

View File

@ -1,256 +0,0 @@
import T from 'ant-design-vue/es/table/Table'
import get from 'lodash.get'
export default {
data () {
return {
needTotalList: [],
selectedRows: [],
selectedRowKeys: [],
localLoading: false,
localDataSource: [],
localPagination: Object.assign({}, T.props.pagination)
}
},
props: Object.assign({}, T.props, {
rowKey: {
type: [String, Function],
default: 'id'
},
data: {
type: Function,
required: true
},
pageNum: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 10
},
showSizeChanger: {
type: Boolean,
default: true
},
size: {
type: String,
default: 'default'
},
/**
* {
* show: true,
* clear: Function
* }
*/
alert: {
type: [Object, Boolean],
default: null
},
rowSelection: {
type: Object,
default: null
},
/** @Deprecated */
showAlertInfo: {
type: Boolean,
default: false
},
showPagination: {
type: String,
default: 'auto'
}
}),
watch: {
'localPagination.current' (val) {
this.$router.push({
name: this.$route.name,
params: Object.assign({}, this.$route.params, {
pageNo: val
})
})
},
pageNum (val) {
Object.assign(this.localPagination, {
current: val
})
},
pageSize (val) {
Object.assign(this.localPagination, {
pageSize: val
})
},
showSizeChanger (val) {
Object.assign(this.localPagination, {
showSizeChanger: val
})
}
},
created () {
this.localPagination = ['auto', true].includes(this.showPagination) && Object.assign({}, this.localPagination, {
current: this.pageNum,
pageSize: this.pageSize,
showSizeChanger: this.showSizeChanger
})
this.needTotalList = this.initTotalList(this.columns)
this.loadData()
},
methods: {
refresh (bool = false) {
bool && (this.localPagination = Object.assign({}, {
current: 1, pageSize: this.pageSize
}))
this.loadData()
},
loadData (pagination, filters, sorter) {
this.localLoading = true
const parameter = Object.assign({
pageNo: (pagination && pagination.current) ||
this.localPagination.current,
pageSize: (pagination && pagination.pageSize) ||
this.localPagination.pageSize
},
(sorter && sorter.field && {
sortField: sorter.field
}) || {},
(sorter && sorter.order && {
sortOrder: sorter.order
}) || {}, {
...filters
}
)
const result = this.data(parameter)
// eslint-disable-next-line
if (result instanceof Promise || '[object Promise]' === result.toString()) {
result.then(r => {
this.localPagination = Object.assign({}, this.localPagination, {
current: r.pageNo, // 返回结果中的当前分页数
total: r.totalCount, // 返回结果中的总记录数
showSizeChanger: this.showSizeChanger,
pageSize: (pagination && pagination.pageSize) ||
this.localPagination.pageSize
})
if (r.data.length === 0 && this.localPagination.current !== 1) {
this.localPagination.current--
this.loadData()
return
}
!r.totalCount && ['auto', false].includes(this.showPagination) && (this.localPagination = false)
this.localDataSource = r.data
this.localLoading = false
})
}
},
initTotalList (columns) {
const totalList = []
columns && columns instanceof Array && columns.forEach(column => {
if (column.needTotal) {
totalList.push({
...column,
total: 0
})
}
})
return totalList
},
updateSelect (selectedRowKeys, selectedRows) {
this.selectedRows = selectedRows
this.selectedRowKeys = selectedRowKeys
const list = this.needTotalList
this.needTotalList = list.map(item => {
return {
...item,
total: selectedRows.reduce((sum, val) => {
const total = sum + parseInt(get(val, item.dataIndex))
return isNaN(total) ? 0 : total
}, 0)
}
})
},
clearSelected () {
if (this.rowSelection) {
this.rowSelection.onChange([], [])
this.updateSelect([], [])
}
},
renderClear (callback) {
if (this.selectedRowKeys.length <= 0) return null
return (
<a style="margin-left: 24px" onClick={() => {
callback()
this.clearSelected()
}}>清空</a>
)
},
renderAlert () {
const needTotalItems = this.needTotalList.map((item) => {
return (<span style="margin-right: 12px">
{item.title}总计 <a style="font-weight: 600">{!item.customRender ? item.total : item.customRender(item.total)}</a>
</span>)
})
const clearItem = (typeof this.alert.clear === 'boolean' && this.alert.clear) ? (
this.renderClear(this.clearSelected)
) : (this.alert !== null && typeof this.alert.clear === 'function') ? (
this.renderClear(this.alert.clear)
) : null
return (
<a-alert showIcon={true} style="margin-bottom: 16px">
<template slot="message">
<span style="margin-right: 12px">已选择: <a style="font-weight: 600">{this.selectedRows.length}</a></span>
{needTotalItems}
{clearItem}
</template>
</a-alert>
)
}
},
render () {
const props = {}
const localKeys = Object.keys(this.$data)
const showAlert = (typeof this.alert === 'object' && this.alert !== null && this.alert.show) && typeof this.rowSelection.selectedRowKeys !== 'undefined' || this.alert
Object.keys(T.props).forEach(k => {
const localKey = `local${k.substring(0, 1).toUpperCase()}${k.substring(1)}`
if (localKeys.includes(localKey)) {
props[k] = this[localKey]
return props[k]
}
if (k === 'rowSelection') {
if (showAlert && this.rowSelection) {
props[k] = {
selectedRows: this.selectedRows,
selectedRowKeys: this.selectedRowKeys,
onChange: (selectedRowKeys, selectedRows) => {
this.updateSelect(selectedRowKeys, selectedRows)
typeof this[k].onChange !== 'undefined' && this[k].onChange(selectedRowKeys, selectedRows)
}
}
return props[k]
} else if (!this.rowSelection) {
props[k] = null
return props[k]
}
}
props[k] = this[k]
return props[k]
})
const table = (
<a-table {...{ props, scopedSlots: { ...this.$scopedSlots } }} onChange={this.loadData}>
{ Object.keys(this.$slots).map(name => (<template slot={name}>{this.$slots[name]}</template>)) }
</a-table>
)
return (
<div class="table-wrapper">
{ showAlert ? this.renderAlert() : null }
{ table }
</div>
)
}
}

View File

@ -1,153 +0,0 @@
<template>
<div :class="['detail-list', size, layout === 'vertical' ? 'vertical': 'horizontal']">
<div v-if="title" class="title">{{ title }}</div>
<a-row>
<slot></slot>
</a-row>
</div>
</template>
<script>
import { Col } from 'ant-design-vue/es/grid/'
const Item = {
name: 'DetailListItem',
props: {
term: {
type: String,
default: '',
required: false
}
},
inject: {
col: {
type: Number
}
},
render () {
return (
<Col {...{ props: responsive[this.col] }}>
<div class="term">{this.$props.term}</div>
<div class="content">{this.$slots.default}</div>
</Col>
)
}
}
const responsive = {
1: { xs: 24 },
2: { xs: 24, sm: 12 },
3: { xs: 24, sm: 12, md: 8 },
4: { xs: 24, sm: 12, md: 6 }
}
export default {
name: 'DetailList',
Item: Item,
components: {
Col
},
props: {
title: {
type: String,
default: '',
required: false
},
col: {
type: Number,
required: false,
default: 3
},
size: {
type: String,
required: false,
default: 'large'
},
layout: {
type: String,
required: false,
default: 'horizontal'
}
},
provide () {
return {
col: this.col > 4 ? 4 : this.col
}
}
}
</script>
<style lang="less" scoped>
.detail-list {
.title {
color: rgba(0,0,0,.85);
font-size: 14px;
font-weight: 500;
margin-bottom: 16px;
}
/deep/ .term {
color: rgba(0,0,0,.85);
display: table-cell;
line-height: 20px;
margin-right: 8px;
padding-bottom: 16px;
white-space: nowrap;
&:not(:empty):after {
content: ":";
margin: 0 8px 0 2px;
position: relative;
top: -.5px;
}
}
/deep/ .content {
color: rgba(0,0,0,.65);
display: table-cell;
min-height: 22px;
line-height: 22px;
padding-bottom: 16px;
width: 100%;
&:empty {
content: ' ';
height: 38px;
padding-bottom: 16px;
}
}
&.small {
.title {
font-size: 14px;
color: rgba(0, 0, 0, .65);
font-weight: normal;
margin-bottom: 12px;
}
/deep/ .term, .content {
padding-bottom: 8px;
}
}
&.large {
/deep/ .term, .content {
padding-bottom: 16px;
}
.title {
font-size: 16px;
}
}
&.vertical {
.term {
padding-bottom: 8px;
}
/deep/ .term, .content {
display: block;
}
}
}
</style>

View File

@ -1,69 +0,0 @@
<template>
<div class="head-info" :class="center && 'center'">
<span class="head-info-title">{{ title }}</span>
<p class="head-info-content">{{ content }}</p>
<em class="head-info-border" v-if="bordered"/>
</div>
</template>
<script>
export default {
name: 'HeadInfo',
props: {
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
bordered: {
type: Boolean,
default: false
},
center: {
type: Boolean,
default: true
}
}
}
</script>
<style lang="less" scoped>
.head-info {
position: relative;
text-align: left;
padding: 0 32px 0 0;
min-width: 125px;
&.center {
text-align: center;
padding: 0 32px;
}
&-title {
color: rgba(0, 0, 0, .45);
display: inline-block;
font-size: 14px;
line-height: 22px;
margin-bottom: 4px;
}
&-content {
color: rgba(0, 0, 0, .85);
font-size: 24px;
line-height: 32px;
margin: 0;
}
&-border {
background-color: #e8e8e8;
position: absolute;
height: 56px;
width: 1px;
top: 0;
right: 0;
}
}
</style>

View File

@ -1,89 +0,0 @@
<template>
<!-- 两步验证 -->
<a-modal
centered
v-model="visible"
@cancel="handleCancel"
:maskClosable="false"
>
<div slot="title" :style="{ textAlign: 'center' }">两步验证</div>
<template slot="footer">
<div :style="{ textAlign: 'center' }">
<a-button key="back" @click="handleCancel">返回</a-button>
<a-button key="submit" type="primary" :loading="stepLoading" @click="handleStepOk">
继续
</a-button>
</div>
</template>
<a-spin :spinning="stepLoading">
<a-form layout="vertical" :auto-form-create="(form)=>{this.form = form}">
<div class="step-form-wrapper">
<p style="text-align: center" v-if="!stepLoading">请在手机中打开 Google Authenticator 或两步验证 APP<br />输入 6 位动态码</p>
<p style="text-align: center" v-else>正在验证..<br/>请稍后</p>
<a-form-item
:style="{ textAlign: 'center' }"
hasFeedback
fieldDecoratorId="stepCode"
:fieldDecoratorOptions="{rules: [{ required: true, message: '请输入 6 位动态码!', pattern: /^\d{6}$/, len: 6 }]}"
>
<a-input :style="{ textAlign: 'center' }" @keyup.enter.native="handleStepOk" placeholder="000000" />
</a-form-item>
<p style="text-align: center">
<a @click="onForgeStepCode">遗失手机?</a>
</p>
</div>
</a-form>
</a-spin>
</a-modal>
</template>
<script>
export default {
props: {
visible: {
type: Boolean,
default: false
}
},
data () {
return {
stepLoading: false,
form: null
}
},
methods: {
handleStepOk () {
const vm = this
this.stepLoading = true
this.form.validateFields((err, values) => {
if (!err) {
console.log('values', values)
setTimeout(() => {
vm.stepLoading = false
vm.$emit('success', { values })
}, 2000)
return
}
this.stepLoading = false
this.$emit('error', { err })
})
},
handleCancel () {
this.visible = false
this.$emit('cancel')
},
onForgeStepCode () {
}
}
}
</script>
<style lang="less" scoped>
.step-form-wrapper {
margin: 0 auto;
width: 80%;
max-width: 400px;
}
</style>

View File

@ -1,94 +0,0 @@
import { message } from 'ant-design-vue/es'
let lessNodesAppended
const colorList = [
{
key: '薄暮', color: '#F5222D'
},
{
key: '火山', color: '#FA541C'
},
{
key: '日暮', color: '#FAAD14'
},
{
key: '明青', color: '#13C2C2'
},
{
key: '极光绿', color: '#52C41A'
},
{
key: '拂晓蓝(默认)', color: '#1890FF'
},
{
key: '极客蓝', color: '#2F54EB'
},
{
key: '酱紫', color: '#722ED1'
}
]
const updateTheme = primaryColor => {
// Don't compile less in production!
/* if (process.env.NODE_ENV === 'production') {
return;
} */
// Determine if the component is remounted
if (!primaryColor) {
return
}
const hideMessage = message.loading('正在编译主题!', 0)
function buildIt () {
if (!window.less) {
return
}
setTimeout(() => {
window.less
.modifyVars({
'@primary-color': primaryColor
})
.then(() => {
hideMessage()
})
.catch(() => {
message.error('Failed to update theme')
hideMessage()
})
}, 200)
}
if (!lessNodesAppended) {
// insert less.js and color.less
const lessStyleNode = document.createElement('link')
const lessConfigNode = document.createElement('script')
const lessScriptNode = document.createElement('script')
lessStyleNode.setAttribute('rel', 'stylesheet/less')
lessStyleNode.setAttribute('href', '/color.less')
lessConfigNode.innerHTML = `
window.less = {
async: true,
env: 'production',
javascriptEnabled: true
};
`
lessScriptNode.src = 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js'
lessScriptNode.async = true
lessScriptNode.onload = () => {
buildIt()
lessScriptNode.onload = null
}
document.body.appendChild(lessStyleNode)
document.body.appendChild(lessConfigNode)
document.body.appendChild(lessScriptNode)
lessNodesAppended = true
} else {
buildIt()
}
}
const updateInvertedMode = invertedMode => {
invertedMode = true
invertedMode ? document.body.classList.add('layout-inverted-mode') : document.body.classList.remove('layout-inverted-mode')
}
export { updateTheme, colorList, updateInvertedMode }

View File

@ -67,7 +67,7 @@
<script> <script>
import ChartCard from '@/components/chart/ChartCard' import ChartCard from '@/components/widgets/ChartCard'
export default { export default {
name: 'FormView', name: 'FormView',

View File

@ -13,14 +13,7 @@
<slot name="name"> <slot name="name">
<h4> <h4>
{{ resource.displayname || resource.name }} {{ resource.displayname || resource.name }}
<a <console :resource="resource" size="default" />
v-if="['vm', 'systemvm', 'router'].includes($route.meta.name)"
:href="'/client/console?cmd=access&vm=' + resource.id"
target="_blank">
<a-button shape="circle" >
<a-icon type="code" />
</a-button>
</a>
</h4> </h4>
<a-tag v-if="resource.instancename"> <a-tag v-if="resource.instancename">
{{ resource.instancename }} {{ resource.instancename }}
@ -372,12 +365,14 @@
<script> <script>
import { api } from '@/api' import { api } from '@/api'
import Console from '@/components/widgets/Console'
import OsLogo from '@/components/widgets/OsLogo' import OsLogo from '@/components/widgets/OsLogo'
import Status from '@/components/widgets/Status' import Status from '@/components/widgets/Status'
export default { export default {
name: 'InfoCard', name: 'InfoCard',
components: { components: {
Console,
OsLogo, OsLogo,
Status Status
}, },

View File

@ -19,6 +19,7 @@
<a slot="name" slot-scope="text, record" href="javascript:;"> <a slot="name" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: $route.path + '/' + record.id }" v-if="record.id">{{ text }}</router-link> <router-link :to="{ path: $route.path + '/' + record.id }" v-if="record.id">{{ text }}</router-link>
<router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{ text }}</router-link> <router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{ text }}</router-link>
<console :resource="record" size="small" style="float: right" />
</a> </a>
<a slot="displayname" slot-scope="text, record" href="javascript:;"> <a slot="displayname" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link> <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
@ -56,11 +57,13 @@
</template> </template>
<script> <script>
import Console from '@/components/widgets/Console'
import Status from '@/components/widgets/Status' import Status from '@/components/widgets/Status'
export default { export default {
name: 'ListView', name: 'ListView',
components: { components: {
Console,
Status Status
}, },
props: { props: {

View File

@ -0,0 +1,26 @@
<template>
<a
v-if="['vm', 'systemvm', 'router'].includes($route.meta.name) && !['Stopped', 'Error', 'Destroyed'].includes(resource.state)"
:href="'/client/console?cmd=access&vm=' + resource.id"
target="_blank">
<a-button shape="circle" type="dashed" :size="size" >
<a-icon type="code" />
</a-button>
</a>
</template>
<script>
export default {
name: 'Console',
props: {
resource: {
type: Object,
required: true
},
size: {
type: String,
default: 'small'
}
}
}
</script>

View File

@ -9,7 +9,7 @@
<script> <script>
import RouteView from '@/layouts/RouteView' import RouteView from '@/layouts/RouteView'
import MultiTab from '@/components/MultiTab' import MultiTab from '@/components/multitab'
import GlobalLayout from '@/components/page/GlobalLayout' import GlobalLayout from '@/components/page/GlobalLayout'
export default { export default {

View File

@ -200,8 +200,8 @@ import { mixinDevice } from '@/utils/mixin.js'
import store from '@/store' import store from '@/store'
import Breadcrumb from '@/components/widgets/Breadcrumb' import Breadcrumb from '@/components/widgets/Breadcrumb'
import ChartCard from '@/components/widgets/ChartCard'
import Status from '@/components/widgets/Status' import Status from '@/components/widgets/Status'
import ChartCard from '@/components/chart/ChartCard'
import ListView from '@/components/view/ListView' import ListView from '@/components/view/ListView'
import ResourceView from '@/components/view/ResourceView' import ResourceView from '@/components/view/ResourceView'

View File

@ -105,13 +105,11 @@
<script> <script>
import { api } from '@/api' import { api } from '@/api'
import ChartCard from '@/components/chart/ChartCard' import ChartCard from '@/components/widgets/ChartCard'
import ACol from 'ant-design-vue/es/grid/Col'
export default { export default {
name: 'CapacityDashboard', name: 'CapacityDashboard',
components: { components: {
ACol,
ChartCard ChartCard
}, },
data () { data () {

View File

@ -48,13 +48,11 @@
<script> <script>
import { api } from '@/api' import { api } from '@/api'
import ChartCard from '@/components/chart/ChartCard' import ChartCard from '@/components/widgets/ChartCard'
import ACol from 'ant-design-vue/es/grid/Col'
export default { export default {
name: 'UsageDashboard', name: 'UsageDashboard',
components: { components: {
ACol,
ChartCard ChartCard
}, },
data () { data () {

View File

@ -46,7 +46,7 @@
import { api } from '@/api' import { api } from '@/api'
import router from '@/router' import router from '@/router'
import ChartCard from '@/components/chart/ChartCard' import ChartCard from '@/components/widgets/ChartCard'
export default { export default {
name: 'InfraSummary', name: 'InfraSummary',