mirror of
https://github.com/veops/cmdb.git
synced 2025-08-08 21:47:06 +08:00
前后端全面升级
This commit is contained in:
@@ -1,89 +0,0 @@
|
||||
<template>
|
||||
<div class="antd-pro-components-article-list-content-index-listContent">
|
||||
<div class="description">
|
||||
<slot>
|
||||
{{ description }}
|
||||
</slot>
|
||||
</div>
|
||||
<div class="extra">
|
||||
<a-avatar :src="avatar" size="small" />
|
||||
<a :href="href">{{ owner }}</a> 发布在 <a :href="href">{{ href }}</a>
|
||||
<em>{{ updateAt | moment }}</em>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ArticleListContent',
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'antd-pro-components-article-list-content-index-listContent'
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
owner: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
avatar: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
updateAt: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '../index.less';
|
||||
|
||||
.antd-pro-components-article-list-content-index-listContent {
|
||||
.description {
|
||||
max-width: 720px;
|
||||
line-height: 22px;
|
||||
}
|
||||
.extra {
|
||||
margin-top: 16px;
|
||||
color: @text-color-secondary;
|
||||
line-height: 22px;
|
||||
|
||||
& /deep/ .ant-avatar {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
& > em {
|
||||
margin-left: 16px;
|
||||
color: @disabled-color;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: @screen-xs) {
|
||||
.antd-pro-components-article-list-content-index-listContent {
|
||||
.extra {
|
||||
& > em {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,3 +0,0 @@
|
||||
import ArticleListContent from './ArticleListContent'
|
||||
|
||||
export default ArticleListContent
|
@@ -1,46 +0,0 @@
|
||||
<template>
|
||||
<tooltip v-if="tips !== ''">
|
||||
<template slot="title">{{ tips }}</template>
|
||||
<avatar :size="avatarSize" :src="src" />
|
||||
</tooltip>
|
||||
<avatar v-else :size="avatarSize" :src="src" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Avatar from 'ant-design-vue/es/avatar'
|
||||
import Tooltip from 'ant-design-vue/es/tooltip'
|
||||
|
||||
export default {
|
||||
name: 'AvatarItem',
|
||||
components: {
|
||||
Avatar,
|
||||
Tooltip
|
||||
},
|
||||
props: {
|
||||
tips: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
size: this.$parent.size
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
avatarSize () {
|
||||
return this.size !== 'mini' && this.size || 20
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$parent.size' (val) {
|
||||
this.size = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -1,99 +0,0 @@
|
||||
<!--
|
||||
<template>
|
||||
<div :class="[prefixCls]">
|
||||
<ul>
|
||||
<slot></slot>
|
||||
<template v-for="item in filterEmpty($slots.default).slice(0, 3)"></template>
|
||||
|
||||
<template v-if="maxLength > 0 && filterEmpty($slots.default).length > maxLength">
|
||||
<avatar-item :size="size">
|
||||
<avatar :size="size !== 'mini' && size || 20" :style="excessItemsStyle">{{ `+${maxLength}` }}</avatar>
|
||||
</avatar-item>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
-->
|
||||
|
||||
<script>
|
||||
import Avatar from 'ant-design-vue/es/avatar'
|
||||
import AvatarItem from './Item'
|
||||
import { filterEmpty } from '@/components/_util/util'
|
||||
|
||||
export default {
|
||||
AvatarItem,
|
||||
name: 'AvatarList',
|
||||
components: {
|
||||
Avatar,
|
||||
AvatarItem
|
||||
},
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-pro-avatar-list'
|
||||
},
|
||||
/**
|
||||
* 头像大小 类型: large、small 、mini, default
|
||||
* 默认值: default
|
||||
*/
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 'default'
|
||||
},
|
||||
/**
|
||||
* 要显示的最大项目
|
||||
*/
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* 多余的项目风格
|
||||
*/
|
||||
excessItemsStyle: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
color: '#f56a00',
|
||||
backgroundColor: '#fde3cf'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
getItems (items) {
|
||||
const classString = {
|
||||
[`${this.prefixCls}-item`]: true,
|
||||
[`${this.size}`]: true
|
||||
}
|
||||
|
||||
if (this.maxLength > 0) {
|
||||
items = items.slice(0, this.maxLength)
|
||||
items.push((<Avatar size={ this.size } style={ this.excessItemsStyle }>{`+${this.maxLength}`}</Avatar>))
|
||||
}
|
||||
const itemList = items.map((item) => (
|
||||
<li class={ classString }>{ item }</li>
|
||||
))
|
||||
return itemList
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { prefixCls, size } = this.$props
|
||||
const classString = {
|
||||
[`${prefixCls}`]: true,
|
||||
[`${size}`]: true
|
||||
}
|
||||
const items = filterEmpty(this.$slots.default)
|
||||
const itemsDom = items && items.length ? <ul class={`${prefixCls}-items`}>{ this.getItems(items) }</ul> : null
|
||||
|
||||
return (
|
||||
<div class={ classString }>
|
||||
{ itemsDom }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -1,4 +0,0 @@
|
||||
import AvatarList from './List'
|
||||
import './index.less'
|
||||
|
||||
export default AvatarList
|
@@ -1,60 +0,0 @@
|
||||
@import "../index";
|
||||
|
||||
@avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list";
|
||||
@avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item";
|
||||
|
||||
.@{avatar-list-prefix-cls} {
|
||||
display: inline-block;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0 0 0 8px;
|
||||
font-size: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.@{avatar-list-item-prefix-cls} {
|
||||
display: inline-block;
|
||||
font-size: @font-size-base;
|
||||
margin-left: -8px;
|
||||
width: @avatar-size-base;
|
||||
height: @avatar-size-base;
|
||||
|
||||
:global {
|
||||
.ant-avatar {
|
||||
border: 1px solid #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
width: @avatar-size-lg;
|
||||
height: @avatar-size-lg;
|
||||
}
|
||||
|
||||
&.small {
|
||||
width: @avatar-size-sm;
|
||||
height: @avatar-size-sm;
|
||||
}
|
||||
|
||||
&.mini {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
:global {
|
||||
.ant-avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
|
||||
.ant-avatar-string {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,64 +0,0 @@
|
||||
# AvatarList 用户头像列表
|
||||
|
||||
|
||||
一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。
|
||||
|
||||
|
||||
|
||||
引用方式:
|
||||
|
||||
```javascript
|
||||
import AvatarList from '@/components/AvatarList'
|
||||
const AvatarListItem = AvatarList.AvatarItem
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AvatarList,
|
||||
AvatarListItem
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 代码演示 [demo](https://pro.loacg.com/test/home)
|
||||
|
||||
```html
|
||||
<avatar-list size="mini">
|
||||
<avatar-list-item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
|
||||
<avatar-list-item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
|
||||
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
</avatar-list>
|
||||
```
|
||||
或
|
||||
```html
|
||||
<avatar-list :max-length="3">
|
||||
<avatar-list-item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
|
||||
<avatar-list-item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
|
||||
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
</avatar-list>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### AvatarList
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ---------------- | -------- | ---------------------------------- | --------- |
|
||||
| size | 头像大小 | `large`、`small` 、`mini`, `default` | `default` |
|
||||
| maxLength | 要显示的最大项目 | number | - |
|
||||
| excessItemsStyle | 多余的项目风格 | CSSProperties | - |
|
||||
|
||||
### AvatarList.Item
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ---- | ------ | --------- | --- |
|
||||
| tips | 头像展示文案 | string | - |
|
||||
| src | 头像图片连接 | string | - |
|
||||
|
2
cmdb-ui/src/components/CMDBExprDrawer/index.js
Normal file
2
cmdb-ui/src/components/CMDBExprDrawer/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import CMDBExprDrawer from './index.vue'
|
||||
export default CMDBExprDrawer
|
47
cmdb-ui/src/components/CMDBExprDrawer/index.vue
Normal file
47
cmdb-ui/src/components/CMDBExprDrawer/index.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
width="1000px"
|
||||
:visible="visible"
|
||||
@close="handleClose"
|
||||
:hasTitle="false"
|
||||
:hasFooter="false"
|
||||
:closable="false"
|
||||
:bodyStyle="{ padding: '24px 12px' }"
|
||||
:placement="placement"
|
||||
>
|
||||
<ResourceSearch :fromCronJob="true" @copySuccess="copySuccess" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ResourceSearch from '@/modules/cmdb/views/resource_search'
|
||||
export default {
|
||||
name: 'CMDBExprDrawer',
|
||||
components: { ResourceSearch },
|
||||
props: {
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'right',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.visible = true
|
||||
},
|
||||
handleClose() {
|
||||
this.visible = false
|
||||
},
|
||||
copySuccess(text) {
|
||||
this.$emit('copySuccess', text)
|
||||
this.handleClose()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
33
cmdb-ui/src/components/CMDBFilterComp/constants.js
Normal file
33
cmdb-ui/src/components/CMDBFilterComp/constants.js
Normal file
@@ -0,0 +1,33 @@
|
||||
export const ruleTypeList = [
|
||||
{ value: 'and', label: '与' },
|
||||
{ value: 'or', label: '或' },
|
||||
// { value: 'not', label: '非' },
|
||||
]
|
||||
|
||||
export const expList = [
|
||||
{ value: 'is', label: '等于' },
|
||||
{ value: '~is', label: '不等于' },
|
||||
{ value: 'contain', label: '包含' },
|
||||
{ value: '~contain', label: '不包含' },
|
||||
{ value: 'start_with', label: '以...开始' },
|
||||
{ value: '~start_with', label: '不以...开始' },
|
||||
{ value: 'end_with', label: '以...结束' },
|
||||
{ value: '~end_with', label: '不以...结束' },
|
||||
{ value: '~value', label: '为空' }, // 为空的定义有点绕
|
||||
{ value: 'value', label: '不为空' },
|
||||
]
|
||||
|
||||
export const advancedExpList = [
|
||||
{ value: 'in', label: 'in查询' },
|
||||
{ value: '~in', label: '非in查询' },
|
||||
{ value: 'range', label: '范围' },
|
||||
{ value: '~range', label: '范围外' },
|
||||
{ value: 'compare', label: '比较' },
|
||||
]
|
||||
|
||||
export const compareTypeList = [
|
||||
{ value: '1', label: '>' },
|
||||
{ value: '2', label: '>=' },
|
||||
{ value: '3', label: '<' },
|
||||
{ value: '4', label: '<=' },
|
||||
]
|
285
cmdb-ui/src/components/CMDBFilterComp/expression.vue
Normal file
285
cmdb-ui/src/components/CMDBFilterComp/expression.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
|
||||
<div :style="{ width: '50px', height: '24px', position: 'relative' }">
|
||||
<treeselect
|
||||
v-if="index"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '50px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }"
|
||||
v-model="item.type"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="ruleTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '130px', '--custom-height': '24px' }"
|
||||
v-model="item.property"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="canSearchPreferenceAttrList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name,
|
||||
label: node.alias || node.name,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" />
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '100px', '--custom-height': '24px' }"
|
||||
v-model="item.exp"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="[...getExpListByProperty(item.property), ...advancedExpList]"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
@select="(value) => handleChangeExp(value, item, index)"
|
||||
>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '175px', '--custom-height': '24px' }"
|
||||
v-model="item.value"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
|
||||
:options="getChoiceValueByProperty(item.property)"
|
||||
placeholder="请选择"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node[0],
|
||||
label: node[0],
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<a-input-group
|
||||
size="small"
|
||||
compact
|
||||
v-else-if="item.exp === 'range' || item.exp === '~range'"
|
||||
:style="{ width: '175px' }"
|
||||
>
|
||||
<a-input class="ops-input" size="small" v-model="item.min" :style="{ width: '78px' }" placeholder="最小值" />
|
||||
~
|
||||
<a-input class="ops-input" size="small" v-model="item.max" :style="{ width: '78px' }" placeholder="最大值" />
|
||||
</a-input-group>
|
||||
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '60px', '--custom-height': '24px' }"
|
||||
v-model="item.compareType"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="compareTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
</treeselect>
|
||||
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
|
||||
</a-input-group>
|
||||
<a-input
|
||||
v-else-if="item.exp !== 'value' && item.exp !== '~value'"
|
||||
size="small"
|
||||
v-model="item.value"
|
||||
:placeholder="item.exp === 'in' || item.exp === '~in' ? '以 ; 分隔' : ''"
|
||||
class="ops-input"
|
||||
></a-input>
|
||||
<div v-else :style="{ width: '175px' }"></div>
|
||||
<a-tooltip title="复制">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="删除">
|
||||
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
<div class="table-filter-add">
|
||||
<a @click="handleAddRule">+ 新增</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
|
||||
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
|
||||
|
||||
export default {
|
||||
name: 'Expression',
|
||||
components: { ValueTypeMapIcon },
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ruleTypeList,
|
||||
expList,
|
||||
advancedExpList,
|
||||
compareTypeList,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ruleList: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getExpListByProperty(property) {
|
||||
if (property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
|
||||
return [
|
||||
{ value: 'is', label: '等于' },
|
||||
{ value: '~is', label: '不等于' },
|
||||
{ value: '~value', label: '为空' }, // 为空的定义有点绕
|
||||
{ value: 'value', label: '不为空' },
|
||||
]
|
||||
}
|
||||
return this.expList
|
||||
}
|
||||
return this.expList
|
||||
},
|
||||
isChoiceByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleAddRule() {
|
||||
this.ruleList.push({
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleCopyRule(item) {
|
||||
this.ruleList.push({ ...item, id: uuidv4() })
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleDeleteRule(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 1)
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleChangeExp({ value }, item, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
if (value === 'range') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
min: '',
|
||||
max: '',
|
||||
exp: value,
|
||||
}
|
||||
} else if (value === 'compare') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
compareType: '1',
|
||||
exp: value,
|
||||
}
|
||||
} else {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
exp: value,
|
||||
}
|
||||
}
|
||||
this.ruleList = _ruleList
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
276
cmdb-ui/src/components/CMDBFilterComp/index.vue
Normal file
276
cmdb-ui/src/components/CMDBFilterComp/index.vue
Normal file
@@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-popover
|
||||
v-if="isDropdown"
|
||||
v-model="visible"
|
||||
trigger="click"
|
||||
:placement="placement"
|
||||
overlayClassName="table-filter"
|
||||
@visibleChange="visibleChange"
|
||||
>
|
||||
<slot name="popover_item">
|
||||
<a-button type="primary" ghost>条件过滤<a-icon type="filter"/></a-button>
|
||||
</slot>
|
||||
<template slot="content">
|
||||
<Expression v-model="ruleList" :canSearchPreferenceAttrList="canSearchPreferenceAttrList" />
|
||||
<a-divider :style="{ margin: '10px 0' }" />
|
||||
<div style="width:534px">
|
||||
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
||||
<a-button type="primary" size="small" @click="handleSubmit">确定</a-button>
|
||||
<a-button size="small" @click="handleClear">清空</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<Expression v-else v-model="ruleList" :canSearchPreferenceAttrList="canSearchPreferenceAttrList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import Expression from './expression.vue'
|
||||
import { advancedExpList, compareTypeList } from './constants'
|
||||
|
||||
export default {
|
||||
name: 'FilterComp',
|
||||
components: { Expression },
|
||||
props: {
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
expression: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
regQ: {
|
||||
type: String,
|
||||
default: '(?<=q=).+(?=&)|(?<=q=).+$',
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottomRight',
|
||||
},
|
||||
isDropdown: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
advancedExpList,
|
||||
compareTypeList,
|
||||
visible: false,
|
||||
ruleList: [],
|
||||
filterExp: '',
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
visibleChange(open) {
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||
: null
|
||||
if (open && exp) {
|
||||
const expArray = exp.split(',').map((item) => {
|
||||
let has_not = ''
|
||||
const key = item.split(':')[0]
|
||||
const val = item
|
||||
.split(':')
|
||||
.slice(1)
|
||||
.join(':')
|
||||
let type, property, exp, value, min, max, compareType
|
||||
if (key.includes('-')) {
|
||||
type = 'or'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(2)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key.substring(1)
|
||||
}
|
||||
} else {
|
||||
type = 'and'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(1)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key
|
||||
}
|
||||
}
|
||||
|
||||
const in_reg = /(?<=\().+(?=\))/g
|
||||
const range_reg = /(?<=\[).+(?=\])/g
|
||||
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
|
||||
if (val === '*') {
|
||||
exp = has_not + 'value'
|
||||
value = ''
|
||||
} else if (in_reg.test(val)) {
|
||||
exp = has_not + 'in'
|
||||
value = val.match(in_reg)[0]
|
||||
} else if (range_reg.test(val)) {
|
||||
exp = has_not + 'range'
|
||||
value = val.match(range_reg)[0]
|
||||
min = value.split('_TO_')[0]
|
||||
max = value.split('_TO_')[1]
|
||||
} else if (compare_reg.test(val)) {
|
||||
exp = has_not + 'compare'
|
||||
value = val.match(compare_reg)[0]
|
||||
const _compareType = val.substring(0, val.match(compare_reg)['index'])
|
||||
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
|
||||
compareType = compareTypeList[idx].value
|
||||
} else if (!val.includes('*')) {
|
||||
exp = has_not + 'is'
|
||||
value = val
|
||||
} else {
|
||||
const resList = [
|
||||
['contain', /(?<=\*).*(?=\*)/g],
|
||||
['end_with', /(?<=\*).+/g],
|
||||
['start_with', /.+(?=\*)/g],
|
||||
]
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const reg = resList[i]
|
||||
if (reg[1].test(val)) {
|
||||
exp = has_not + reg[0]
|
||||
value = val.match(reg[1])[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type,
|
||||
property,
|
||||
exp,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
compareType,
|
||||
}
|
||||
})
|
||||
this.ruleList = [...expArray]
|
||||
} else if (open) {
|
||||
this.ruleList = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0].name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
handleClear() {
|
||||
this.ruleList = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0].name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
this.filterExp = ''
|
||||
this.visible = false
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.ruleList && this.ruleList.length) {
|
||||
this.ruleList[0].type = 'and' // 增删后,以防万一第一个不是and
|
||||
this.filterExp = ''
|
||||
const expList = this.ruleList.map((rule) => {
|
||||
let singleRuleExp = ''
|
||||
let _exp = rule.exp
|
||||
if (rule.type === 'or') {
|
||||
singleRuleExp += '-'
|
||||
}
|
||||
if (rule.exp.includes('~')) {
|
||||
singleRuleExp += '~'
|
||||
_exp = rule.exp.split('~')[1]
|
||||
}
|
||||
singleRuleExp += `${rule.property}:`
|
||||
if (_exp === 'is') {
|
||||
singleRuleExp += `${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'contain') {
|
||||
singleRuleExp += `*${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'start_with') {
|
||||
singleRuleExp += `${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'end_with') {
|
||||
singleRuleExp += `*${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'value') {
|
||||
singleRuleExp += `*`
|
||||
}
|
||||
if (_exp === 'in') {
|
||||
singleRuleExp += `(${rule.value ?? ''})`
|
||||
}
|
||||
if (_exp === 'range') {
|
||||
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
|
||||
}
|
||||
if (_exp === 'compare') {
|
||||
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
|
||||
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
|
||||
}
|
||||
return singleRuleExp
|
||||
})
|
||||
this.filterExp = expList.join(',')
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
} else {
|
||||
this.$emit('setExpFromFilter', '')
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table-filter {
|
||||
.table-filter-add {
|
||||
margin-top: 10px;
|
||||
& > a {
|
||||
padding: 2px 8px;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.table-filter-extra-icon {
|
||||
padding: 0px 2px;
|
||||
&:hover {
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
background-color: #f0faff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.table-filter-extra-operation {
|
||||
.ant-popover-inner-content {
|
||||
padding: 3px 4px;
|
||||
.operation {
|
||||
cursor: pointer;
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 3px 4px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
background-color: #f0faff;
|
||||
}
|
||||
> .anticon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
57
cmdb-ui/src/components/CMDBValueTypeMapIcon/index.vue
Normal file
57
cmdb-ui/src/components/CMDBValueTypeMapIcon/index.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<span>
|
||||
<ops-icon :type="getPropertyIcon(attr)" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ValueTypeIcon',
|
||||
props: {
|
||||
attr: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPropertyStyle(attr) {
|
||||
switch (attr.value_type) {
|
||||
case '0':
|
||||
return { color: '#cf1322', backgroundColor: '#fff1f0' }
|
||||
case '1':
|
||||
return { color: '#d4b106', backgroundColor: '#feffe6' }
|
||||
case '2':
|
||||
return { color: '#d46b08', backgroundColor: '#fff7e6' }
|
||||
case '3':
|
||||
return { color: '#531dab', backgroundColor: '#f9f0ff' }
|
||||
case '4':
|
||||
return { color: '#389e0d', backgroundColor: '#f6ffed' }
|
||||
case '5':
|
||||
return { color: '#08979c', backgroundColor: '#e6fffb' }
|
||||
case '6':
|
||||
return { color: '#c41d7f', backgroundColor: '#fff0f6' }
|
||||
}
|
||||
},
|
||||
getPropertyIcon(attr) {
|
||||
switch (attr.value_type) {
|
||||
case '0':
|
||||
return 'icon-xianxing-shishu'
|
||||
case '1':
|
||||
return 'icon-xianxing-fudianshu'
|
||||
case '2':
|
||||
return 'icon-xianxing-wenben'
|
||||
case '3':
|
||||
return 'icon-xianxing-datetime'
|
||||
case '4':
|
||||
return 'icon-xianxing-date'
|
||||
case '5':
|
||||
return 'icon-xianxing-time'
|
||||
case '6':
|
||||
return 'icon-xianxing-json'
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
24
cmdb-ui/src/components/CardTitle/CardTitle.vue
Normal file
24
cmdb-ui/src/components/CardTitle/CardTitle.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="ops-card-title">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CardTitle',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ops-card-title {
|
||||
border-top-left-radius: 15px;
|
||||
font-size: 1vw;
|
||||
height: 2.2vw;
|
||||
color: #011d93;
|
||||
background: linear-gradient(270deg, rgba(206, 226, 255, 0) -6.74%, #d2e4ff 96.74%);
|
||||
padding: 5px 10px;
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
2
cmdb-ui/src/components/CardTitle/index.js
Normal file
2
cmdb-ui/src/components/CardTitle/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import CardTitle from './CardTitle'
|
||||
export default CardTitle
|
@@ -1,62 +0,0 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0 0 32px 32px' }">
|
||||
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
|
||||
<v-chart
|
||||
height="254"
|
||||
:data="data"
|
||||
:forceFit="true"
|
||||
:padding="['auto', 'auto', '40', '50']">
|
||||
<v-tooltip />
|
||||
<v-axis />
|
||||
<v-bar position="x*y"/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Bar',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
scale: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [{
|
||||
dataKey: 'x',
|
||||
min: 2
|
||||
}, {
|
||||
dataKey: 'y',
|
||||
title: '时间',
|
||||
min: 1,
|
||||
max: 22
|
||||
}]
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -1,120 +0,0 @@
|
||||
<template>
|
||||
<a-card :loading="loading" :body-style="{ padding: '20px 24px 8px' }" :bordered="false">
|
||||
<div class="chart-card-header">
|
||||
<div class="meta">
|
||||
<span class="chart-card-title">
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
</span>
|
||||
<span class="chart-card-action">
|
||||
<slot name="action"></slot>
|
||||
</span>
|
||||
</div>
|
||||
<div class="total">
|
||||
<slot name="total">
|
||||
<span>{{ typeof total === 'function' && total() || total }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-card-content">
|
||||
<div class="content-fix">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-card-footer">
|
||||
<div class="field">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ChartCard',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
total: {
|
||||
type: [Function, Number, String],
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-card-header {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
.meta {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
color: rgba(0, 0, 0, .45);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-card-action {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.chart-card-footer {
|
||||
border-top: 1px solid #e8e8e8;
|
||||
padding-top: 9px;
|
||||
margin-top: 8px;
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.field {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-card-content {
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
height: 46px;
|
||||
width: 100%;
|
||||
|
||||
.content-fix {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.total {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
color: #000;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
font-size: 30px;
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
@@ -1,67 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-chart
|
||||
:forceFit="true"
|
||||
:height="height"
|
||||
:width="width"
|
||||
:data="data"
|
||||
:scale="scale"
|
||||
:padding="0">
|
||||
<v-tooltip />
|
||||
<v-interval
|
||||
:shape="['liquid-fill-gauge']"
|
||||
position="transfer*value"
|
||||
color=""
|
||||
:v-style="{
|
||||
lineWidth: 10,
|
||||
opacity: 0.75
|
||||
}"
|
||||
:tooltip="[
|
||||
'transfer*value',
|
||||
(transfer, value) => {
|
||||
return {
|
||||
name: transfer,
|
||||
value,
|
||||
};
|
||||
},
|
||||
]"
|
||||
></v-interval>
|
||||
<v-guide
|
||||
v-for="(row, index) in data"
|
||||
:key="index"
|
||||
type="text"
|
||||
:top="true"
|
||||
:position="{
|
||||
gender: row.transfer,
|
||||
value: 45
|
||||
}"
|
||||
:content="row.value + '%'"
|
||||
:v-style="{
|
||||
fontSize: 100,
|
||||
textAlign: 'center',
|
||||
opacity: 0.75,
|
||||
}"
|
||||
/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Liquid',
|
||||
props: {
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -1,56 +0,0 @@
|
||||
<template>
|
||||
<div class="antv-chart-mini">
|
||||
<div class="chart-wrapper" :style="{ height: 46 }">
|
||||
<v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 0, 18, 0]">
|
||||
<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,
|
||||
height: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "chart";
|
||||
</style>
|
@@ -1,57 +0,0 @@
|
||||
<template>
|
||||
<div class="antv-chart-mini">
|
||||
<div class="chart-wrapper" :style="{ height: 46 }">
|
||||
<v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 5, 18, 5]">
|
||||
<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: 30
|
||||
}]
|
||||
|
||||
export default {
|
||||
name: 'MiniBar',
|
||||
data () {
|
||||
return {
|
||||
data,
|
||||
tooltip,
|
||||
scale,
|
||||
height: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "chart";
|
||||
</style>
|
@@ -1,75 +0,0 @@
|
||||
<template>
|
||||
<div class="chart-mini-progress">
|
||||
<div class="target" :style="{ left: target + '%'}">
|
||||
<span :style="{ backgroundColor: color }" />
|
||||
<span :style="{ backgroundColor: color }"/>
|
||||
</div>
|
||||
<div class="progress-wrapper">
|
||||
<div class="progress" :style="{ backgroundColor: color, width: percentage + '%', height: height }"></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>
|
||||
.chart-mini-progress {
|
||||
padding: 5px 0;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.target {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
span {
|
||||
border-radius: 100px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 4px;
|
||||
width: 2px;
|
||||
|
||||
&:last-child {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.progress-wrapper {
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
|
||||
.progress {
|
||||
transition: all .4s cubic-bezier(.08,.82,.17,1) 0s;
|
||||
border-radius: 1px 0 0 1px;
|
||||
background-color: #1890ff;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<div :class="prefixCls">
|
||||
<div class="chart-wrapper" :style="{ height: 46 }">
|
||||
<v-chart :force-fit="true" :height="100" :data="dataSource" :scale="scale" :padding="[36, 0, 18, 0]">
|
||||
<v-tooltip />
|
||||
<v-smooth-line position="x*y" :size="2" />
|
||||
<v-smooth-area position="x*y" />
|
||||
</v-chart>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MiniSmoothArea',
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-pro-smooth-area'
|
||||
},
|
||||
scale: {
|
||||
type: [Object, Array],
|
||||
required: true
|
||||
},
|
||||
dataSource: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
height: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "smooth.area.less";
|
||||
</style>
|
@@ -1,68 +0,0 @@
|
||||
<template>
|
||||
<v-chart :forceFit="true" height="400" :data="data" :padding="[20, 20, 95, 20]" :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: '类型'
|
||||
}
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'Radar',
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
axis1Opts,
|
||||
axis2Opts,
|
||||
scale
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<div class="rank">
|
||||
<h4 class="title">{{ title }}</h4>
|
||||
<ul class="list">
|
||||
<li :key="index" v-for="(item, index) in list">
|
||||
<span :class="index < 3 ? 'active' : null">{{ index + 1 }}</span>
|
||||
<span>{{ item.name }}</span>
|
||||
<span>{{ 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 {
|
||||
padding: 0 32px 32px 72px;
|
||||
|
||||
.list {
|
||||
margin: 25px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
margin-top: 16px;
|
||||
|
||||
span {
|
||||
color: rgba(0, 0, 0, .65);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
|
||||
&:first-child {
|
||||
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;
|
||||
}
|
||||
&.active {
|
||||
background-color: #314659;
|
||||
color: #fff;
|
||||
}
|
||||
&:last-child {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile .rank {
|
||||
padding: 0 32px 32px 32px;
|
||||
}
|
||||
|
||||
</style>
|
@@ -1,113 +0,0 @@
|
||||
<template>
|
||||
<v-chart :width="width" :height="height" :padding="[0]" :data="data" :scale="scale">
|
||||
<v-tooltip :show-title="false" />
|
||||
<v-coord type="rect" direction="TL" />
|
||||
<v-point position="x*y" color="category" shape="cloud" tooltip="value*category" />
|
||||
</v-chart>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { registerShape } from 'viser-vue'
|
||||
const DataSet = require('@antv/data-set')
|
||||
|
||||
const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png'
|
||||
|
||||
const scale = [
|
||||
{ dataKey: 'x', nice: false },
|
||||
{ dataKey: 'y', nice: false }
|
||||
]
|
||||
|
||||
registerShape('point', 'cloud', {
|
||||
draw (cfg, container) {
|
||||
return container.addShape('text', {
|
||||
attrs: {
|
||||
fillOpacity: cfg.opacity,
|
||||
fontSize: cfg.origin._origin.size,
|
||||
rotate: cfg.origin._origin.rotate,
|
||||
text: cfg.origin._origin.text,
|
||||
textAlign: 'center',
|
||||
fontFamily: cfg.origin._origin.font,
|
||||
fill: cfg.color,
|
||||
textBaseline: 'Alphabetic',
|
||||
...cfg.style,
|
||||
x: cfg.x,
|
||||
y: cfg.y
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default {
|
||||
name: 'TagCloud',
|
||||
props: {
|
||||
tagList: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 400
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 640
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
data: [],
|
||||
scale
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
tagList: function (val) {
|
||||
if (val.length > 0) {
|
||||
this.initTagCloud(val)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.tagList.length > 0) {
|
||||
this.initTagCloud(this.tagList)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initTagCloud (dataSource) {
|
||||
const { height, width } = this
|
||||
|
||||
const dv = new DataSet.View().source(dataSource)
|
||||
const range = dv.range('value')
|
||||
const min = range[0]
|
||||
const max = range[1]
|
||||
const imageMask = new Image()
|
||||
imageMask.crossOrigin = ''
|
||||
imageMask.src = imgUrl
|
||||
imageMask.onload = () => {
|
||||
dv.transform({
|
||||
type: 'tag-cloud',
|
||||
fields: ['name', 'value'],
|
||||
size: [width, height],
|
||||
imageMask,
|
||||
font: 'Verdana',
|
||||
padding: 0,
|
||||
timeInterval: 5000, // max execute time
|
||||
rotate () {
|
||||
let random = ~~(Math.random() * 4) % 4
|
||||
if (random === 2) {
|
||||
random = 0
|
||||
}
|
||||
return random * 90 // 0, 90, 270
|
||||
},
|
||||
fontSize (d) {
|
||||
if (d.value) {
|
||||
return ((d.value - min) / (max - min)) * (32 - 8) + 8
|
||||
}
|
||||
return 0
|
||||
}
|
||||
})
|
||||
this.data = dv.rows
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0 0 32px 32px' }">
|
||||
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
|
||||
<v-chart
|
||||
height="254"
|
||||
:data="data"
|
||||
:scale="scale"
|
||||
:forceFit="true"
|
||||
:padding="['auto', 'auto', '40', '50']">
|
||||
<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: '流量(Gb)',
|
||||
alias: '流量(Gb)',
|
||||
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>
|
@@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<div class="chart-trend">
|
||||
{{ term }}
|
||||
<span>{{ rate }}%</span>
|
||||
<span :class="['trend-icon', 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>
|
||||
.chart-trend {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
|
||||
.trend-icon {
|
||||
font-size: 12px;
|
||||
|
||||
&.up, &.down {
|
||||
margin-left: 4px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
transform: scale(.83);
|
||||
}
|
||||
}
|
||||
|
||||
&.up {
|
||||
color: #f5222d;
|
||||
}
|
||||
&.down {
|
||||
color: #52c41a;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,13 +0,0 @@
|
||||
.antv-chart-mini {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.chart-wrapper {
|
||||
position: absolute;
|
||||
bottom: -28px;
|
||||
width: 100%;
|
||||
|
||||
/* margin: 0 -5px;
|
||||
overflow: hidden;*/
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
@import "../index";
|
||||
|
||||
@smoothArea-prefix-cls: ~"@{ant-pro-prefix}-smooth-area";
|
||||
|
||||
.@{smoothArea-prefix-cls} {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.chart-wrapper {
|
||||
position: absolute;
|
||||
bottom: -28px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
124
cmdb-ui/src/components/CollapseTransition/index.vue
Normal file
124
cmdb-ui/src/components/CollapseTransition/index.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<transition
|
||||
:name="transitionName"
|
||||
@before-enter="collapseBeforeEnter"
|
||||
@enter="collapseEnter"
|
||||
@after-enter="collapseAfterEnter"
|
||||
@before-leave="collapseBeforeLeave"
|
||||
@leave="collapseLeave"
|
||||
@after-leave="collapseAfterLeave"
|
||||
>
|
||||
<slot></slot>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 元素折叠过度效果
|
||||
*/
|
||||
export default {
|
||||
name: 'CollapseTransition',
|
||||
props: {
|
||||
transitionName: {
|
||||
type: String,
|
||||
default: 'collapse-transition',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
oldPaddingTop: '',
|
||||
oldPaddingBottom: '',
|
||||
oldOverflow: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
collapseBeforeEnter(el) {
|
||||
// console.log('11, collapseBeforeEnter');
|
||||
this.oldPaddingBottom = el.style.paddingBottom
|
||||
this.oldPaddingTop = el.style.paddingTop
|
||||
// 过渡效果开始前设置元素的maxHeight为0,让元素maxHeight有一个初始值
|
||||
el.style.paddingTop = '0'
|
||||
el.style.paddingBottom = '0'
|
||||
el.style.maxHeight = '0'
|
||||
},
|
||||
collapseEnter(el, done) {
|
||||
// console.log('22, collapseEnter');
|
||||
//
|
||||
this.oldOverflow = el.style.overflow
|
||||
const elHeight = el.scrollHeight
|
||||
// 过渡效果进入后将元素的maxHeight设置为元素本身的高度,将元素maxHeight设置为auto不会有过渡效果
|
||||
if (elHeight > 0) {
|
||||
el.style.maxHeight = elHeight + 'px'
|
||||
} else {
|
||||
el.style.maxHeight = '0'
|
||||
}
|
||||
el.style.paddingTop = this.oldPaddingTop
|
||||
el.style.paddingBottom = this.oldPaddingBottom
|
||||
|
||||
el.style.overflow = 'hidden'
|
||||
// done();
|
||||
const onTransitionDone = function() {
|
||||
done()
|
||||
// console.log('enter onTransitionDone');
|
||||
el.removeEventListener('transitionend', onTransitionDone, false)
|
||||
el.removeEventListener('transitioncancel', onTransitionDone, false)
|
||||
}
|
||||
// 绑定元素的transition完成事件,在transition完成后立即完成vue的过度动效
|
||||
el.addEventListener('transitionend', onTransitionDone, false)
|
||||
el.addEventListener('transitioncancel', onTransitionDone, false)
|
||||
},
|
||||
collapseAfterEnter(el) {
|
||||
// console.log('33, collapseAfterEnter');
|
||||
// 过渡效果完成后恢复元素的maxHeight
|
||||
el.style.maxHeight = ''
|
||||
el.style.overflow = this.oldOverflow
|
||||
},
|
||||
|
||||
collapseBeforeLeave(el) {
|
||||
// console.log('44, collapseBeforeLeave', el.scrollHeight);
|
||||
|
||||
this.oldPaddingBottom = el.style.paddingBottom
|
||||
this.oldPaddingTop = el.style.paddingTop
|
||||
this.oldOverflow = el.style.overflow
|
||||
|
||||
el.style.maxHeight = el.scrollHeight + 'px'
|
||||
el.style.overflow = 'hidden'
|
||||
},
|
||||
collapseLeave(el, done) {
|
||||
// console.log('55, collapseLeave', el.scrollHeight);
|
||||
|
||||
if (el.scrollHeight !== 0) {
|
||||
el.style.maxHeight = '0'
|
||||
el.style.paddingBottom = '0'
|
||||
el.style.paddingTop = '0'
|
||||
}
|
||||
// done();
|
||||
const onTransitionDone = function() {
|
||||
done()
|
||||
// console.log('leave onTransitionDone');
|
||||
el.removeEventListener('transitionend', onTransitionDone, false)
|
||||
el.removeEventListener('transitioncancel', onTransitionDone, false)
|
||||
}
|
||||
// 绑定元素的transition完成事件,在transition完成后立即完成vue的过度动效
|
||||
el.addEventListener('transitionend', onTransitionDone, false)
|
||||
el.addEventListener('transitioncancel', onTransitionDone, false)
|
||||
},
|
||||
collapseAfterLeave(el) {
|
||||
// console.log('66, collapseAfterLeave');
|
||||
el.style.maxHeight = ''
|
||||
el.style.overflow = this.oldOverflow
|
||||
el.style.paddingBottom = this.oldPaddingBottom
|
||||
el.style.paddingTop = this.oldPaddingTop
|
||||
|
||||
this.oldOverflow = this.oldPaddingBottom = this.oldPaddingTop = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.collapse-transition-enter-active,
|
||||
.collapse-transition-leave-active {
|
||||
transition: height 0.3s ease-in-out, padding 0.3s ease-in-out, max-height 0.3s ease-in-out;
|
||||
}
|
||||
</style>
|
@@ -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>
|
@@ -1,3 +0,0 @@
|
||||
import CountDown from './CountDown'
|
||||
|
||||
export default CountDown
|
@@ -1,34 +0,0 @@
|
||||
# CountDown 倒计时
|
||||
|
||||
倒计时组件。
|
||||
|
||||
|
||||
|
||||
引用方式:
|
||||
|
||||
```javascript
|
||||
import CountDown from '@/components/CountDown/CountDown'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CountDown
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 代码演示 [demo](https://pro.loacg.com/test/home)
|
||||
|
||||
```html
|
||||
<count-down :target="new Date().getTime() + 3000000" :on-end="onEndHandle" />
|
||||
```
|
||||
|
||||
|
||||
|
||||
## API
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|----------|------------------------------------------|-------------|-------|
|
||||
| target | 目标时间 | Date | - |
|
||||
| onEnd | 倒计时结束回调 | funtion | -|
|
170
cmdb-ui/src/components/Crontab/Crontab-Day.vue
Normal file
170
cmdb-ui/src/components/Crontab/Crontab-Day.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<el-form size="small">
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="1">
|
||||
日,允许的通配符[, - * / L M]
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="2">
|
||||
不指定
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="3">
|
||||
周期从
|
||||
<el-input-number v-model="cycle01" :min="0" :max="31" /> -
|
||||
<el-input-number v-model="cycle02" :min="0" :max="31" /> 日
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
从
|
||||
<el-input-number v-model="average01" :min="0" :max="31" /> 号开始,每
|
||||
<el-input-number v-model="average02" :min="0" :max="31" /> 日执行一次
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="5">
|
||||
每月
|
||||
<el-input-number v-model="workday" :min="0" :max="31" /> 号最近的那个工作日
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="6">
|
||||
本月最后一天
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="7">
|
||||
指定
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||
<el-option v-for="item in 31" :key="item" :value="item">{{ item }}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
radioValue: 1,
|
||||
workday: 1,
|
||||
cycle01: 1,
|
||||
cycle02: 2,
|
||||
average01: 1,
|
||||
average02: 1,
|
||||
checkboxList: [],
|
||||
checkNum: this.$options.propsData.check,
|
||||
}
|
||||
},
|
||||
name: 'CrontabDay',
|
||||
props: ['check', 'cron'],
|
||||
methods: {
|
||||
// 单选按钮值变化时
|
||||
radioChange() {
|
||||
;('day rachange')
|
||||
|
||||
switch (this.radioValue) {
|
||||
case 1:
|
||||
this.$emit('update', 'day', '*', 'day')
|
||||
this.$emit('update', 'week', '?', 'day')
|
||||
break
|
||||
case 2:
|
||||
this.$emit('update', 'day', '?')
|
||||
this.$emit('update', 'week', '*')
|
||||
break
|
||||
case 3:
|
||||
this.$emit('update', 'day', this.cycle01 + '-' + this.cycle02)
|
||||
break
|
||||
case 4:
|
||||
this.$emit('update', 'day', this.average01 + '/' + this.average02)
|
||||
break
|
||||
case 5:
|
||||
this.$emit('update', 'day', this.workday + 'W')
|
||||
break
|
||||
case 6:
|
||||
this.$emit('update', 'day', 'L')
|
||||
break
|
||||
case 7:
|
||||
this.$emit('update', 'day', this.checkboxString)
|
||||
break
|
||||
}
|
||||
;('day rachange end')
|
||||
},
|
||||
// 周期两个值变化时
|
||||
cycleChange() {
|
||||
if (this.radioValue == '3') {
|
||||
this.$emit('update', 'day', this.cycleTotal)
|
||||
}
|
||||
},
|
||||
// 平均两个值变化时
|
||||
averageChange() {
|
||||
if (this.radioValue == '4') {
|
||||
this.$emit('update', 'day', this.averageTotal)
|
||||
}
|
||||
},
|
||||
// 最近工作日值变化时
|
||||
workdayChange() {
|
||||
if (this.radioValue == '5') {
|
||||
this.$emit('update', 'day', this.workday + 'W')
|
||||
}
|
||||
},
|
||||
// checkbox值变化时
|
||||
checkboxChange() {
|
||||
if (this.radioValue == '7') {
|
||||
this.$emit('update', 'day', this.checkboxString)
|
||||
}
|
||||
},
|
||||
// 父组件传递的week发生变化触发
|
||||
weekChange() {
|
||||
// 判断week值与day不能同时为“?”
|
||||
if (this.cron.week == '?' && this.radioValue == '2') {
|
||||
this.radioValue = '1'
|
||||
} else if (this.cron.week !== '?' && this.radioValue != '2') {
|
||||
this.radioValue = '2'
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
radioValue: 'radioChange',
|
||||
cycleTotal: 'cycleChange',
|
||||
averageTotal: 'averageChange',
|
||||
workdayCheck: 'workdayChange',
|
||||
checkboxString: 'checkboxChange',
|
||||
},
|
||||
computed: {
|
||||
// 计算两个周期值
|
||||
cycleTotal: function() {
|
||||
this.cycle01 = this.checkNum(this.cycle01, 1, 31)
|
||||
this.cycle02 = this.checkNum(this.cycle02, 1, 31)
|
||||
return this.cycle01 + '-' + this.cycle02
|
||||
},
|
||||
// 计算平均用到的值
|
||||
averageTotal: function() {
|
||||
this.average01 = this.checkNum(this.average01, 1, 31)
|
||||
this.average02 = this.checkNum(this.average02, 1, 31)
|
||||
return this.average01 + '/' + this.average02
|
||||
},
|
||||
// 计算工作日格式
|
||||
workdayCheck: function() {
|
||||
this.workday = this.checkNum(this.workday, 1, 31)
|
||||
return this.workday
|
||||
},
|
||||
// 计算勾选的checkbox值合集
|
||||
checkboxString: function() {
|
||||
const str = this.checkboxList.join()
|
||||
return str == '' ? '*' : str
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
123
cmdb-ui/src/components/Crontab/Crontab-Hour.vue
Normal file
123
cmdb-ui/src/components/Crontab/Crontab-Hour.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<el-form size="small">
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="1">
|
||||
小时,允许的通配符[, - * /]
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="2">
|
||||
周期从
|
||||
<el-input-number v-model="cycle01" :min="0" :max="23" /> -
|
||||
<el-input-number v-model="cycle02" :min="0" :max="23" /> 小时
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="3">
|
||||
从
|
||||
<el-input-number v-model="average01" :min="0" :max="23" /> 小时开始,每
|
||||
<el-input-number v-model="average02" :min="0" :max="23" /> 小时执行一次
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
指定
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||
<el-option v-for="item in 24" :key="item" :value="item - 1">{{ item - 1 }}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
radioValue: 1,
|
||||
cycle01: 0,
|
||||
cycle02: 1,
|
||||
average01: 0,
|
||||
average02: 1,
|
||||
checkboxList: [],
|
||||
checkNum: this.$options.propsData.check,
|
||||
}
|
||||
},
|
||||
name: 'CrontabHour',
|
||||
props: ['check', 'cron'],
|
||||
methods: {
|
||||
// 单选按钮值变化时
|
||||
radioChange() {
|
||||
if (this.radioValue === 1) {
|
||||
this.$emit('update', 'hour', '*', 'hour')
|
||||
this.$emit('update', 'day', '*', 'hour')
|
||||
} else {
|
||||
if (this.cron.min === '*') {
|
||||
this.$emit('update', 'min', '0', 'hour')
|
||||
}
|
||||
if (this.cron.second === '*') {
|
||||
this.$emit('update', 'second', '0', 'hour')
|
||||
}
|
||||
}
|
||||
switch (this.radioValue) {
|
||||
case 2:
|
||||
this.$emit('update', 'hour', this.cycle01 + '-' + this.cycle02)
|
||||
break
|
||||
case 3:
|
||||
this.$emit('update', 'hour', this.average01 + '/' + this.average02)
|
||||
break
|
||||
case 4:
|
||||
this.$emit('update', 'hour', this.checkboxString)
|
||||
break
|
||||
}
|
||||
},
|
||||
// 周期两个值变化时
|
||||
cycleChange() {
|
||||
if (this.radioValue == '2') {
|
||||
this.$emit('update', 'hour', this.cycleTotal)
|
||||
}
|
||||
},
|
||||
// 平均两个值变化时
|
||||
averageChange() {
|
||||
if (this.radioValue == '3') {
|
||||
this.$emit('update', 'hour', this.averageTotal)
|
||||
}
|
||||
},
|
||||
// checkbox值变化时
|
||||
checkboxChange() {
|
||||
if (this.radioValue == '4') {
|
||||
this.$emit('update', 'hour', this.checkboxString)
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
radioValue: 'radioChange',
|
||||
cycleTotal: 'cycleChange',
|
||||
averageTotal: 'averageChange',
|
||||
checkboxString: 'checkboxChange',
|
||||
},
|
||||
computed: {
|
||||
// 计算两个周期值
|
||||
cycleTotal: function() {
|
||||
this.cycle01 = this.checkNum(this.cycle01, 0, 23)
|
||||
this.cycle02 = this.checkNum(this.cycle02, 0, 23)
|
||||
return this.cycle01 + '-' + this.cycle02
|
||||
},
|
||||
// 计算平均用到的值
|
||||
averageTotal: function() {
|
||||
this.average01 = this.checkNum(this.average01, 0, 23)
|
||||
this.average02 = this.checkNum(this.average02, 1, 23)
|
||||
return this.average01 + '/' + this.average02
|
||||
},
|
||||
// 计算勾选的checkbox值合集
|
||||
checkboxString: function() {
|
||||
const str = this.checkboxList.join()
|
||||
return str == '' ? '*' : str
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
119
cmdb-ui/src/components/Crontab/Crontab-Min.vue
Normal file
119
cmdb-ui/src/components/Crontab/Crontab-Min.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<el-form size="small">
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="1">
|
||||
分钟,允许的通配符[, - * /]
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="2">
|
||||
周期从
|
||||
<el-input-number v-model="cycle01" :min="0" :max="60" /> -
|
||||
<el-input-number v-model="cycle02" :min="0" :max="60" /> 分钟
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="3">
|
||||
从
|
||||
<el-input-number v-model="average01" :min="0" :max="60" /> 分钟开始,每
|
||||
<el-input-number v-model="average02" :min="0" :max="60" /> 分钟执行一次
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
指定
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||
<el-option v-for="item in 60" :key="item" :value="item - 1">{{ item - 1 }}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
radioValue: 1,
|
||||
cycle01: 1,
|
||||
cycle02: 2,
|
||||
average01: 0,
|
||||
average02: 1,
|
||||
checkboxList: [],
|
||||
checkNum: this.$options.propsData.check,
|
||||
}
|
||||
},
|
||||
name: 'CrontabMin',
|
||||
props: ['check', 'cron'],
|
||||
methods: {
|
||||
// 单选按钮值变化时
|
||||
radioChange() {
|
||||
if (this.radioValue !== 1 && this.cron.second === '*') {
|
||||
this.$emit('update', 'second', '0', 'min')
|
||||
}
|
||||
switch (this.radioValue) {
|
||||
case 1:
|
||||
this.$emit('update', 'min', '*', 'min')
|
||||
this.$emit('update', 'hour', '*', 'min')
|
||||
break
|
||||
case 2:
|
||||
this.$emit('update', 'min', this.cycle01 + '-' + this.cycle02, 'min')
|
||||
break
|
||||
case 3:
|
||||
this.$emit('update', 'min', this.average01 + '/' + this.average02, 'min')
|
||||
break
|
||||
case 4:
|
||||
this.$emit('update', 'min', this.checkboxString, 'min')
|
||||
break
|
||||
}
|
||||
},
|
||||
// 周期两个值变化时
|
||||
cycleChange() {
|
||||
if (this.radioValue == '2') {
|
||||
this.$emit('update', 'min', this.cycleTotal, 'min')
|
||||
}
|
||||
},
|
||||
// 平均两个值变化时
|
||||
averageChange() {
|
||||
if (this.radioValue == '3') {
|
||||
this.$emit('update', 'min', this.averageTotal, 'min')
|
||||
}
|
||||
},
|
||||
// checkbox值变化时
|
||||
checkboxChange() {
|
||||
if (this.radioValue == '4') {
|
||||
this.$emit('update', 'min', this.checkboxString, 'min')
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
radioValue: 'radioChange',
|
||||
cycleTotal: 'cycleChange',
|
||||
averageTotal: 'averageChange',
|
||||
checkboxString: 'checkboxChange',
|
||||
},
|
||||
computed: {
|
||||
// 计算两个周期值
|
||||
cycleTotal: function() {
|
||||
this.cycle01 = this.checkNum(this.cycle01, 0, 59)
|
||||
this.cycle02 = this.checkNum(this.cycle02, 0, 59)
|
||||
return this.cycle01 + '-' + this.cycle02
|
||||
},
|
||||
// 计算平均用到的值
|
||||
averageTotal: function() {
|
||||
this.average01 = this.checkNum(this.average01, 0, 59)
|
||||
this.average02 = this.checkNum(this.average02, 1, 59)
|
||||
return this.average01 + '/' + this.average02
|
||||
},
|
||||
// 计算勾选的checkbox值合集
|
||||
checkboxString: function() {
|
||||
const str = this.checkboxList.join()
|
||||
return str == '' ? '*' : str
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
128
cmdb-ui/src/components/Crontab/Crontab-Mouth.vue
Normal file
128
cmdb-ui/src/components/Crontab/Crontab-Mouth.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<el-form size="small">
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="1">
|
||||
月,允许的通配符[, - * /]
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="2">
|
||||
周期从
|
||||
<el-input-number v-model="cycle01" :min="1" :max="12" /> -
|
||||
<el-input-number v-model="cycle02" :min="1" :max="12" /> 月
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="3">
|
||||
从
|
||||
<el-input-number v-model="average01" :min="1" :max="12" /> 月开始,每
|
||||
<el-input-number v-model="average02" :min="1" :max="12" /> 月月执行一次
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
指定
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||
<el-option v-for="item in 12" :key="item" :value="item">{{ item }}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
radioValue: 1,
|
||||
cycle01: 1,
|
||||
cycle02: 2,
|
||||
average01: 1,
|
||||
average02: 1,
|
||||
checkboxList: [],
|
||||
checkNum: this.check,
|
||||
}
|
||||
},
|
||||
name: 'CrontabMouth',
|
||||
props: ['check', 'cron'],
|
||||
methods: {
|
||||
// 单选按钮值变化时
|
||||
radioChange() {
|
||||
if (this.radioValue === 1) {
|
||||
this.$emit('update', 'mouth', '*')
|
||||
} else {
|
||||
if (this.cron.day === '*') {
|
||||
this.$emit('update', 'day', '0', 'mouth')
|
||||
}
|
||||
if (this.cron.hour === '*') {
|
||||
this.$emit('update', 'hour', '0', 'mouth')
|
||||
}
|
||||
if (this.cron.min === '*') {
|
||||
this.$emit('update', 'min', '0', 'mouth')
|
||||
}
|
||||
if (this.cron.second === '*') {
|
||||
this.$emit('update', 'second', '0', 'mouth')
|
||||
}
|
||||
}
|
||||
switch (this.radioValue) {
|
||||
case 2:
|
||||
this.$emit('update', 'mouth', this.cycle01 + '-' + this.cycle02)
|
||||
break
|
||||
case 3:
|
||||
this.$emit('update', 'mouth', this.average01 + '/' + this.average02)
|
||||
break
|
||||
case 4:
|
||||
this.$emit('update', 'mouth', this.checkboxString)
|
||||
break
|
||||
}
|
||||
},
|
||||
// 周期两个值变化时
|
||||
cycleChange() {
|
||||
if (this.radioValue == '2') {
|
||||
this.$emit('update', 'mouth', this.cycleTotal)
|
||||
}
|
||||
},
|
||||
// 平均两个值变化时
|
||||
averageChange() {
|
||||
if (this.radioValue == '3') {
|
||||
this.$emit('update', 'mouth', this.averageTotal)
|
||||
}
|
||||
},
|
||||
// checkbox值变化时
|
||||
checkboxChange() {
|
||||
if (this.radioValue == '4') {
|
||||
this.$emit('update', 'mouth', this.checkboxString)
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
radioValue: 'radioChange',
|
||||
cycleTotal: 'cycleChange',
|
||||
averageTotal: 'averageChange',
|
||||
checkboxString: 'checkboxChange',
|
||||
},
|
||||
computed: {
|
||||
// 计算两个周期值
|
||||
cycleTotal: function() {
|
||||
this.cycle01 = this.checkNum(this.cycle01, 1, 12)
|
||||
this.cycle02 = this.checkNum(this.cycle02, 1, 12)
|
||||
return this.cycle01 + '-' + this.cycle02
|
||||
},
|
||||
// 计算平均用到的值
|
||||
averageTotal: function() {
|
||||
this.average01 = this.checkNum(this.average01, 1, 12)
|
||||
this.average02 = this.checkNum(this.average02, 1, 12)
|
||||
return this.average01 + '/' + this.average02
|
||||
},
|
||||
// 计算勾选的checkbox值合集
|
||||
checkboxString: function() {
|
||||
const str = this.checkboxList.join()
|
||||
return str == '' ? '*' : str
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
580
cmdb-ui/src/components/Crontab/Crontab-Result.vue
Normal file
580
cmdb-ui/src/components/Crontab/Crontab-Result.vue
Normal file
@@ -0,0 +1,580 @@
|
||||
<template>
|
||||
<div class="popup-result">
|
||||
<p class="title">最近5次运行时间</p>
|
||||
<ul class="popup-result-scroll">
|
||||
<template v-if="isShow">
|
||||
<li v-for="item in resultList" :key="item">{{ item }}</li>
|
||||
</template>
|
||||
<li v-else>计算结果中...</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dayRule: '',
|
||||
dayRuleSup: '',
|
||||
dateArr: [],
|
||||
resultList: [],
|
||||
isShow: false,
|
||||
}
|
||||
},
|
||||
name: 'CrontabResult',
|
||||
methods: {
|
||||
// 表达式值变化时,开始去计算结果
|
||||
expressionChange() {
|
||||
// 计算开始-隐藏结果
|
||||
this.isShow = false
|
||||
// 获取规则数组[0秒、1分、2时、3日、4月、5星期、6年]
|
||||
const ruleArr = this.$options.propsData.ex.split(' ')
|
||||
// 用于记录进入循环的次数
|
||||
let nums = 0
|
||||
// 用于暂时存符号时间规则结果的数组
|
||||
const resultArr = []
|
||||
// 获取当前时间精确至[年、月、日、时、分、秒]
|
||||
const nTime = new Date()
|
||||
const nYear = nTime.getFullYear()
|
||||
let nMouth = nTime.getMonth() + 1
|
||||
let nDay = nTime.getDate()
|
||||
let nHour = nTime.getHours()
|
||||
let nMin = nTime.getMinutes()
|
||||
let nSecond = nTime.getSeconds()
|
||||
// 根据规则获取到近100年可能年数组、月数组等等
|
||||
this.getSecondArr(ruleArr[0])
|
||||
this.getMinArr(ruleArr[1])
|
||||
this.getHourArr(ruleArr[2])
|
||||
this.getDayArr(ruleArr[3])
|
||||
this.getMouthArr(ruleArr[4])
|
||||
this.getWeekArr(ruleArr[5])
|
||||
this.getYearArr(ruleArr[6], nYear)
|
||||
// 将获取到的数组赋值-方便使用
|
||||
const sDate = this.dateArr[0]
|
||||
const mDate = this.dateArr[1]
|
||||
const hDate = this.dateArr[2]
|
||||
const DDate = this.dateArr[3]
|
||||
const MDate = this.dateArr[4]
|
||||
const YDate = this.dateArr[5]
|
||||
// 获取当前时间在数组中的索引
|
||||
let sIdx = this.getIndex(sDate, nSecond)
|
||||
let mIdx = this.getIndex(mDate, nMin)
|
||||
let hIdx = this.getIndex(hDate, nHour)
|
||||
let DIdx = this.getIndex(DDate, nDay)
|
||||
let MIdx = this.getIndex(MDate, nMouth)
|
||||
const YIdx = this.getIndex(YDate, nYear)
|
||||
// 重置月日时分秒的函数(后面用的比较多)
|
||||
const resetSecond = function() {
|
||||
sIdx = 0
|
||||
nSecond = sDate[sIdx]
|
||||
}
|
||||
const resetMin = function() {
|
||||
mIdx = 0
|
||||
nMin = mDate[mIdx]
|
||||
resetSecond()
|
||||
}
|
||||
const resetHour = function() {
|
||||
hIdx = 0
|
||||
nHour = hDate[hIdx]
|
||||
resetMin()
|
||||
}
|
||||
const resetDay = function() {
|
||||
DIdx = 0
|
||||
nDay = DDate[DIdx]
|
||||
resetHour()
|
||||
}
|
||||
const resetMouth = function() {
|
||||
MIdx = 0
|
||||
nMouth = MDate[MIdx]
|
||||
resetDay()
|
||||
}
|
||||
// 如果当前年份不为数组中当前值
|
||||
if (nYear !== YDate[YIdx]) {
|
||||
resetMouth()
|
||||
}
|
||||
// 如果当前月份不为数组中当前值
|
||||
if (nMouth !== MDate[MIdx]) {
|
||||
resetDay()
|
||||
}
|
||||
// 如果当前“日”不为数组中当前值
|
||||
if (nDay !== DDate[DIdx]) {
|
||||
resetHour()
|
||||
}
|
||||
// 如果当前“时”不为数组中当前值
|
||||
if (nHour !== hDate[hIdx]) {
|
||||
resetMin()
|
||||
}
|
||||
// 如果当前“分”不为数组中当前值
|
||||
if (nMin !== mDate[mIdx]) {
|
||||
resetSecond()
|
||||
}
|
||||
|
||||
// 循环年份数组
|
||||
goYear: for (let Yi = YIdx; Yi < YDate.length; Yi++) {
|
||||
const YY = YDate[Yi]
|
||||
// 如果到达最大值时
|
||||
if (nMouth > MDate[MDate.length - 1]) {
|
||||
resetMouth()
|
||||
continue
|
||||
}
|
||||
// 循环月份数组
|
||||
goMouth: for (let Mi = MIdx; Mi < MDate.length; Mi++) {
|
||||
// 赋值、方便后面运算
|
||||
let MM = MDate[Mi]
|
||||
MM = MM < 10 ? '0' + MM : MM
|
||||
// 如果到达最大值时
|
||||
if (nDay > DDate[DDate.length - 1]) {
|
||||
resetDay()
|
||||
if (Mi == MDate.length - 1) {
|
||||
resetMouth()
|
||||
continue goYear
|
||||
}
|
||||
continue
|
||||
}
|
||||
// 循环日期数组
|
||||
goDay: for (let Di = DIdx; Di < DDate.length; Di++) {
|
||||
// 赋值、方便后面运算
|
||||
let DD = DDate[Di]
|
||||
let thisDD = DD < 10 ? '0' + DD : DD
|
||||
|
||||
// 如果到达最大值时
|
||||
if (nHour > hDate[hDate.length - 1]) {
|
||||
resetHour()
|
||||
if (Di == DDate.length - 1) {
|
||||
resetDay()
|
||||
if (Mi == MDate.length - 1) {
|
||||
resetMouth()
|
||||
continue goYear
|
||||
}
|
||||
continue goMouth
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 判断日期的合法性,不合法的话也是跳出当前循环
|
||||
if (
|
||||
this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true &&
|
||||
this.dayRule !== 'workDay' &&
|
||||
this.dayRule !== 'lastWeek' &&
|
||||
this.dayRule !== 'lastDay'
|
||||
) {
|
||||
resetDay()
|
||||
continue goMouth
|
||||
}
|
||||
// 如果日期规则中有值时
|
||||
if (this.dayRule == 'lastDay') {
|
||||
// 如果不是合法日期则需要将前将日期调到合法日期即月末最后一天
|
||||
|
||||
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
|
||||
while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
|
||||
DD--
|
||||
|
||||
thisDD = DD < 10 ? '0' + DD : DD
|
||||
}
|
||||
}
|
||||
} else if (this.dayRule == 'workDay') {
|
||||
// 校验并调整如果是2月30号这种日期传进来时需调整至正常月底
|
||||
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
|
||||
while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
|
||||
DD--
|
||||
thisDD = DD < 10 ? '0' + DD : DD
|
||||
}
|
||||
}
|
||||
// 获取达到条件的日期是星期X
|
||||
const thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week')
|
||||
// 当星期日时
|
||||
if (thisWeek == 0) {
|
||||
// 先找下一个日,并判断是否为月底
|
||||
DD++
|
||||
thisDD = DD < 10 ? '0' + DD : DD
|
||||
// 判断下一日已经不是合法日期
|
||||
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
|
||||
DD -= 3
|
||||
}
|
||||
} else if (thisWeek == 6) {
|
||||
// 当星期6时只需判断不是1号就可进行操作
|
||||
if (this.dayRuleSup !== 1) {
|
||||
DD--
|
||||
} else {
|
||||
DD += 2
|
||||
}
|
||||
}
|
||||
} else if (this.dayRule == 'weekDay') {
|
||||
// 如果指定了是星期几
|
||||
// 获取当前日期是属于星期几
|
||||
const thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week')
|
||||
// 校验当前星期是否在星期池(dayRuleSup)中
|
||||
if (!this.dayRuleSup.includes(thisWeek)) {
|
||||
// 如果到达最大值时
|
||||
if (Di == DDate.length - 1) {
|
||||
resetDay()
|
||||
if (Mi == MDate.length - 1) {
|
||||
resetMouth()
|
||||
continue goYear
|
||||
}
|
||||
continue goMouth
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else if (this.dayRule == 'assWeek') {
|
||||
// 如果指定了是第几周的星期几
|
||||
// 获取每月1号是属于星期几
|
||||
const thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week')
|
||||
if (this.dayRuleSup[1] >= thisWeek) {
|
||||
DD = (this.dayRuleSup[0] - 1) * 7 + this.dayRuleSup[1] - thisWeek + 1
|
||||
} else {
|
||||
DD = this.dayRuleSup[0] * 7 + this.dayRuleSup[1] - thisWeek + 1
|
||||
}
|
||||
} else if (this.dayRule == 'lastWeek') {
|
||||
// 如果指定了每月最后一个星期几
|
||||
// 校验并调整如果是2月30号这种日期传进来时需调整至正常月底
|
||||
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
|
||||
while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
|
||||
DD--
|
||||
thisDD = DD < 10 ? '0' + DD : DD
|
||||
}
|
||||
}
|
||||
// 获取月末最后一天是星期几
|
||||
const thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week')
|
||||
// 找到要求中最近的那个星期几
|
||||
if (this.dayRuleSup < thisWeek) {
|
||||
DD -= thisWeek - this.dayRuleSup
|
||||
} else if (this.dayRuleSup > thisWeek) {
|
||||
DD -= 7 - (this.dayRuleSup - thisWeek)
|
||||
}
|
||||
}
|
||||
// 判断时间值是否小于10置换成“05”这种格式
|
||||
DD = DD < 10 ? '0' + DD : DD
|
||||
|
||||
// 循环“时”数组
|
||||
goHour: for (let hi = hIdx; hi < hDate.length; hi++) {
|
||||
const hh = hDate[hi] < 10 ? '0' + hDate[hi] : hDate[hi]
|
||||
|
||||
// 如果到达最大值时
|
||||
if (nMin > mDate[mDate.length - 1]) {
|
||||
resetMin()
|
||||
if (hi == hDate.length - 1) {
|
||||
resetHour()
|
||||
if (Di == DDate.length - 1) {
|
||||
resetDay()
|
||||
if (Mi == MDate.length - 1) {
|
||||
resetMouth()
|
||||
continue goYear
|
||||
}
|
||||
continue goMouth
|
||||
}
|
||||
continue goDay
|
||||
}
|
||||
continue
|
||||
}
|
||||
// 循环"分"数组
|
||||
goMin: for (let mi = mIdx; mi < mDate.length; mi++) {
|
||||
const mm = mDate[mi] < 10 ? '0' + mDate[mi] : mDate[mi]
|
||||
|
||||
// 如果到达最大值时
|
||||
if (nSecond > sDate[sDate.length - 1]) {
|
||||
resetSecond()
|
||||
if (mi == mDate.length - 1) {
|
||||
resetMin()
|
||||
if (hi == hDate.length - 1) {
|
||||
resetHour()
|
||||
if (Di == DDate.length - 1) {
|
||||
resetDay()
|
||||
if (Mi == MDate.length - 1) {
|
||||
resetMouth()
|
||||
continue goYear
|
||||
}
|
||||
continue goMouth
|
||||
}
|
||||
continue goDay
|
||||
}
|
||||
continue goHour
|
||||
}
|
||||
continue
|
||||
}
|
||||
// 循环"秒"数组
|
||||
goSecond: for (let si = sIdx; si <= sDate.length - 1; si++) {
|
||||
const ss = sDate[si] < 10 ? '0' + sDate[si] : sDate[si]
|
||||
// 添加当前时间(时间合法性在日期循环时已经判断)
|
||||
if (MM !== '00' && DD !== '00') {
|
||||
resultArr.push(YY + '-' + MM + '-' + DD + ' ' + hh + ':' + mm + ':' + ss)
|
||||
nums++
|
||||
}
|
||||
// 如果条数满了就退出循环
|
||||
if (nums == 5) break goYear
|
||||
// 如果到达最大值时
|
||||
if (si == sDate.length - 1) {
|
||||
resetSecond()
|
||||
if (mi == mDate.length - 1) {
|
||||
resetMin()
|
||||
if (hi == hDate.length - 1) {
|
||||
resetHour()
|
||||
if (Di == DDate.length - 1) {
|
||||
resetDay()
|
||||
if (Mi == MDate.length - 1) {
|
||||
resetMouth()
|
||||
continue goYear
|
||||
}
|
||||
continue goMouth
|
||||
}
|
||||
continue goDay
|
||||
}
|
||||
continue goHour
|
||||
}
|
||||
continue goMin
|
||||
}
|
||||
} // goSecond
|
||||
} // goMin
|
||||
} // goHour
|
||||
} // goDay
|
||||
} // goMouth
|
||||
}
|
||||
// 判断100年内的结果条数
|
||||
if (resultArr.length == 0) {
|
||||
this.resultList = ['没有达到条件的结果!']
|
||||
} else {
|
||||
this.resultList = resultArr
|
||||
if (resultArr.length !== 5) {
|
||||
this.resultList.push('最近100年内只有上面' + resultArr.length + '条结果!')
|
||||
}
|
||||
}
|
||||
// 计算完成-显示结果
|
||||
this.isShow = true
|
||||
},
|
||||
// 用于计算某位数字在数组中的索引
|
||||
getIndex(arr, value) {
|
||||
if (value <= arr[0] || value > arr[arr.length - 1]) {
|
||||
return 0
|
||||
} else {
|
||||
for (let i = 0; i < arr.length - 1; i++) {
|
||||
if (value > arr[i] && value <= arr[i + 1]) {
|
||||
return i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取"年"数组
|
||||
getYearArr(rule, year) {
|
||||
this.dateArr[5] = this.getOrderArr(year, year + 100)
|
||||
if (rule !== undefined) {
|
||||
if (rule.indexOf('-') >= 0) {
|
||||
this.dateArr[5] = this.getCycleArr(rule, year + 100, false)
|
||||
} else if (rule.indexOf('/') >= 0) {
|
||||
this.dateArr[5] = this.getAverageArr(rule, year + 100)
|
||||
} else if (rule !== '*') {
|
||||
this.dateArr[5] = this.getAssignArr(rule)
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取"月"数组
|
||||
getMouthArr(rule) {
|
||||
this.dateArr[4] = this.getOrderArr(1, 12)
|
||||
if (rule.indexOf('-') >= 0) {
|
||||
this.dateArr[4] = this.getCycleArr(rule, 12, false)
|
||||
} else if (rule.indexOf('/') >= 0) {
|
||||
this.dateArr[4] = this.getAverageArr(rule, 12)
|
||||
} else if (rule !== '*') {
|
||||
this.dateArr[4] = this.getAssignArr(rule)
|
||||
}
|
||||
},
|
||||
// 获取"日"数组-主要为日期规则
|
||||
getWeekArr(rule) {
|
||||
// 只有当日期规则的两个值均为“”时则表达日期是有选项的
|
||||
if (this.dayRule == '' && this.dayRuleSup == '') {
|
||||
if (rule.indexOf('-') >= 0) {
|
||||
this.dayRule = 'weekDay'
|
||||
this.dayRuleSup = this.getCycleArr(rule, 7, false)
|
||||
} else if (rule.indexOf('#') >= 0) {
|
||||
this.dayRule = 'assWeek'
|
||||
const matchRule = rule.match(/[0-9]{1}/g)
|
||||
this.dayRuleSup = [Number(matchRule[0]), Number(matchRule[1])]
|
||||
this.dateArr[3] = [1]
|
||||
if (this.dayRuleSup[1] == 7) {
|
||||
this.dayRuleSup[1] = 0
|
||||
}
|
||||
} else if (rule.indexOf('L') >= 0) {
|
||||
this.dayRule = 'lastWeek'
|
||||
this.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0])
|
||||
this.dateArr[3] = [31]
|
||||
if (this.dayRuleSup == 7) {
|
||||
this.dayRuleSup = 0
|
||||
}
|
||||
} else if (rule !== '*' && rule !== '?') {
|
||||
this.dayRule = 'weekDay'
|
||||
this.dayRuleSup = this.getAssignArr(rule)
|
||||
}
|
||||
// 如果weekDay时将7调整为0【week值0即是星期日】
|
||||
if (this.dayRule == 'weekDay') {
|
||||
for (let i = 0; i < this.dayRuleSup.length; i++) {
|
||||
if (this.dayRuleSup[i] == 7) {
|
||||
this.dayRuleSup[i] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取"日"数组-少量为日期规则
|
||||
getDayArr(rule) {
|
||||
this.dateArr[3] = this.getOrderArr(1, 31)
|
||||
this.dayRule = ''
|
||||
this.dayRuleSup = ''
|
||||
if (rule.indexOf('-') >= 0) {
|
||||
this.dateArr[3] = this.getCycleArr(rule, 31, false)
|
||||
this.dayRuleSup = 'null'
|
||||
} else if (rule.indexOf('/') >= 0) {
|
||||
this.dateArr[3] = this.getAverageArr(rule, 31)
|
||||
this.dayRuleSup = 'null'
|
||||
} else if (rule.indexOf('W') >= 0) {
|
||||
this.dayRule = 'workDay'
|
||||
this.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0])
|
||||
this.dateArr[3] = [this.dayRuleSup]
|
||||
} else if (rule.indexOf('L') >= 0) {
|
||||
this.dayRule = 'lastDay'
|
||||
this.dayRuleSup = 'null'
|
||||
this.dateArr[3] = [31]
|
||||
} else if (rule !== '*' && rule !== '?') {
|
||||
this.dateArr[3] = this.getAssignArr(rule)
|
||||
this.dayRuleSup = 'null'
|
||||
} else if (rule == '*') {
|
||||
this.dayRuleSup = 'null'
|
||||
}
|
||||
},
|
||||
// 获取"时"数组
|
||||
getHourArr(rule) {
|
||||
this.dateArr[2] = this.getOrderArr(0, 23)
|
||||
if (rule.indexOf('-') >= 0) {
|
||||
this.dateArr[2] = this.getCycleArr(rule, 24, true)
|
||||
} else if (rule.indexOf('/') >= 0) {
|
||||
this.dateArr[2] = this.getAverageArr(rule, 23)
|
||||
} else if (rule !== '*') {
|
||||
this.dateArr[2] = this.getAssignArr(rule)
|
||||
}
|
||||
},
|
||||
// 获取"分"数组
|
||||
getMinArr(rule) {
|
||||
this.dateArr[1] = this.getOrderArr(0, 59)
|
||||
if (rule.indexOf('-') >= 0) {
|
||||
this.dateArr[1] = this.getCycleArr(rule, 60, true)
|
||||
} else if (rule.indexOf('/') >= 0) {
|
||||
this.dateArr[1] = this.getAverageArr(rule, 59)
|
||||
} else if (rule !== '*') {
|
||||
this.dateArr[1] = this.getAssignArr(rule)
|
||||
}
|
||||
},
|
||||
// 获取"秒"数组
|
||||
getSecondArr(rule) {
|
||||
this.dateArr[0] = this.getOrderArr(0, 59)
|
||||
if (rule.indexOf('-') >= 0) {
|
||||
this.dateArr[0] = this.getCycleArr(rule, 60, true)
|
||||
} else if (rule.indexOf('/') >= 0) {
|
||||
this.dateArr[0] = this.getAverageArr(rule, 59)
|
||||
} else if (rule !== '*') {
|
||||
this.dateArr[0] = this.getAssignArr(rule)
|
||||
}
|
||||
},
|
||||
// 根据传进来的min-max返回一个顺序的数组
|
||||
getOrderArr(min, max) {
|
||||
const arr = []
|
||||
for (let i = min; i <= max; i++) {
|
||||
arr.push(i)
|
||||
}
|
||||
return arr
|
||||
},
|
||||
// 根据规则中指定的零散值返回一个数组
|
||||
getAssignArr(rule) {
|
||||
const arr = []
|
||||
const assiginArr = rule.split(',')
|
||||
for (let i = 0; i < assiginArr.length; i++) {
|
||||
arr[i] = Number(assiginArr[i])
|
||||
}
|
||||
arr.sort(this.compare)
|
||||
return arr
|
||||
},
|
||||
// 根据一定算术规则计算返回一个数组
|
||||
getAverageArr(rule, limit) {
|
||||
const arr = []
|
||||
const agArr = rule.split('/')
|
||||
let min = Number(agArr[0])
|
||||
const step = Number(agArr[1])
|
||||
while (min <= limit) {
|
||||
arr.push(min)
|
||||
min += step
|
||||
}
|
||||
return arr
|
||||
},
|
||||
// 根据规则返回一个具有周期性的数组
|
||||
getCycleArr(rule, limit, status) {
|
||||
// status--表示是否从0开始(则从1开始)
|
||||
const arr = []
|
||||
const cycleArr = rule.split('-')
|
||||
const min = Number(cycleArr[0])
|
||||
let max = Number(cycleArr[1])
|
||||
if (min > max) {
|
||||
max += limit
|
||||
}
|
||||
for (let i = min; i <= max; i++) {
|
||||
let add = 0
|
||||
if (status == false && i % limit == 0) {
|
||||
add = limit
|
||||
}
|
||||
arr.push(Math.round((i % limit) + add))
|
||||
}
|
||||
arr.sort(this.compare)
|
||||
return arr
|
||||
},
|
||||
// 比较数字大小(用于Array.sort)
|
||||
compare(value1, value2) {
|
||||
if (value2 - value1 > 0) {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
},
|
||||
// 格式化日期格式如:2017-9-19 18:04:33
|
||||
formatDate(value, type) {
|
||||
// 计算日期相关值
|
||||
const time = typeof value === 'number' ? new Date(value) : value
|
||||
const Y = time.getFullYear()
|
||||
const M = time.getMonth() + 1
|
||||
const D = time.getDate()
|
||||
const h = time.getHours()
|
||||
const m = time.getMinutes()
|
||||
const s = time.getSeconds()
|
||||
const week = time.getDay()
|
||||
// 如果传递了type的话
|
||||
if (type == undefined) {
|
||||
return (
|
||||
Y +
|
||||
'-' +
|
||||
(M < 10 ? '0' + M : M) +
|
||||
'-' +
|
||||
(D < 10 ? '0' + D : D) +
|
||||
' ' +
|
||||
(h < 10 ? '0' + h : h) +
|
||||
':' +
|
||||
(m < 10 ? '0' + m : m) +
|
||||
':' +
|
||||
(s < 10 ? '0' + s : s)
|
||||
)
|
||||
} else if (type == 'week') {
|
||||
return week
|
||||
}
|
||||
},
|
||||
// 检查日期是否存在
|
||||
checkDate(value) {
|
||||
const time = new Date(value)
|
||||
const format = this.formatDate(time)
|
||||
return value == format
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
ex: 'expressionChange',
|
||||
},
|
||||
props: ['ex'],
|
||||
mounted: function() {
|
||||
// 初始化 获取一次结果
|
||||
this.expressionChange()
|
||||
},
|
||||
}
|
||||
</script>
|
133
cmdb-ui/src/components/Crontab/Crontab-Second.vue
Normal file
133
cmdb-ui/src/components/Crontab/Crontab-Second.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<el-form size="small">
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="1">
|
||||
秒,允许的通配符[, - * /]
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="2">
|
||||
周期从
|
||||
<el-input-number v-model="cycle01" :min="0" :max="60" /> -
|
||||
<el-input-number v-model="cycle02" :min="0" :max="60" /> 秒
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="3">
|
||||
从
|
||||
<el-input-number v-model="average01" :min="0" :max="60" /> 秒开始,每
|
||||
<el-input-number v-model="average02" :min="0" :max="60" /> 秒执行一次
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
指定
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||
<el-option v-for="item in 60" :key="item" :value="item - 1">{{ item - 1 }}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
radioValue: 1,
|
||||
cycle01: 1,
|
||||
cycle02: 2,
|
||||
average01: 0,
|
||||
average02: 1,
|
||||
checkboxList: [],
|
||||
checkNum: this.$options.propsData.check,
|
||||
}
|
||||
},
|
||||
name: 'CrontabSecond',
|
||||
props: ['check', 'radioParent'],
|
||||
methods: {
|
||||
// 单选按钮值变化时
|
||||
radioChange() {
|
||||
switch (this.radioValue) {
|
||||
case 1:
|
||||
this.$emit('update', 'second', '*', 'second')
|
||||
this.$emit('update', 'min', '*', 'second')
|
||||
break
|
||||
case 2:
|
||||
this.$emit('update', 'second', this.cycle01 + '-' + this.cycle02)
|
||||
break
|
||||
case 3:
|
||||
this.$emit('update', 'second', this.average01 + '/' + this.average02)
|
||||
break
|
||||
case 4:
|
||||
this.$emit('update', 'second', this.checkboxString)
|
||||
break
|
||||
}
|
||||
},
|
||||
// 周期两个值变化时
|
||||
cycleChange() {
|
||||
if (this.radioValue == '2') {
|
||||
this.$emit('update', 'second', this.cycleTotal)
|
||||
}
|
||||
},
|
||||
// 平均两个值变化时
|
||||
averageChange() {
|
||||
if (this.radioValue == '3') {
|
||||
this.$emit('update', 'second', this.averageTotal)
|
||||
}
|
||||
},
|
||||
// checkbox值变化时
|
||||
checkboxChange() {
|
||||
if (this.radioValue == '4') {
|
||||
this.$emit('update', 'second', this.checkboxString)
|
||||
}
|
||||
},
|
||||
othChange() {
|
||||
// 反解析
|
||||
const ins = this.cron.second('反解析 second', ins)
|
||||
if (ins === '*') {
|
||||
this.radioValue = 1
|
||||
} else if (ins.indexOf('-') > -1) {
|
||||
this.radioValue = 2
|
||||
} else if (ins.indexOf('/') > -1) {
|
||||
this.radioValue = 3
|
||||
} else {
|
||||
this.radioValue = 4
|
||||
this.checkboxList = ins.split(',')
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
radioValue: 'radioChange',
|
||||
cycleTotal: 'cycleChange',
|
||||
averageTotal: 'averageChange',
|
||||
checkboxString: 'checkboxChange',
|
||||
radioParent() {
|
||||
this.radioValue = this.radioParent
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 计算两个周期值
|
||||
cycleTotal: function() {
|
||||
this.cycle01 = this.checkNum(this.cycle01, 0, 59)
|
||||
this.cycle02 = this.checkNum(this.cycle02, 0, 59)
|
||||
return this.cycle01 + '-' + this.cycle02
|
||||
},
|
||||
// 计算平均用到的值
|
||||
averageTotal: function() {
|
||||
this.average01 = this.checkNum(this.average01, 0, 59)
|
||||
this.average02 = this.checkNum(this.average02, 1, 59)
|
||||
return this.average01 + '/' + this.average02
|
||||
},
|
||||
// 计算勾选的checkbox值合集
|
||||
checkboxString: function() {
|
||||
const str = this.checkboxList.join()
|
||||
return str == '' ? '*' : str
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
154
cmdb-ui/src/components/Crontab/Crontab-Week.vue
Normal file
154
cmdb-ui/src/components/Crontab/Crontab-Week.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<el-form size="small">
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="1">
|
||||
周,允许的通配符[, - * / L #]
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="2">
|
||||
不指定
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="3">
|
||||
周期从星期
|
||||
<el-input-number v-model="cycle01" :min="1" :max="7" /> -
|
||||
<el-input-number v-model="cycle02" :min="1" :max="7" />
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
第
|
||||
<el-input-number v-model="average01" :min="1" :max="4" /> 周的星期
|
||||
<el-input-number v-model="average02" :min="1" :max="7" />
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="5">
|
||||
本月最后一个星期
|
||||
<el-input-number v-model="weekday" :min="1" :max="7" />
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="6">
|
||||
指定
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||
<el-option v-for="(item, index) of weekList" :key="index" :value="index + 1">{{ item }}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
radioValue: 2,
|
||||
weekday: 1,
|
||||
cycle01: 1,
|
||||
cycle02: 2,
|
||||
average01: 1,
|
||||
average02: 1,
|
||||
checkboxList: [],
|
||||
weekList: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||
checkNum: this.$options.propsData.check,
|
||||
}
|
||||
},
|
||||
name: 'CrontabWeek',
|
||||
props: ['check', 'cron'],
|
||||
methods: {
|
||||
// 单选按钮值变化时
|
||||
radioChange() {
|
||||
if (this.radioValue !== 2) {
|
||||
this.$emit('update', 'day', '?')
|
||||
}
|
||||
switch (this.radioValue) {
|
||||
case 1:
|
||||
this.$emit('update', 'week', '*')
|
||||
break
|
||||
case 2:
|
||||
this.$emit('update', 'week', '?')
|
||||
this.$emit('update', 'day', '*')
|
||||
break
|
||||
case 3:
|
||||
this.$emit('update', 'week', this.cycle01 + '-' + this.cycle02)
|
||||
break
|
||||
case 4:
|
||||
this.$emit('update', 'week', this.average01 + '#' + this.average02)
|
||||
break
|
||||
case 5:
|
||||
this.$emit('update', 'week', this.weekday + 'L')
|
||||
break
|
||||
case 6:
|
||||
this.$emit('update', 'week', this.checkboxString)
|
||||
break
|
||||
}
|
||||
},
|
||||
// 根据互斥事件,更改radio的值
|
||||
|
||||
// 周期两个值变化时
|
||||
cycleChange() {
|
||||
if (this.radioValue == '3') {
|
||||
this.$emit('update', 'week', this.cycleTotal)
|
||||
}
|
||||
},
|
||||
// 平均两个值变化时
|
||||
averageChange() {
|
||||
if (this.radioValue == '4') {
|
||||
this.$emit('update', 'week', this.averageTotal)
|
||||
}
|
||||
},
|
||||
// 最近工作日值变化时
|
||||
weekdayChange() {
|
||||
if (this.radioValue == '5') {
|
||||
this.$emit('update', 'week', this.weekday + 'L')
|
||||
}
|
||||
},
|
||||
// checkbox值变化时
|
||||
checkboxChange() {
|
||||
if (this.radioValue == '6') {
|
||||
this.$emit('update', 'week', this.checkboxString)
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
radioValue: 'radioChange',
|
||||
cycleTotal: 'cycleChange',
|
||||
averageTotal: 'averageChange',
|
||||
weekdayCheck: 'weekdayChange',
|
||||
checkboxString: 'checkboxChange',
|
||||
},
|
||||
computed: {
|
||||
// 计算两个周期值
|
||||
cycleTotal: function() {
|
||||
this.cycle01 = this.checkNum(this.cycle01, 1, 7)
|
||||
this.cycle02 = this.checkNum(this.cycle02, 1, 7)
|
||||
return this.cycle01 + '-' + this.cycle02
|
||||
},
|
||||
// 计算平均用到的值
|
||||
averageTotal: function() {
|
||||
this.average01 = this.checkNum(this.average01, 1, 4)
|
||||
this.average02 = this.checkNum(this.average02, 1, 7)
|
||||
return this.average01 + '#' + this.average02
|
||||
},
|
||||
// 最近的工作日(格式)
|
||||
weekdayCheck: function() {
|
||||
this.weekday = this.checkNum(this.weekday, 1, 7)
|
||||
return this.weekday
|
||||
},
|
||||
// 计算勾选的checkbox值合集
|
||||
checkboxString: function() {
|
||||
const str = this.checkboxList.join()
|
||||
return str == '' ? '*' : str
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
144
cmdb-ui/src/components/Crontab/Crontab-Year.vue
Normal file
144
cmdb-ui/src/components/Crontab/Crontab-Year.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<el-form size="small">
|
||||
<el-form-item>
|
||||
<el-radio :label="1" v-model="radioValue">
|
||||
不填,允许的通配符[, - * /]
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio :label="2" v-model="radioValue">
|
||||
每年
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio :label="3" v-model="radioValue">
|
||||
周期从
|
||||
<el-input-number v-model="cycle01" :min="fullYear" /> -
|
||||
<el-input-number v-model="cycle02" :min="fullYear" />
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio :label="4" v-model="radioValue">
|
||||
从
|
||||
<el-input-number v-model="average01" :min="fullYear" /> 年开始,每
|
||||
<el-input-number v-model="average02" :min="fullYear" /> 年执行一次
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio :label="5" v-model="radioValue">
|
||||
指定
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple>
|
||||
<el-option v-for="item in 9" :key="item" :value="item - 1 + fullYear" :label="item - 1 + fullYear" />
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
fullYear: 0,
|
||||
radioValue: 1,
|
||||
cycle01: 0,
|
||||
cycle02: 0,
|
||||
average01: 0,
|
||||
average02: 1,
|
||||
checkboxList: [],
|
||||
checkNum: this.$options.propsData.check,
|
||||
}
|
||||
},
|
||||
name: 'CrontabYear',
|
||||
props: ['check', 'mouth', 'cron'],
|
||||
methods: {
|
||||
// 单选按钮值变化时
|
||||
radioChange() {
|
||||
if (this.cron.mouth === '*') {
|
||||
this.$emit('update', 'mouth', '0', 'year')
|
||||
}
|
||||
if (this.cron.day === '*') {
|
||||
this.$emit('update', 'day', '0', 'year')
|
||||
}
|
||||
if (this.cron.hour === '*') {
|
||||
this.$emit('update', 'hour', '0', 'year')
|
||||
}
|
||||
if (this.cron.min === '*') {
|
||||
this.$emit('update', 'min', '0', 'year')
|
||||
}
|
||||
if (this.cron.second === '*') {
|
||||
this.$emit('update', 'second', '0', 'year')
|
||||
}
|
||||
switch (this.radioValue) {
|
||||
case 1:
|
||||
this.$emit('update', 'year', '')
|
||||
break
|
||||
case 2:
|
||||
this.$emit('update', 'year', '*')
|
||||
break
|
||||
case 3:
|
||||
this.$emit('update', 'year', this.cycle01 + '-' + this.cycle02)
|
||||
break
|
||||
case 4:
|
||||
this.$emit('update', 'year', this.average01 + '/' + this.average02)
|
||||
break
|
||||
case 5:
|
||||
this.$emit('update', 'year', this.checkboxString)
|
||||
break
|
||||
}
|
||||
},
|
||||
// 周期两个值变化时
|
||||
cycleChange() {
|
||||
if (this.radioValue == '3') {
|
||||
this.$emit('update', 'year', this.cycleTotal)
|
||||
}
|
||||
},
|
||||
// 平均两个值变化时
|
||||
averageChange() {
|
||||
if (this.radioValue == '4') {
|
||||
this.$emit('update', 'year', this.averageTotal)
|
||||
}
|
||||
},
|
||||
// checkbox值变化时
|
||||
checkboxChange() {
|
||||
if (this.radioValue == '5') {
|
||||
this.$emit('update', 'year', this.checkboxString)
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
radioValue: 'radioChange',
|
||||
cycleTotal: 'cycleChange',
|
||||
averageTotal: 'averageChange',
|
||||
checkboxString: 'checkboxChange',
|
||||
},
|
||||
computed: {
|
||||
// 计算两个周期值
|
||||
cycleTotal: function() {
|
||||
this.cycle01 = this.checkNum(this.cycle01, this.fullYear, this.fullYear + 100)
|
||||
this.cycle02 = this.checkNum(this.cycle02, this.fullYear + 1, this.fullYear + 101)
|
||||
return this.cycle01 + '-' + this.cycle02
|
||||
},
|
||||
// 计算平均用到的值
|
||||
averageTotal: function() {
|
||||
this.average01 = this.checkNum(this.average01, this.fullYear, this.fullYear + 100)
|
||||
this.average02 = this.checkNum(this.average02, 1, 10)
|
||||
return this.average01 + '/' + this.average02
|
||||
},
|
||||
// 计算勾选的checkbox值合集
|
||||
checkboxString: function() {
|
||||
const str = this.checkboxList.join()
|
||||
return str
|
||||
},
|
||||
},
|
||||
mounted: function() {
|
||||
// 仅获取当前年份
|
||||
this.fullYear = Number(new Date().getFullYear())
|
||||
},
|
||||
}
|
||||
</script>
|
408
cmdb-ui/src/components/Crontab/Crontab.vue
Normal file
408
cmdb-ui/src/components/Crontab/Crontab.vue
Normal file
@@ -0,0 +1,408 @@
|
||||
<template>
|
||||
<div :style="{ width: '490px' }">
|
||||
<el-tabs type="card" class="ops-crontab">
|
||||
<el-tab-pane label="秒" v-if="shouldHide('second')">
|
||||
<CrontabSecond @update="updateContabValue" :check="checkNumber" ref="cronsecond" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="分钟" v-if="shouldHide('min')">
|
||||
<CrontabMin @update="updateContabValue" :check="checkNumber" :cron="contabValueObj" ref="cronmin" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="小时" v-if="shouldHide('hour')">
|
||||
<CrontabHour @update="updateContabValue" :check="checkNumber" :cron="contabValueObj" ref="cronhour" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="日" v-if="shouldHide('day')">
|
||||
<CrontabDay @update="updateContabValue" :check="checkNumber" :cron="contabValueObj" ref="cronday" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="月" v-if="shouldHide('mouth')">
|
||||
<CrontabMouth @update="updateContabValue" :check="checkNumber" :cron="contabValueObj" ref="cronmouth" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="周" v-if="shouldHide('week')">
|
||||
<CrontabWeek @update="updateContabValue" :check="checkNumber" :cron="contabValueObj" ref="cronweek" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="年" v-if="shouldHide('year')">
|
||||
<CrontabYear @update="updateContabValue" :check="checkNumber" :cron="contabValueObj" ref="cronyear" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="popup-main">
|
||||
<div class="popup-result">
|
||||
<p class="title">时间表达式</p>
|
||||
<div style="padding: 12px;">
|
||||
<div></div>
|
||||
<table>
|
||||
<thead>
|
||||
<th v-for="item of displayTabTitles" width="40" :key="item.value">{{ item.label }}</th>
|
||||
<th>crontab完整表达式</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<td v-if="shouldHide('second')">
|
||||
<span class="square">{{ contabValueObj.second }}</span>
|
||||
</td>
|
||||
<td v-if="shouldHide('min')">
|
||||
<span class="square">{{ contabValueObj.min }}</span>
|
||||
</td>
|
||||
<td v-if="shouldHide('hour')">
|
||||
<span class="square">{{ contabValueObj.hour }}</span>
|
||||
</td>
|
||||
<td v-if="shouldHide('day')">
|
||||
<span class="square">{{ contabValueObj.day === '?' ? '*' : contabValueObj.day }}</span>
|
||||
</td>
|
||||
<td v-if="shouldHide('mouth')">
|
||||
<span class="square">{{ contabValueObj.mouth }}</span>
|
||||
</td>
|
||||
<td v-if="shouldHide('week')">
|
||||
<span class="square">{{ contabValueObj.week === '?' ? '*' : contabValueObj.week }}</span>
|
||||
</td>
|
||||
<td v-if="shouldHide('year')">
|
||||
<span class="square">{{ contabValueObj.year }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="rectangle">{{ displayContabValueString }}</span>
|
||||
</td>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <CrontabResult :ex="contabValueString"></CrontabResult> -->
|
||||
</div>
|
||||
<div class="pop_btn" v-if="hasFooter">
|
||||
<a-space>
|
||||
<a-button size="small" type="primary" @click="submitFill">确定</a-button>
|
||||
<a-button size="small" type="warning" @click="clearCron">重置</a-button>
|
||||
<a-button size="small" @click="hidePopup">取消</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
import CrontabSecond from './Crontab-Second.vue'
|
||||
import CrontabMin from './Crontab-Min.vue'
|
||||
import CrontabHour from './Crontab-Hour.vue'
|
||||
import CrontabDay from './Crontab-Day.vue'
|
||||
import CrontabMouth from './Crontab-Mouth.vue'
|
||||
import CrontabWeek from './Crontab-Week.vue'
|
||||
import CrontabYear from './Crontab-Year.vue'
|
||||
import CrontabResult from './Crontab-Result.vue'
|
||||
import { cronValidate } from './utils/index'
|
||||
// 对表达式进行特异化处理 不展示? 但是计算的时候还是有?的
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tabTitles: [
|
||||
{ value: 'second', label: '秒' },
|
||||
{ value: 'min', label: '分钟' },
|
||||
{ value: 'hour', label: '小时' },
|
||||
{ value: 'day', label: '日' },
|
||||
{ value: 'month', label: '月' },
|
||||
{ value: 'week', label: '周' },
|
||||
{ value: 'year', label: '年' },
|
||||
],
|
||||
tabActive: 0,
|
||||
myindex: 0,
|
||||
contabValueObj: {
|
||||
second: '*',
|
||||
min: '*',
|
||||
hour: '*',
|
||||
day: '*',
|
||||
mouth: '*',
|
||||
week: '?',
|
||||
year: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
name: 'Vcrontab',
|
||||
props: ['expression', 'hideComponent', 'defaultExpression', 'hasFooter'],
|
||||
methods: {
|
||||
shouldHide(key) {
|
||||
if (this.hideComponent && this.hideComponent.includes(key)) return false
|
||||
return true
|
||||
},
|
||||
resolveExp(expression) {
|
||||
// 反解析 表达式
|
||||
if (expression) {
|
||||
const arr = expression.split(' ')
|
||||
if (arr.length >= 6) {
|
||||
// 6 位以上是合法表达式
|
||||
const obj = {
|
||||
second: arr[0],
|
||||
min: arr[1],
|
||||
hour: arr[2],
|
||||
day: arr[3],
|
||||
mouth: arr[4],
|
||||
week: arr[5],
|
||||
year: arr[6] ? arr[6] : '',
|
||||
}
|
||||
this.contabValueObj = {
|
||||
...obj,
|
||||
}
|
||||
for (const i in obj) {
|
||||
if (obj[i]) this.changeRadio(i, obj[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// tab切换值
|
||||
tabCheck(index) {
|
||||
this.tabActive = index
|
||||
},
|
||||
// 由子组件触发,更改表达式组成的字段值
|
||||
updateContabValue(name, value, from) {
|
||||
'updateContabValue', name, value, from
|
||||
this.$set(this.contabValueObj, name, value)
|
||||
if (from && from !== name) {
|
||||
// console.log(`来自组件 ${from} 改变了 ${name} ${value}`);
|
||||
this.changeRadio(name, value)
|
||||
}
|
||||
},
|
||||
// 赋值到组件
|
||||
changeRadio(name, value) {
|
||||
const arr = ['second', 'min', 'hour', 'mouth']
|
||||
const refName = 'cron' + name
|
||||
let insVlaue
|
||||
if (!this.$refs[refName]) return
|
||||
|
||||
if (arr.includes(name)) {
|
||||
if (value === '*') {
|
||||
insVlaue = 1
|
||||
} else if (value.indexOf('-') > -1) {
|
||||
const indexArr = value.split('-')
|
||||
isNaN(indexArr[0]) ? (this.$refs[refName].cycle01 = 0) : (this.$refs[refName].cycle01 = indexArr[0])
|
||||
this.$refs[refName].cycle02 = indexArr[1]
|
||||
insVlaue = 2
|
||||
} else if (value.indexOf('/') > -1) {
|
||||
const indexArr = value.split('/')
|
||||
isNaN(indexArr[0]) ? (this.$refs[refName].average01 = 0) : (this.$refs[refName].average01 = indexArr[0])
|
||||
this.$refs[refName].average02 = indexArr[1]
|
||||
insVlaue = 3
|
||||
} else {
|
||||
insVlaue = 4
|
||||
this.$refs[refName].checkboxList = value.split(',').map((v) => Number(v))
|
||||
}
|
||||
} else if (name == 'day') {
|
||||
if (value === '*') {
|
||||
insVlaue = 1
|
||||
} else if (value == '?') {
|
||||
insVlaue = 2
|
||||
} else if (value.indexOf('-') > -1) {
|
||||
const indexArr = value.split('-')
|
||||
isNaN(indexArr[0]) ? (this.$refs[refName].cycle01 = 0) : (this.$refs[refName].cycle01 = indexArr[0])
|
||||
this.$refs[refName].cycle02 = indexArr[1]
|
||||
insVlaue = 3
|
||||
} else if (value.indexOf('/') > -1) {
|
||||
const indexArr = value.split('/')
|
||||
isNaN(indexArr[0]) ? (this.$refs[refName].average01 = 0) : (this.$refs[refName].average01 = indexArr[0])
|
||||
this.$refs[refName].average02 = indexArr[1]
|
||||
insVlaue = 4
|
||||
} else if (value.indexOf('W') > -1) {
|
||||
const indexArr = value.split('W')
|
||||
isNaN(indexArr[0]) ? (this.$refs[refName].workday = 0) : (this.$refs[refName].workday = indexArr[0])
|
||||
insVlaue = 5
|
||||
} else if (value === 'L') {
|
||||
insVlaue = 6
|
||||
} else {
|
||||
this.$refs[refName].checkboxList = value.split(',')
|
||||
insVlaue = 7
|
||||
}
|
||||
} else if (name == 'week') {
|
||||
if (value === '*') {
|
||||
insVlaue = 1
|
||||
} else if (value == '?') {
|
||||
insVlaue = 2
|
||||
} else if (value.indexOf('-') > -1) {
|
||||
const indexArr = value.split('-')
|
||||
isNaN(indexArr[0]) ? (this.$refs[refName].cycle01 = 0) : (this.$refs[refName].cycle01 = indexArr[0])
|
||||
this.$refs[refName].cycle02 = indexArr[1]
|
||||
insVlaue = 3
|
||||
} else if (value.indexOf('#') > -1) {
|
||||
const indexArr = value.split('#')
|
||||
isNaN(indexArr[0]) ? (this.$refs[refName].average01 = 1) : (this.$refs[refName].average01 = indexArr[0])
|
||||
this.$refs[refName].average02 = indexArr[1]
|
||||
insVlaue = 4
|
||||
} else if (value.indexOf('L') > -1) {
|
||||
const indexArr = value.split('L')
|
||||
isNaN(indexArr[0]) ? (this.$refs[refName].weekday = 1) : (this.$refs[refName].weekday = indexArr[0])
|
||||
insVlaue = 5
|
||||
} else {
|
||||
this.$refs[refName].checkboxList = value.split(',')
|
||||
insVlaue = 6
|
||||
}
|
||||
} else if (name == 'year') {
|
||||
if (value == '') {
|
||||
insVlaue = 1
|
||||
} else if (value == '*') {
|
||||
insVlaue = 2
|
||||
} else if (value.indexOf('-') > -1) {
|
||||
insVlaue = 3
|
||||
} else if (value.indexOf('/') > -1) {
|
||||
insVlaue = 4
|
||||
} else {
|
||||
this.$refs[refName].checkboxList = value.split(',')
|
||||
insVlaue = 5
|
||||
}
|
||||
}
|
||||
this.$refs[refName].radioValue = insVlaue
|
||||
},
|
||||
// 表单选项的子组件校验数字格式(通过-props传递)
|
||||
checkNumber(value, minLimit, maxLimit) {
|
||||
// 检查必须为整数
|
||||
value = Math.floor(value)
|
||||
if (value < minLimit) {
|
||||
value = minLimit
|
||||
} else if (value > maxLimit) {
|
||||
value = maxLimit
|
||||
}
|
||||
return value
|
||||
},
|
||||
// 隐藏弹窗
|
||||
hidePopup() {
|
||||
this.$emit('hide')
|
||||
},
|
||||
// 填充表达式
|
||||
submitFill() {
|
||||
const result = cronValidate(this.contabValueString)
|
||||
console.log(result)
|
||||
if (typeof result !== 'boolean') {
|
||||
this.$message.warning(result)
|
||||
return this.$emit('error', result)
|
||||
}
|
||||
this.$emit('fill', this.displayContabValueString)
|
||||
this.hidePopup()
|
||||
},
|
||||
clearCron() {
|
||||
// 还原选择项
|
||||
this.resolveExp(this.defaultExpression || '* * * * * ?')
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
contabValueString: function() {
|
||||
const obj = this.contabValueObj
|
||||
const str =
|
||||
obj.second +
|
||||
' ' +
|
||||
obj.min +
|
||||
' ' +
|
||||
obj.hour +
|
||||
' ' +
|
||||
obj.day +
|
||||
' ' +
|
||||
obj.mouth +
|
||||
' ' +
|
||||
obj.week +
|
||||
(obj.year == '' ? '' : ' ' + obj.year)
|
||||
return str
|
||||
},
|
||||
displayContabValueString() {
|
||||
//去掉第一位秒,?改成 * 仅作展示用
|
||||
const _temp = this.contabValueString.substring(2)
|
||||
const reg = /\?/g
|
||||
return _temp.replace(reg, '*')
|
||||
},
|
||||
displayTabTitles() {
|
||||
return this.tabTitles.filter((item) => !this.hideComponent.includes(item.value))
|
||||
},
|
||||
},
|
||||
components: {
|
||||
CrontabSecond,
|
||||
CrontabMin,
|
||||
CrontabHour,
|
||||
CrontabDay,
|
||||
CrontabMouth,
|
||||
CrontabWeek,
|
||||
CrontabYear,
|
||||
CrontabResult,
|
||||
},
|
||||
watch: {
|
||||
expression: {
|
||||
handler(val) {
|
||||
if (!val) {
|
||||
this.clearCron()
|
||||
return
|
||||
}
|
||||
this.resolveExp(val)
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// 初始化
|
||||
if (this.expression) {
|
||||
this.resolveExp(this.expression)
|
||||
} else {
|
||||
this.clearCron()
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.pop_btn {
|
||||
text-align: right;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.popup-main {
|
||||
position: relative;
|
||||
margin: 16px auto;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 8px 16px rgba(160, 181, 235, 0.25);
|
||||
}
|
||||
.popup-title {
|
||||
overflow: hidden;
|
||||
line-height: 34px;
|
||||
padding-top: 6px;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
.popup-result {
|
||||
border-radius: 8px;
|
||||
}
|
||||
.popup-result .title {
|
||||
background: #fff;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #2f54eb;
|
||||
background-color: #f0f5ff;
|
||||
margin: 0px;
|
||||
box-sizing: border-box;
|
||||
padding-left: 12px;
|
||||
}
|
||||
.popup-result table {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.popup-result table span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-family: arial;
|
||||
line-height: 26px;
|
||||
height: 26px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.popup-result table span.square {
|
||||
width: 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.popup-result table span.rectangle {
|
||||
width: 247px;
|
||||
}
|
||||
.popup-result-scroll {
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
height: 10em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
2
cmdb-ui/src/components/Crontab/index.js
Normal file
2
cmdb-ui/src/components/Crontab/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import Vcrontab from './Crontab.vue'
|
||||
export default Vcrontab
|
441
cmdb-ui/src/components/Crontab/utils/index.js
Normal file
441
cmdb-ui/src/components/Crontab/utils/index.js
Normal file
@@ -0,0 +1,441 @@
|
||||
/* eslint-disable */
|
||||
/*
|
||||
!!!!!!!
|
||||
以下为凶残的cron表达式验证,胆小肾虚及心脏病者慎入!!!
|
||||
不听劝告者后果自负T T
|
||||
!!!!!!!
|
||||
cron表达式为秒,分,时,日,月,周,年
|
||||
判断正误方法:错误的话返回错误信息,正确的话返回true
|
||||
*/
|
||||
export function cronValidate(cronExpression ){
|
||||
//返回错误信息用
|
||||
var message = '';
|
||||
//先将cron表达式进行分割
|
||||
var cronParams = cronExpression.split(" ");
|
||||
//判断cron表达式是否具有该具有的属性长度,没有年份的长度为6,带年份的长度为7,其他情况都是错误的
|
||||
if (cronParams.length < 6 || cronParams.length > 7) {
|
||||
return "cron表达式需要输入6-7位参数,请重新输入";
|
||||
}else{
|
||||
//日和周必须有一个为?,或者全为*
|
||||
if((cronParams[3] == "?" && cronParams[5] != "?") || (cronParams[5] == "?" && cronParams[3] != "?") || (cronParams[3] == "*" && cronParams[5] == "*")){
|
||||
//检查第一位的秒是否正确
|
||||
message = checkSecondsField(cronParams[0]);
|
||||
if (message != true) {
|
||||
return message;
|
||||
}
|
||||
|
||||
//检查第二位的分是否正确
|
||||
message = checkMinutesField(cronParams[1]);
|
||||
if (message != true) {
|
||||
return message;
|
||||
}
|
||||
|
||||
//检查第三位的时是否正确
|
||||
message = checkHoursField(cronParams[2]);
|
||||
if (message != true) {
|
||||
return message;
|
||||
}
|
||||
|
||||
//检查第四位的日是否正确
|
||||
message = checkDayOfMonthField(cronParams[3]);
|
||||
if (message != true) {
|
||||
return message;
|
||||
}
|
||||
|
||||
//检查第五位的月是否正确
|
||||
message = checkMonthsField(cronParams[4]);
|
||||
if (message != true) {
|
||||
return message;
|
||||
}
|
||||
|
||||
//检查第6位的周是否正确
|
||||
message = checkDayOfWeekField(cronParams[5]);
|
||||
if (message != true) {
|
||||
return message;
|
||||
}
|
||||
|
||||
//检查第七位的年是否正确
|
||||
if(cronParams.length>6){
|
||||
message = checkYearField(cronParams[6]);
|
||||
if (message != true) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}else{
|
||||
return "指定日时周必须设为不指定(?),指定周时日必须设为不指定(?)"
|
||||
}
|
||||
}
|
||||
}
|
||||
let message = ''
|
||||
//检查秒的函数方法
|
||||
function checkSecondsField(secondsField) {
|
||||
return checkField(secondsField, 0, 59, "秒");
|
||||
}
|
||||
|
||||
//检查分的函数方法
|
||||
function checkMinutesField(minutesField) {
|
||||
return checkField(minutesField, 0, 59, "分");
|
||||
}
|
||||
|
||||
//检查小时的函数方法
|
||||
function checkHoursField(hoursField) {
|
||||
return checkField(hoursField, 0, 23, "时");
|
||||
}
|
||||
|
||||
//检查日期的函数方法
|
||||
function checkDayOfMonthField(dayOfMonthField) {
|
||||
if (dayOfMonthField == "?") {
|
||||
return true;
|
||||
}
|
||||
if (dayOfMonthField.indexOf("L") >= 0) {
|
||||
return checkFieldWithLetter(dayOfMonthField, "L", 1, 7, "日");
|
||||
} else if ( dayOfMonthField.indexOf("W") >= 0) {
|
||||
return checkFieldWithLetter(dayOfMonthField, "W", 1, 31, "日");
|
||||
} else if (dayOfMonthField.indexOf("C") >= 0) {
|
||||
return checkFieldWithLetter(dayOfMonthField, "C", 1, 31, "日");
|
||||
}
|
||||
return checkField( dayOfMonthField, 1, 31, "日");
|
||||
}
|
||||
|
||||
//检查月份的函数方法
|
||||
function checkMonthsField(monthsField) {
|
||||
//月份简写处理
|
||||
if(monthsField != "*"){
|
||||
monthsField=monthsField.replace("JAN", "1");
|
||||
monthsField=monthsField.replace("FEB", "2");
|
||||
monthsField=monthsField.replace("MAR", "3");
|
||||
monthsField=monthsField.replace("APR", "4");
|
||||
monthsField=monthsField.replace("MAY", "5");
|
||||
monthsField=monthsField.replace("JUN", "6");
|
||||
monthsField=monthsField.replace("JUL", "7");
|
||||
monthsField=monthsField.replace("AUG", "8");
|
||||
monthsField=monthsField.replace("SEP", "9");
|
||||
monthsField=monthsField.replace("OCT", "10");
|
||||
monthsField=monthsField.replace("NOV", "11");
|
||||
monthsField=monthsField.replace("DEC", "12");
|
||||
return checkField(monthsField, 1, 12, "月份");
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//星期验证
|
||||
function checkDayOfWeekField(dayOfWeekField) {
|
||||
dayOfWeekField=dayOfWeekField.replace("SUN", "1" );
|
||||
dayOfWeekField=dayOfWeekField.replace("MON", "2" );
|
||||
dayOfWeekField=dayOfWeekField.replace("TUE", "3" );
|
||||
dayOfWeekField=dayOfWeekField.replace("WED", "4" );
|
||||
dayOfWeekField=dayOfWeekField.replace("THU", "5" );
|
||||
dayOfWeekField=dayOfWeekField.replace("FRI", "6" );
|
||||
dayOfWeekField=dayOfWeekField.replace("SAT", "7" );
|
||||
if (dayOfWeekField == "?") {
|
||||
return true;
|
||||
}
|
||||
if (dayOfWeekField.indexOf("L") >= 0) {
|
||||
return checkFieldWithLetterWeek(dayOfWeekField, "L", 1, 7, "星期");
|
||||
} else if (dayOfWeekField.indexOf("C") >= 0) {
|
||||
return checkFieldWithLetterWeek(dayOfWeekField, "C", 1, 7, "星期");
|
||||
} else if (dayOfWeekField.indexOf("#") >= 0) {
|
||||
return checkFieldWithLetterWeek(dayOfWeekField, "#", 1, 7, "星期");
|
||||
} else {
|
||||
return checkField(dayOfWeekField, 1, 7, "星期");
|
||||
}
|
||||
}
|
||||
|
||||
//检查年份的函数方法
|
||||
function checkYearField(yearField) {
|
||||
return checkField(yearField, 1970, 2099, "年的");
|
||||
}
|
||||
|
||||
//通用的检查值的大小范围的方法( - , / *)
|
||||
function checkField(value, minimal, maximal, attribute) {
|
||||
//校验值中是否有“-”,如果有“-”的话,下标会>0
|
||||
if (value.indexOf("-") > -1 ) {
|
||||
return checkRangeAndCycle(value, minimal, maximal,attribute);
|
||||
}
|
||||
//校验值中是否有“,”,如果有“,”的话,下标会>0
|
||||
else if (value.indexOf(",") > -1) {
|
||||
return checkListField(value, minimal, maximal,attribute);
|
||||
}
|
||||
//校验值中是否有“/”,如果有“/”的话,下标会>0
|
||||
else if (value.indexOf( "/" ) > -1) {
|
||||
return checkIncrementField( value, minimal, maximal ,attribute);
|
||||
}
|
||||
//校验值是否为“*”
|
||||
else if (value=="*") {
|
||||
return true;
|
||||
}
|
||||
//校验单独的数字,英文字母,以及各种神奇的符号等...
|
||||
else {
|
||||
return checkIntValue(value, minimal, maximal,true, attribute);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//检测是否是整数以及是否在范围内,参数:检测的值,下限,上限,是否检查端点,检查的属性
|
||||
function checkIntValue(value, minimal, maximal, checkExtremity,attribute) {
|
||||
try {
|
||||
//用10进制犯法来进行整数转换
|
||||
var val = parseInt(value, 10);
|
||||
if (value == val) {
|
||||
if (checkExtremity) {
|
||||
if (val < minimal || val > maximal) {
|
||||
return (attribute+"的参数取值范围必须在"+ minimal + "-" + maximal +"之间");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return (attribute+"的参数存在非法字符,必须为整数或允许的大写英文");
|
||||
} catch (e) {
|
||||
return (attribute+"的参数有非法字符,必须是整数~")
|
||||
}
|
||||
}
|
||||
//检验枚举类型的参数是否正确
|
||||
function checkListField(value, minimal, maximal,attribute) {
|
||||
var st = value.split(",");
|
||||
var values = new Array(st.length);
|
||||
//计算枚举的数字在数组中中出现的次数,出现一次为没有重复的。
|
||||
var count=0;
|
||||
for(var j = 0; j < st.length; j++) {
|
||||
values[j] = st[j];
|
||||
}
|
||||
//判断枚举类型的值是否重复
|
||||
for(var i=0;i<values.length;i++){
|
||||
//判断枚举的值是否在范围内
|
||||
message = checkIntValue(values[i], minimal, maximal, true, attribute);
|
||||
if (message!=true) {
|
||||
return message;
|
||||
}
|
||||
count=0;
|
||||
for(var j=0;j<values.length;j++){
|
||||
if(values[i]==values[j])
|
||||
{
|
||||
count++;
|
||||
}
|
||||
if(count>1){
|
||||
return (attribute+"中的参数重复");
|
||||
}
|
||||
}
|
||||
}
|
||||
var previousValue = -1;
|
||||
//判断枚举的值是否排序正确
|
||||
for (var i= 0; i < values.length; i++) {
|
||||
var currentValue = values[i];
|
||||
try {
|
||||
var val = parseInt(currentValue, 10);
|
||||
if (val < previousValue) {
|
||||
return (attribute+"的参数应该从小到大");
|
||||
} else {
|
||||
previousValue = val;
|
||||
}
|
||||
} catch (e) {
|
||||
//前面验证过了,这边的代码不可能跑到
|
||||
return ("这段提示用不到")
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//检验循环
|
||||
function checkIncrementField(value, minimal, maximal, attribute) {
|
||||
if(value.split("/").length>2){
|
||||
return (attribute + "中的参数只能有一个'/'");
|
||||
}
|
||||
var start = value.substring(0, value.indexOf("/"));
|
||||
var increment = value.substring(value.indexOf("/") + 1);
|
||||
if (start != "*") {
|
||||
//检验前值是否正确
|
||||
message = checkIntValue(start, minimal, maximal, true, attribute);
|
||||
if(message != true){
|
||||
return message;
|
||||
}
|
||||
//检验后值是否正确
|
||||
message = checkIntValue(increment, minimal, maximal, true, attribute);
|
||||
if(message != true){
|
||||
return message;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
//检验后值是否正确
|
||||
return checkIntValue(increment, minimal, maximal, false, attribute);
|
||||
}
|
||||
}
|
||||
|
||||
//检验范围
|
||||
function checkRangeAndCycle(params, minimal, maximal, attribute){
|
||||
//校验“-”符号是否只有一个
|
||||
if(params.split("-").length>2){
|
||||
return (attribute + "中的参数只能有一个'-'");
|
||||
}
|
||||
var value = null;
|
||||
var cycle = null;
|
||||
//检验范围内是否有嵌套周期
|
||||
if(params.indexOf("/") > -1){
|
||||
//校验“/”符号是否只有一个
|
||||
if(params.split("/").length>2){
|
||||
return (attribute + "中的参数只能有一个'/'");
|
||||
}
|
||||
value = params.split("/")[0];
|
||||
cycle = params.split("/")[1];
|
||||
//判断循环的参数是否正确
|
||||
message =checkIntValue(cycle, minimal, maximal, true, attribute);
|
||||
if (message!=true) {
|
||||
return message;
|
||||
}
|
||||
}else{
|
||||
value = params;
|
||||
}
|
||||
var startValue = value.substring(0, value.indexOf( "-" ));
|
||||
var endValue = value.substring(value.indexOf( "-" ) + 1);
|
||||
//判断参数范围的第一个值是否正确
|
||||
message =checkIntValue(startValue, minimal, maximal, true, attribute);
|
||||
if (message!=true) {
|
||||
return message;
|
||||
}
|
||||
//判断参数范围的第二个值是否正确
|
||||
message =checkIntValue(endValue, minimal, maximal, true, attribute);
|
||||
if(message!=true){
|
||||
return message;
|
||||
}
|
||||
//判断参数的范围前值是否小于后值
|
||||
try {
|
||||
var startVal = parseInt(startValue, 10);
|
||||
var endVal = parseInt(endValue, 10);
|
||||
if(endVal < startVal){
|
||||
return (attribute+"的取值范围错误,前值必须小于后值");
|
||||
}
|
||||
if((endVal-startVal)<parseInt(cycle,10)){
|
||||
return (attribute+"的取值范围内的循环无意义");
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
//用不到这行代码的
|
||||
return (attribute+"的参数有非法字符,必须是整数");
|
||||
}
|
||||
}
|
||||
|
||||
//检查日中的特殊字符
|
||||
function checkFieldWithLetter(value, letter, minimalBefore, maximalBefore,attribute) {
|
||||
//判断是否只有一个字母
|
||||
for(var i=0;i<value.length;i++){
|
||||
var count = 0;
|
||||
if(value.charAt(i)==letter){
|
||||
count++;
|
||||
}
|
||||
if(count>1){
|
||||
return (attribute+"的值的"+letter+"字母只能有一个")
|
||||
}
|
||||
}
|
||||
//校验L
|
||||
if(letter == "L"){
|
||||
if(value == "LW"){
|
||||
return true;
|
||||
}
|
||||
if(value=="L"){
|
||||
return true;
|
||||
}
|
||||
if(value.endsWith("LW")&&value.length>2)
|
||||
{
|
||||
return (attribute + "中的参数,最后的LW前面不能有任何字母参数")
|
||||
}
|
||||
if(!value.endsWith("L"))
|
||||
{
|
||||
return (attribute + "中的参数,L字母后面不能有W以外的字符、数字等")
|
||||
}else{
|
||||
var num = value.substring(0,value.indexOf(letter));
|
||||
return checkIntValue(num, minimalBefore, maximalBefore, true, attribute);
|
||||
}
|
||||
}
|
||||
|
||||
//校验W
|
||||
if(letter == "W"){
|
||||
if(!value.endsWith("W")){
|
||||
return (attribute + "中的参数的W必须作为结尾")
|
||||
}else{
|
||||
if(value=="W"){
|
||||
return (attribute + "中的参数的W前面必须有数字")
|
||||
}
|
||||
var num = value.substring(0,value.indexOf(letter));
|
||||
return checkIntValue(num, minimalBefore, maximalBefore, true, attribute);
|
||||
}
|
||||
}
|
||||
|
||||
if(letter == "C"){
|
||||
if(!value.endsWith("C")){
|
||||
return (attribute + "中的参数的C必须作为结尾")
|
||||
}else{
|
||||
if(value=="C"){
|
||||
return (attribute + "中的参数的C前面必须有数字")
|
||||
}
|
||||
var num = value.substring(0,value.indexOf(letter));
|
||||
return checkIntValue(num, minimalBefore, maximalBefore, true, attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//检查星期中的特殊字符
|
||||
function checkFieldWithLetterWeek(value, letter, minimalBefore, maximalBefore,attribute) {
|
||||
//判断是否只有一个字母
|
||||
for(var i=0;i<value.length;i++){
|
||||
var count = 0;
|
||||
if(value.charAt(i)==letter){
|
||||
count++;
|
||||
}
|
||||
if(count>1){
|
||||
return (attribute+"的值的"+letter+"字母只能有一个")
|
||||
}
|
||||
}
|
||||
//校验L
|
||||
if(letter == "L"){
|
||||
if(value=="L"){
|
||||
return true;
|
||||
}
|
||||
if(!value.endsWith("L"))
|
||||
{
|
||||
return (attribute + "中的参数,L字母必须是最后一位")
|
||||
}else{
|
||||
var num = value.substring(0,value.indexOf(letter));
|
||||
return checkIntValue(num, minimalBefore, maximalBefore, true, attribute);
|
||||
}
|
||||
}
|
||||
|
||||
if(letter == "C"){
|
||||
if(!value.endsWith("C")){
|
||||
return (attribute + "中的参数的C必须作为结尾")
|
||||
}else{
|
||||
if(value=="C"){
|
||||
return (attribute + "中的参数的C前面必须有数字")
|
||||
}
|
||||
var num = value.substring(0,value.indexOf(letter));
|
||||
return checkIntValue(num, minimalBefore, maximalBefore, true, attribute);
|
||||
}
|
||||
}
|
||||
|
||||
if(letter == "#"){
|
||||
if(value=="#"){
|
||||
return (attribute + "中的#前后必须有整数");
|
||||
}
|
||||
if(value.charAt(0)==letter){
|
||||
return (attribute + "中的#前面必须有整数")
|
||||
}
|
||||
if(value.endsWith("#")){
|
||||
return (attribute + "中的#后面必须有整数")
|
||||
}
|
||||
var num1 = value.substring(0,value.indexOf(letter));
|
||||
var num2 = value.substring(value.indexOf(letter)+1,value.length)
|
||||
message = checkIntValue(num1, 1, 4, true, (attribute+"的#前面"));
|
||||
if(message!=true){
|
||||
return message;
|
||||
}
|
||||
message = checkIntValue(num2, minimalBefore, maximalBefore, true, (attribute+"的#后面"));
|
||||
if(message!=true){
|
||||
return message;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
2
cmdb-ui/src/components/CustomCodeMirror/index.js
Normal file
2
cmdb-ui/src/components/CustomCodeMirror/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import CusotomCodeMirror from './index.vue'
|
||||
export default CusotomCodeMirror
|
275
cmdb-ui/src/components/CustomCodeMirror/index.vue
Normal file
275
cmdb-ui/src/components/CustomCodeMirror/index.vue
Normal file
@@ -0,0 +1,275 @@
|
||||
<template>
|
||||
<div :style="{ marginTop: '20px' }">
|
||||
<div class="codemirror-toolbar">
|
||||
<div class="codemirror-toolbar-item">
|
||||
<a class="icon" @click="changeFontSize(-1)"><a-icon type="minus-circle"/></a>
|
||||
<span> A </span>
|
||||
<a class="icon" @click="changeFontSize(1)"><a-icon type="plus-circle"/></a>
|
||||
</div>
|
||||
<div class="codemirror-toolbar-item" :style="{ position: 'absolute', right: '0' }">
|
||||
<a class="icon" @click="changeFullscreen"><a-icon type="fullscreen"/></a>
|
||||
</div>
|
||||
<div class="codemirror-toolbar-item">
|
||||
<a-select :value="keyMap" :style="{ width: '100px' }" @change="changeKeyMap">
|
||||
<a-select-option v-for="item in keyMapList" :key="item.value" :value="item.value">{{
|
||||
item.label
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</div>
|
||||
<textarea :id="codeMirrorId" :style="{ width: '100%' }" />
|
||||
<div class="codemirror-toolbar-fullscreen-exit" v-if="fullscreenExitVisible" @click="changeFullscreen">
|
||||
<a-icon type="fullscreen-exit" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CodeMirror from 'codemirror'
|
||||
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
import 'codemirror/theme/monokai.css'
|
||||
|
||||
import 'codemirror/addon/hint/show-hint.js'
|
||||
import 'codemirror/addon/hint/show-hint.css'
|
||||
// import 'codemirror/addon/fold/foldcode.js'
|
||||
// import 'codemirror/addon/fold/foldgutter.js'
|
||||
// import 'codemirror/addon/fold/brace-fold.js'
|
||||
// import 'codemirror/addon/fold/indent-fold.js'
|
||||
// import 'codemirror/addon/fold/comment-fold.js'
|
||||
// import 'codemirror/addon/edit/closebrackets.js'
|
||||
// import 'codemirror/addon/edit/matchbrackets.js'
|
||||
// import 'codemirror/addon/selection/active-line.js'
|
||||
|
||||
import 'codemirror/addon/display/fullscreen.js'
|
||||
import 'codemirror/addon/display/fullscreen.css'
|
||||
|
||||
import 'codemirror/addon/dialog/dialog.js'
|
||||
import 'codemirror/addon/dialog/dialog.css'
|
||||
import 'codemirror/addon/search/searchcursor.js'
|
||||
import 'codemirror/addon/search/search.js'
|
||||
import 'codemirror/addon/search/matchesonscrollbar.css'
|
||||
import 'codemirror/addon/scroll/annotatescrollbar.js'
|
||||
import 'codemirror/addon/search/matchesonscrollbar.js'
|
||||
import 'codemirror/addon/search/jump-to-line.js'
|
||||
import 'codemirror/addon/search/match-highlighter.js'
|
||||
|
||||
import 'codemirror/keymap/vim.js'
|
||||
import 'codemirror/keymap/emacs.js'
|
||||
import 'codemirror/keymap/sublime.js'
|
||||
import 'codemirror/addon/edit/matchbrackets.js'
|
||||
import 'codemirror/addon/edit/closebrackets.js'
|
||||
import 'codemirror/addon/comment/comment.js'
|
||||
import 'codemirror/addon/wrap/hardwrap.js'
|
||||
import 'codemirror/addon/fold/foldcode.js'
|
||||
import 'codemirror/addon/fold/brace-fold.js'
|
||||
import 'codemirror/mode/javascript/javascript.js'
|
||||
import 'codemirror/mode/clike/clike.js'
|
||||
|
||||
require('codemirror/mode/python/python.js')
|
||||
require('codemirror/mode/shell/shell.js')
|
||||
require('codemirror/mode/powershell/powershell.js')
|
||||
|
||||
export default {
|
||||
name: 'CustomCodeMirror',
|
||||
// props: {
|
||||
// codeContent: {
|
||||
// type: String,
|
||||
// default: '',
|
||||
// },
|
||||
// },
|
||||
// model: {
|
||||
// prop: 'codeContent',
|
||||
// event: 'change',
|
||||
// },
|
||||
props: {
|
||||
codeMirrorId: {
|
||||
type: String,
|
||||
default: 'codemirror',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const keyMapList = [
|
||||
{ value: 'default', label: '默认' },
|
||||
{ value: 'vim', label: 'vim' },
|
||||
{ value: 'emacs', label: 'emacs' },
|
||||
{ value: 'sublime', label: 'sublime' },
|
||||
]
|
||||
return {
|
||||
keyMapList,
|
||||
coder: null,
|
||||
fontSize: 14,
|
||||
keyMap: 'default',
|
||||
fullscreenExitVisible: false,
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
initCodeMirror(codeContent) {
|
||||
const that = this
|
||||
if (this.coder) {
|
||||
this.coder.setValue(codeContent)
|
||||
return
|
||||
}
|
||||
this.coder = CodeMirror.fromTextArea(document.getElementById(this.codeMirrorId), {
|
||||
lineNumbers: true,
|
||||
mode: 'python',
|
||||
theme: 'monokai',
|
||||
smartIndent: true,
|
||||
tabSize: 4,
|
||||
lineWrapping: false,
|
||||
indentUnit: 4,
|
||||
extraKeys: {
|
||||
F11: function(cm) {
|
||||
that.fullscreenExitVisible = !cm.getOption('fullScreen')
|
||||
cm.setOption('fullScreen', !cm.getOption('fullScreen'))
|
||||
},
|
||||
Tab: (cm) => {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('add')
|
||||
} else {
|
||||
cm.replaceSelection(Array(cm.getOption('indentUnit') + 1).join(' '), 'end', '+input')
|
||||
}
|
||||
},
|
||||
'Shift-Tab': (cm) => {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('subtract')
|
||||
} else {
|
||||
const cursor = cm.getCursor()
|
||||
cm.setCursor({ line: cursor.line, ch: cursor.ch - 4 })
|
||||
}
|
||||
},
|
||||
|
||||
// Esc: function(cm) {
|
||||
// if (cm.getOption('fullScreen')) cm.setOption('fullScreen', false)
|
||||
// },
|
||||
},
|
||||
hintOptions: {
|
||||
// 自定义提示选项
|
||||
completeSingle: false, // 当匹配只有一项的时候是否自动补全
|
||||
},
|
||||
})
|
||||
const keyMap = localStorage.getItem('dagCodeMirrorKeyMap')
|
||||
if (keyMap) {
|
||||
this.changeKeyMap(keyMap)
|
||||
}
|
||||
this.coder.setValue(codeContent)
|
||||
this.coder.on('keypress', () => {
|
||||
// 显示智能提示
|
||||
this.coder.showHint()
|
||||
})
|
||||
this.coder.on('change', () => {
|
||||
this.$emit('changeCodeContent', this.coder.getValue())
|
||||
})
|
||||
this.coder.on('focus', () => {
|
||||
this.$emit('focus')
|
||||
})
|
||||
},
|
||||
changeStyle(value) {
|
||||
switch (value) {
|
||||
case '0':
|
||||
this.coder.setOption('mode', 'shell')
|
||||
break
|
||||
case '1':
|
||||
this.coder.setOption('mode', 'shell')
|
||||
break
|
||||
case '3':
|
||||
this.coder.setOption('mode', 'powershell')
|
||||
break
|
||||
case '4':
|
||||
this.coder.setOption('mode', 'ruby')
|
||||
break
|
||||
default:
|
||||
this.coder.setOption('mode', 'python')
|
||||
}
|
||||
},
|
||||
changeFontSize(num) {
|
||||
const element = document.getElementsByClassName('CodeMirror')[0]
|
||||
if (element) {
|
||||
if (num === -1 && this.fontSize <= 12) {
|
||||
return
|
||||
}
|
||||
if (num === 1 && this.fontSize >= 25) {
|
||||
return
|
||||
}
|
||||
this.fontSize = this.fontSize + num
|
||||
element.style.fontSize = `${this.fontSize}px`
|
||||
}
|
||||
},
|
||||
changeFullscreen() {
|
||||
this.fullscreenExitVisible = !this.coder.getOption('fullScreen')
|
||||
this.coder.setOption('fullScreen', !this.coder.getOption('fullScreen'))
|
||||
},
|
||||
changeKeyMap(value) {
|
||||
this.keyMap = value
|
||||
localStorage.setItem('dagCodeMirrorKeyMap', value)
|
||||
this.coder.setOption('keyMap', value)
|
||||
this.coder.setOption('matchBrackets', true)
|
||||
if (value === 'vim') {
|
||||
// var commandDisplay = document.getElementById('command-display')
|
||||
var keys = ''
|
||||
CodeMirror.on(this.coder, 'vim-keypress', function(key) {
|
||||
keys = keys + key
|
||||
// commandDisplay.innerText = keys
|
||||
})
|
||||
CodeMirror.on(this.coder, 'vim-command-done', function(e) {
|
||||
keys = ''
|
||||
// commandDisplay.innerHTML = keys
|
||||
})
|
||||
// var vimMode = document.getElementById('vim-mode')
|
||||
// CodeMirror.on(this.coder, 'vim-mode-change', function(e) {
|
||||
// vimMode.innerText = JSON.stringify(e)
|
||||
// })
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.CodeMirror {
|
||||
border: 1px solid silver;
|
||||
border-width: 1px 2px;
|
||||
line-height: 150%;
|
||||
font-size: 14px;
|
||||
}
|
||||
.CodeMirror-hints {
|
||||
z-index: 9999;
|
||||
}
|
||||
.codemirror-toolbar {
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
background-color: #fafafa;
|
||||
border: 1px solid #d9d9d9;
|
||||
position: relative;
|
||||
.codemirror-toolbar-item {
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 10px;
|
||||
vertical-align: text-bottom;
|
||||
.icon {
|
||||
color: #000000a6;
|
||||
&:hover {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.codemirror-toolbar-fullscreen-exit {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 15px;
|
||||
background-color: #f3f3f3;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
</style>
|
122
cmdb-ui/src/components/CustomDrawer/CustomDrawer.vue
Normal file
122
cmdb-ui/src/components/CustomDrawer/CustomDrawer.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
ref="customDrawer"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
:closable="false"
|
||||
:placement="placement"
|
||||
:bodyStyle="{ maxHeight: bodyMaxHeight, overflow: 'auto', ...$attrs.bodyStyle }"
|
||||
:keyboard="false"
|
||||
>
|
||||
<div v-if="closable" :class="`custom-drawer-close custom-drawer-${placement}`" @click="clickCustomClose">
|
||||
<a-icon :type="closeIconType" />
|
||||
</div>
|
||||
<template v-if="hasTitle" slot="title">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</template>
|
||||
<slot></slot>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CustomDrawer',
|
||||
components: {},
|
||||
props: {
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'right',
|
||||
},
|
||||
hasTitle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hasFooter: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
closeIconType() {
|
||||
if (this.placement === 'top') return 'up'
|
||||
if (this.placement === 'bottom') return 'down'
|
||||
return this.placement || 'right'
|
||||
},
|
||||
customClass() {
|
||||
if (!this.placement) return 'right'
|
||||
return this.placement
|
||||
},
|
||||
bodyMaxHeight() {
|
||||
const titleHeight = this.hasTitle ? 55 : 0
|
||||
const footerHeight = this.hasFooter ? 53 : 0
|
||||
return `calc(100vh - ${titleHeight + footerHeight}px)`
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickCustomClose() {
|
||||
this.$refs.customDrawer.close()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.custom-drawer-close {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
background: #custom_colors[color_1];
|
||||
color: white;
|
||||
text-align: center;
|
||||
transition: all 0.3s;
|
||||
z-index: 1;
|
||||
&:hover {
|
||||
background: #597ef7;
|
||||
}
|
||||
}
|
||||
.custom-drawer-right,
|
||||
.custom-drawer-left {
|
||||
width: 14px;
|
||||
height: 50px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
line-height: 50px;
|
||||
}
|
||||
.custom-drawer-left {
|
||||
right: 0;
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.custom-drawer-right {
|
||||
left: 0;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.custom-drawer-top,
|
||||
.custom-drawer-bottom {
|
||||
width: 50px;
|
||||
height: 14px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
line-height: 14px;
|
||||
}
|
||||
.custom-drawer-top {
|
||||
bottom: 0;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.custom-drawer-bottom {
|
||||
top: 0;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
</style>
|
2
cmdb-ui/src/components/CustomDrawer/index.js
Normal file
2
cmdb-ui/src/components/CustomDrawer/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import CustomDrawer from './CustomDrawer'
|
||||
export default CustomDrawer
|
1063
cmdb-ui/src/components/CustomIconSelect/constants.js
Normal file
1063
cmdb-ui/src/components/CustomIconSelect/constants.js
Normal file
File diff suppressed because it is too large
Load Diff
2
cmdb-ui/src/components/CustomIconSelect/index.js
Normal file
2
cmdb-ui/src/components/CustomIconSelect/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import CustomIconSelect from './index.vue'
|
||||
export default CustomIconSelect
|
248
cmdb-ui/src/components/CustomIconSelect/index.vue
Normal file
248
cmdb-ui/src/components/CustomIconSelect/index.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<a-popover
|
||||
:visible="visible"
|
||||
overlayClassName="custom-icon-select-popover"
|
||||
:destroyTooltipOnHide="true"
|
||||
placement="bottom"
|
||||
>
|
||||
<div id="custom-icon-select-popover" slot="content">
|
||||
<div class="custom-icon-select-popover-icon-type">
|
||||
<div
|
||||
:class="`${currentIconType === item.value ? 'selected' : ''}`"
|
||||
v-for="item in iconTypeList"
|
||||
:key="item.value"
|
||||
@click="handleChangeIconType(item.value)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="custom-icon-select-popover-content">
|
||||
<div v-for="category in iconList" :key="category.value">
|
||||
<h4 class="category">{{ category.label }}</h4>
|
||||
<div class="custom-icon-select-popover-content-wrapper">
|
||||
<div
|
||||
v-for="name in category.list"
|
||||
:key="name.value"
|
||||
:class="`custom-icon-select-popover-item ${value.name === name.value ? 'selected' : ''}`"
|
||||
@click="clickIcon(name.value)"
|
||||
>
|
||||
<ops-icon :type="name.value" />
|
||||
<span class="custom-icon-select-popover-item-label">{{ name.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="currentIconType !== '0' && currentIconType !== '3'">
|
||||
<a-divider :style="{ margin: '5px 0' }" />
|
||||
<el-color-picker size="mini" v-model="value.color"> </el-color-picker>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="custom-icon-select-block" id="custom-icon-select-block" @click="showSelect">
|
||||
<ops-icon
|
||||
:type="value.name"
|
||||
:style="{ color: value.name && value.name.startsWith('icon-') ? value.color || '' : '' }"
|
||||
/>
|
||||
</div>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ColorPicker } from 'element-ui'
|
||||
import {
|
||||
iconTypeList,
|
||||
commonIconList,
|
||||
linearIconList,
|
||||
fillIconList,
|
||||
multicolorIconList,
|
||||
} from './constants'
|
||||
export default {
|
||||
name: 'CustomIconSelect',
|
||||
components: { ElColorPicker: ColorPicker },
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return { name: '', color: '' }
|
||||
},
|
||||
},
|
||||
iconType: {
|
||||
type: String,
|
||||
default: 'cmdb',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
iconTypeList,
|
||||
commonIconList,
|
||||
linearIconList,
|
||||
fillIconList,
|
||||
multicolorIconList,
|
||||
visible: false,
|
||||
currentIconType: '1',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconList() {
|
||||
switch (this.currentIconType) {
|
||||
case '0': // 常用
|
||||
return this.commonIconList
|
||||
case '1': // 线性
|
||||
return this.linearIconList
|
||||
case '2': // 实底
|
||||
return this.fillIconList
|
||||
case '3': // 多色
|
||||
return this.multicolorIconList
|
||||
default:
|
||||
return this.linearIconList
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.eventListener)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('click', this.eventListener)
|
||||
},
|
||||
methods: {
|
||||
eventListener(e) {
|
||||
if (this.visible) {
|
||||
const dom = document.getElementById(`custom-icon-select-popover`)
|
||||
const dom_icon = document.getElementById(`custom-icon-select-block`)
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
if (dom) {
|
||||
const isSelf = dom.contains(e.target) || dom_icon.contains(e.target)
|
||||
if (!isSelf) {
|
||||
this.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
clickIcon(name) {
|
||||
// 当name一样时,取消选择
|
||||
if (name === this.value.name) {
|
||||
this.$emit('change', {
|
||||
name: '',
|
||||
color: '',
|
||||
})
|
||||
} else {
|
||||
this.$emit('change', {
|
||||
name,
|
||||
color: this.value.name && this.value.name.startsWith('icon-') ? this.value.color || '' : '',
|
||||
})
|
||||
}
|
||||
},
|
||||
showSelect() {
|
||||
this.visible = true
|
||||
if (!this.value.name) {
|
||||
this.currentIconType = '1'
|
||||
return
|
||||
}
|
||||
if (this.value.name.startsWith('changyong-')) {
|
||||
this.currentIconType = '0'
|
||||
} else if (this.value.name.startsWith('icon-xianxing')) {
|
||||
this.currentIconType = '1'
|
||||
} else if (this.value.name.startsWith('icon-shidi')) {
|
||||
this.currentIconType = '2'
|
||||
} else {
|
||||
this.currentIconType = '3'
|
||||
}
|
||||
},
|
||||
handleChangeIconType(value) {
|
||||
this.currentIconType = value
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.custom-icon-select-popover.ant-popover-placement-top .ant-popover-content {
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
.custom-icon-select-popover {
|
||||
width: 650px;
|
||||
overflow: auto;
|
||||
padding-top: 0;
|
||||
box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.1);
|
||||
.ant-popover-arrow {
|
||||
display: none;
|
||||
}
|
||||
.ant-popover-inner-content {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
.custom-icon-select-popover-content {
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
.category {
|
||||
font-size: 14px;
|
||||
}
|
||||
.custom-icon-select-popover-content-wrapper {
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin-left: 10px;
|
||||
.custom-icon-select-popover-item {
|
||||
width: 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 5px 5px 2px 5px;
|
||||
margin: 0 2px 6px;
|
||||
color: #666;
|
||||
.custom-icon-select-popover-item-label {
|
||||
margin-top: 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
}
|
||||
.selected {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
}
|
||||
}
|
||||
.custom-icon-select-popover-icon-type {
|
||||
display: inline-block;
|
||||
> div {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid #eeeeee;
|
||||
&:hover {
|
||||
color: #2f54eb;
|
||||
}
|
||||
}
|
||||
.selected {
|
||||
border-color: #2f54eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.custom-icon-select-block {
|
||||
position: relative;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #eeeeee;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
> i {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
54
cmdb-ui/src/components/CustomRadio/CustomRadio.vue
Normal file
54
cmdb-ui/src/components/CustomRadio/CustomRadio.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="custom-radio">
|
||||
<div
|
||||
:class="`custom-radio-inner custom-radio-inner-${layout || 'inline'}`"
|
||||
v-for="{ value: radioValue, label, layout } in radioList"
|
||||
:key="radioValue"
|
||||
>
|
||||
<a-radio @click="clickRadio(radioValue)" :checked="value === radioValue" :key="`raido_${radioValue}`">{{
|
||||
label
|
||||
}}</a-radio>
|
||||
<slot :name="`extra_${radioValue}`" v-bind="{ radioValue, label }"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CustomRadio',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
radioList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickRadio(radioValue) {
|
||||
this.$emit('change', radioValue)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.custom-radio {
|
||||
.custom-radio-inner {
|
||||
min-height: 40px;
|
||||
}
|
||||
.custom-radio-inner-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.custom-radio-inner-vertical label {
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
2
cmdb-ui/src/components/CustomRadio/index.js
Normal file
2
cmdb-ui/src/components/CustomRadio/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import CustomRadio from './CustomRadio'
|
||||
export default CustomRadio
|
65
cmdb-ui/src/components/CustomTransfer/CustomTransfer.vue
Normal file
65
cmdb-ui/src/components/CustomTransfer/CustomTransfer.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<a-transfer v-bind="$attrs" v-on="$listeners" :data-source="dataSource" :target-keys="targetKeys"> </a-transfer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CustomTransfer',
|
||||
props: {
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
targetKeys: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 穿梭框双击实现
|
||||
leftToRight(leftList, dataSource, targetKeys, sourceImportantKey, targetImportantKey) {
|
||||
for (let i = 0; i < leftList.length; i++) {
|
||||
leftList[i].ondblclick = e => {
|
||||
dataSource.forEach(item => {
|
||||
if (item[`${sourceImportantKey}`] === e.toElement.innerText) {
|
||||
targetKeys.push(item[`${targetImportantKey}`])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
rightToLeft(rightList, dataSource, targetKeys, sourceImportantKey, targetImportantKey) {
|
||||
for (let i = 0; i < rightList.length; i++) {
|
||||
rightList[i].ondblclick = e => {
|
||||
dataSource.forEach(item => {
|
||||
if (item[`${sourceImportantKey}`] === e.toElement.innerText) {
|
||||
const idx = targetKeys.findIndex(item1 => {
|
||||
return item1 === item[`${targetImportantKey}`]
|
||||
})
|
||||
targetKeys.splice(idx, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
// 必须传入importantKey,用来做键名比对,传错或不传会造成错误
|
||||
dbClick(sourceSelectedKeys, targetSelectedKeys, sourceImportantKey, targetImportantKey) {
|
||||
window.setTimeout(() => {
|
||||
const element = document.getElementsByClassName('ant-transfer-list-content')
|
||||
if (this.dataSource.length !== this.targetKeys.length) {
|
||||
const leftList = element[0].children
|
||||
const rightList = element[1] ? element[1].children : []
|
||||
this.leftToRight(leftList, this.dataSource, this.targetKeys, sourceImportantKey, targetImportantKey)
|
||||
this.rightToLeft(rightList, this.dataSource, this.targetKeys, sourceImportantKey, targetImportantKey)
|
||||
}
|
||||
if (this.targetKeys.length && this.targetKeys.length === this.dataSource.length) {
|
||||
const rightList = element[0].children
|
||||
this.rightToLeft(rightList, this.dataSource, this.targetKeys, sourceImportantKey, targetImportantKey)
|
||||
}
|
||||
}, 100)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
2
cmdb-ui/src/components/CustomTransfer/index.js
Normal file
2
cmdb-ui/src/components/CustomTransfer/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import CustomTransfer from './CustomTransfer'
|
||||
export default CustomTransfer
|
@@ -1,153 +0,0 @@
|
||||
<template>
|
||||
<div :class="['description-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>
|
||||
|
||||
.description-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>
|
@@ -1,2 +0,0 @@
|
||||
import DescriptionList from './DescriptionList'
|
||||
export default DescriptionList
|
@@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<div :class="prefixCls">
|
||||
<quill-editor
|
||||
v-model="content"
|
||||
ref="myQuillEditor"
|
||||
:options="editorOption"
|
||||
@blur="onEditorBlur($event)"
|
||||
@focus="onEditorFocus($event)"
|
||||
@ready="onEditorReady($event)"
|
||||
@change="onEditorChange($event)">
|
||||
</quill-editor>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'quill/dist/quill.core.css'
|
||||
import 'quill/dist/quill.snow.css'
|
||||
import 'quill/dist/quill.bubble.css'
|
||||
|
||||
import { quillEditor } from 'vue-quill-editor'
|
||||
|
||||
export default {
|
||||
name: 'QuillEditor',
|
||||
components: {
|
||||
quillEditor
|
||||
},
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-editor-quill'
|
||||
},
|
||||
// 表单校验用字段
|
||||
// eslint-disable-next-line
|
||||
value: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
content: null,
|
||||
editorOption: {
|
||||
// some quill options
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onEditorBlur (quill) {
|
||||
console.log('editor blur!', quill)
|
||||
},
|
||||
onEditorFocus (quill) {
|
||||
console.log('editor focus!', quill)
|
||||
},
|
||||
onEditorReady (quill) {
|
||||
console.log('editor ready!', quill)
|
||||
},
|
||||
onEditorChange ({ quill, html, text }) {
|
||||
console.log('editor change!', quill, html, text)
|
||||
this.$emit('change', html)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.content = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('../index.less');
|
||||
|
||||
/* 覆盖 quill 默认边框圆角为 ant 默认圆角,用于统一 ant 组件风格 */
|
||||
.ant-editor-quill {
|
||||
/deep/ .ql-toolbar.ql-snow {
|
||||
border-radius: @border-radius-base @border-radius-base 0 0;
|
||||
}
|
||||
/deep/ .ql-container.ql-snow {
|
||||
border-radius: 0 0 @border-radius-base @border-radius-base;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,57 +0,0 @@
|
||||
<template>
|
||||
<div :class="prefixCls">
|
||||
<div ref="editor" class="editor-wrapper"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WEditor from 'wangeditor'
|
||||
|
||||
export default {
|
||||
name: 'WangEditor',
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-editor-wang'
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
value: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editor: null,
|
||||
editorContent: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.editorContent = val
|
||||
this.editor.txt.html(val)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initEditor()
|
||||
},
|
||||
methods: {
|
||||
initEditor () {
|
||||
this.editor = new WEditor(this.$refs.editor)
|
||||
// this.editor.onchangeTimeout = 200
|
||||
this.editor.customConfig.onchange = (html) => {
|
||||
this.editorContent = html
|
||||
this.$emit('change', this.editorContent)
|
||||
}
|
||||
this.editor.create()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-editor-wang {
|
||||
.editor-wrapper {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -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>
|
@@ -1,3 +0,0 @@
|
||||
import Ellipsis from './Ellipsis'
|
||||
|
||||
export default Ellipsis
|
@@ -1,38 +0,0 @@
|
||||
# Ellipsis 文本自动省略号
|
||||
|
||||
文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。
|
||||
|
||||
|
||||
|
||||
引用方式:
|
||||
|
||||
```javascript
|
||||
import Ellipsis from '@/components/Ellipsis'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Ellipsis
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 代码演示 [demo](https://pro.loacg.com/test/home)
|
||||
|
||||
```html
|
||||
<ellipsis :length="100" tooltip>
|
||||
There were injuries alleged in three cases in 2015, and a
|
||||
fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall.
|
||||
</ellipsis>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## API
|
||||
|
||||
|
||||
参数 | 说明 | 类型 | 默认值
|
||||
----|------|-----|------
|
||||
tooltip | 移动到文本展示完整内容的提示 | boolean | -
|
||||
length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | -
|
2
cmdb-ui/src/components/EmployeeTransfer/index.js
Normal file
2
cmdb-ui/src/components/EmployeeTransfer/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import EmployeeTransfer from './index.vue'
|
||||
export default EmployeeTransfer
|
293
cmdb-ui/src/components/EmployeeTransfer/index.vue
Normal file
293
cmdb-ui/src/components/EmployeeTransfer/index.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<template>
|
||||
<div class="employee-transfer" :style="{ '--custom-height': `${height}px` }">
|
||||
<div class="employee-transfer-left" v-if="!readOnly">
|
||||
<treeselect
|
||||
:disable-branch-nodes="disableBranchNodes"
|
||||
:flat="true"
|
||||
:multiple="true"
|
||||
:options="employeeTreeSelectOption"
|
||||
placeholder="请输入搜索内容"
|
||||
v-model="treeValue"
|
||||
:max-height="height - 50"
|
||||
noChildrenText="空"
|
||||
noOptionsText="空"
|
||||
:clearable="false"
|
||||
:always-open="true"
|
||||
:default-expand-level="1"
|
||||
:class="{ 'employee-transfer': true, 'employee-transfer-has-input': !!inputValue }"
|
||||
@search-change="changeInputValue"
|
||||
noResultsText="暂无数据"
|
||||
openDirection="below"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
<div class="employee-transfer-operation" v-if="!readOnly">
|
||||
<div @click="handleRight" class="operation-right"><a-icon type="right" /></div>
|
||||
<br />
|
||||
<div @click="handleLeft" class="operation-left"><a-icon type="left" /></div>
|
||||
</div>
|
||||
<div class="employee-transfer-right">
|
||||
<div
|
||||
:class="{
|
||||
'employee-transfer-right-item': true,
|
||||
'employee-transfer-right-selected': !readOnly && selectedRight.includes(right),
|
||||
}"
|
||||
v-for="right in rightData"
|
||||
:key="right"
|
||||
@click="handleSelectedRight(right)"
|
||||
>
|
||||
{{ getLabel(right) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAllDepAndEmployee, getAllDepartmentList } from '@/api/company'
|
||||
import { getEmployeeList } from '@/api/employee'
|
||||
import { formatOption } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'EmployeeTransfer',
|
||||
inject: {
|
||||
getDataBySelf: {
|
||||
from: 'getDataBySelf',
|
||||
default: true,
|
||||
},
|
||||
provide_allTreeDepAndEmp: {
|
||||
default: () => null,
|
||||
},
|
||||
provide_allFlatDepartments: {
|
||||
default: () => null,
|
||||
},
|
||||
provide_allFlatEmployees: {
|
||||
default: () => null,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
height: {
|
||||
type: Number,
|
||||
default: 260,
|
||||
},
|
||||
disableBranchNodes: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
uniqueKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isDisabledAllCompany: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
default_allTreeDepAndEmp: [],
|
||||
treeValue: [],
|
||||
inputValue: '',
|
||||
rightData: [],
|
||||
selectedRight: [],
|
||||
default_allFlatDepartments: [],
|
||||
default_allFlatEmployees: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
employeeTreeSelectOption() {
|
||||
return formatOption(
|
||||
this.allTreeDepAndEmp,
|
||||
2,
|
||||
this.isDisabledAllCompany,
|
||||
this.uniqueKey || 'department_id',
|
||||
this.uniqueKey || 'employee_id'
|
||||
)
|
||||
},
|
||||
allTreeDepAndEmp() {
|
||||
if (this.getDataBySelf) {
|
||||
return this.default_allTreeDepAndEmp
|
||||
}
|
||||
return this.provide_allTreeDepAndEmp()
|
||||
},
|
||||
allFlatDepartments() {
|
||||
if (this.getDataBySelf) {
|
||||
return this.default_allFlatDepartments
|
||||
}
|
||||
return this.provide_allFlatDepartments()
|
||||
},
|
||||
allFlatEmployees() {
|
||||
if (this.getDataBySelf) {
|
||||
return this.default_allFlatEmployees
|
||||
}
|
||||
return this.provide_allFlatEmployees()
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.getDataBySelf) {
|
||||
getAllDepAndEmployee({ block: 0 }).then((res) => {
|
||||
this.default_allTreeDepAndEmp = res
|
||||
})
|
||||
// 获取全部员工和部门的平铺列表
|
||||
getEmployeeList({ block_status: 0, page_size: 99999 }).then((res) => {
|
||||
this.default_allFlatEmployees = res.data_list
|
||||
})
|
||||
getAllDepartmentList({ is_tree: 0 }).then((res) => {
|
||||
this.default_allFlatDepartments = res
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setValues({ rightData }) {
|
||||
this.rightData = rightData
|
||||
},
|
||||
getValues() {
|
||||
const department = []
|
||||
const user = []
|
||||
this.rightData.forEach((item) => {
|
||||
const _split = item.split('-')
|
||||
if (_split[0] === 'department') {
|
||||
department.push(Number(_split[1]))
|
||||
} else {
|
||||
user.push(Number(_split[1]))
|
||||
}
|
||||
})
|
||||
const _idx = department.findIndex((item) => item === 0)
|
||||
if (_idx > -1) {
|
||||
department.splice(_idx, 1)
|
||||
department.unshift(-1)
|
||||
}
|
||||
return {
|
||||
department,
|
||||
user,
|
||||
}
|
||||
},
|
||||
changeInputValue(value) {
|
||||
this.inputValue = value
|
||||
},
|
||||
handleRight() {
|
||||
this.rightData = [...new Set([...this.treeValue, ...this.rightData])]
|
||||
this.treeValue = []
|
||||
this.selectedRight = []
|
||||
},
|
||||
handleLeft() {
|
||||
this.selectedRight.forEach((id) => {
|
||||
const _idx = this.rightData.findIndex((item) => item === id)
|
||||
if (_idx > -1) {
|
||||
this.rightData.splice(_idx, 1)
|
||||
}
|
||||
})
|
||||
this.selectedRight = []
|
||||
},
|
||||
handleSelectedRight(id) {
|
||||
const _idx = this.selectedRight.findIndex((item) => item === id)
|
||||
if (_idx > -1) {
|
||||
this.selectedRight.splice(_idx, 1)
|
||||
} else {
|
||||
this.selectedRight.push(id)
|
||||
}
|
||||
},
|
||||
getLabel(id) {
|
||||
const _split = id.split('-')
|
||||
const type = _split[0]
|
||||
const _id = Number(_split[1])
|
||||
if (type === 'department') {
|
||||
const _find = this.allFlatDepartments.find((item) => item[this.uniqueKey || 'department_id'] === _id)
|
||||
return _find?.department_name ?? ''
|
||||
} else {
|
||||
const _find = this.allFlatEmployees.find((item) => item[this.uniqueKey || 'employee_id'] === _id)
|
||||
return _find?.nickname ?? ''
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.employee-transfer {
|
||||
width: 100%;
|
||||
.vue-treeselect__multi-value-item-container {
|
||||
display: none;
|
||||
}
|
||||
.vue-treeselect__menu {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
margin-top: 10px;
|
||||
background-color: #f9fbff;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
.employee-transfer.vue-treeselect--open.vue-treeselect--open-below .vue-treeselect__control {
|
||||
border-radius: 2px;
|
||||
width: 90%;
|
||||
margin-left: 5%;
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.employee-transfer.vue-treeselect--has-value {
|
||||
.vue-treeselect-helper-hide {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.employee-transfer.employee-transfer-has-input {
|
||||
.vue-treeselect-helper-hide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.employee-transfer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
& &-left,
|
||||
& &-right {
|
||||
width: 40%;
|
||||
background-color: #f9fbff;
|
||||
padding-top: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
height: var(--custom-height);
|
||||
}
|
||||
& &-right {
|
||||
padding-top: 12px;
|
||||
overflow: auto;
|
||||
.employee-transfer-right-item {
|
||||
cursor: pointer;
|
||||
padding: 2px 12px;
|
||||
margin: 2px 0;
|
||||
}
|
||||
.employee-transfer-right-selected {
|
||||
background-color: #f0f5ff;
|
||||
}
|
||||
}
|
||||
& &-operation {
|
||||
width: 10%;
|
||||
height: var(--custom-height);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.operation-left,
|
||||
.operation-right {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_1];
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -32,7 +32,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
handleToHome () {
|
||||
this.$router.push({ name: 'cmdb_preference' })
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<div :class="prefixCls">
|
||||
<div style="float: left">
|
||||
<slot name="extra">{{ extra }}</slot>
|
||||
</div>
|
||||
<div style="float: right">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FooterToolBar',
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-pro-footer-toolbar'
|
||||
},
|
||||
extra: {
|
||||
type: [String, Object],
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
@@ -1,4 +0,0 @@
|
||||
import FooterToolBar from './FooterToolBar'
|
||||
import './index.less'
|
||||
|
||||
export default FooterToolBar
|
@@ -1,23 +0,0 @@
|
||||
@import "../index";
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
@@ -1,48 +0,0 @@
|
||||
# FooterToolbar 底部工具栏
|
||||
|
||||
固定在底部的工具栏。
|
||||
|
||||
|
||||
|
||||
## 何时使用
|
||||
|
||||
固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。
|
||||
|
||||
|
||||
|
||||
引用方式:
|
||||
|
||||
```javascript
|
||||
import FooterToolBar from '@/components/FooterToolbar'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FooterToolBar
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 代码演示
|
||||
|
||||
```html
|
||||
<footer-tool-bar>
|
||||
<a-button type="primary" @click="validate" :loading="loading">提交</a-button>
|
||||
</footer-tool-bar>
|
||||
```
|
||||
或
|
||||
```html
|
||||
<footer-tool-bar extra="扩展信息提示">
|
||||
<a-button type="primary" @click="validate" :loading="loading">提交</a-button>
|
||||
</footer-tool-bar>
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
参数 | 说明 | 类型 | 默认值
|
||||
----|------|-----|------
|
||||
children (slot) | 工具栏内容,向右对齐 | - | -
|
||||
extra | 额外信息,向左对齐 | String, Object | -
|
||||
|
@@ -1,6 +1,22 @@
|
||||
<template>
|
||||
<div class="footer">
|
||||
<div class="links">
|
||||
<a
|
||||
href="https://veops.cn/"
|
||||
target="_blank"
|
||||
>维易科技</a>
|
||||
<a
|
||||
href="https://github.com/sendya/ant-design-pro-vue"
|
||||
target="_blank"
|
||||
>
|
||||
<a-icon type="github" />
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class="copyright">
|
||||
Copyright
|
||||
<a-icon type="copyright" /> 2021-2023 <span>@维易科技</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@@ -1,21 +1,29 @@
|
||||
<template>
|
||||
<transition name="showHeader">
|
||||
<div v-if="visible" class="header-animat">
|
||||
<div class="header-animat">
|
||||
<a-layout-header
|
||||
v-if="visible"
|
||||
:class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', ]"
|
||||
:style="{ padding: '0' }">
|
||||
:class="[
|
||||
fixedHeader && 'ant-header-fixedHeader',
|
||||
sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed',
|
||||
]"
|
||||
:style="{ padding: '0' }"
|
||||
>
|
||||
<div v-if="mode === 'sidemenu'" class="header">
|
||||
<a-icon v-if="device==='mobile'" class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle"/>
|
||||
<a-icon v-else class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggle"/>
|
||||
<a-icon
|
||||
v-if="device === 'mobile'"
|
||||
class="trigger"
|
||||
:type="collapsed ? 'menu-fold' : 'menu-unfold'"
|
||||
@click="toggle"
|
||||
/>
|
||||
<a-icon v-else class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggle" />
|
||||
<top-menu></top-menu>
|
||||
<user-menu></user-menu>
|
||||
</div>
|
||||
<div v-else :class="['top-nav-header-index', theme]">
|
||||
<div class="header-index-wide">
|
||||
<div class="header-index-left">
|
||||
<logo class="top-nav-header" :show-title="device !== 'mobile'"/>
|
||||
<s-menu v-if="device !== 'mobile'" mode="horizontal" :menu="menus" :theme="theme" :i18n-render="i18nRender" />
|
||||
<logo class="top-nav-header" :show-title="device !== 'mobile'" :collapsed="collapsed" />
|
||||
<s-menu v-if="device !== 'mobile'" mode="horizontal" :menu="menus" :theme="theme" />
|
||||
<a-icon v-else class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle" />
|
||||
</div>
|
||||
<top-menu></top-menu>
|
||||
@@ -33,7 +41,6 @@ import TopMenu from '../tools/TopMenu'
|
||||
import SMenu from '../Menu/'
|
||||
import Logo from '../tools/Logo'
|
||||
import { mixin } from '@/utils/mixin'
|
||||
import { i18nRender } from '@/locales'
|
||||
|
||||
export default {
|
||||
name: 'GlobalHeader',
|
||||
@@ -41,47 +48,46 @@ export default {
|
||||
UserMenu,
|
||||
TopMenu,
|
||||
SMenu,
|
||||
Logo
|
||||
Logo,
|
||||
},
|
||||
mixins: [mixin],
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
// sidemenu, topmenu
|
||||
default: 'sidemenu'
|
||||
default: 'sidemenu',
|
||||
},
|
||||
menus: {
|
||||
type: Array,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'dark'
|
||||
default: 'dark',
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
device: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'desktop'
|
||||
}
|
||||
default: 'desktop',
|
||||
},
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
visible: true,
|
||||
oldScrollTop: 0
|
||||
oldScrollTop: 0,
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
computed: {},
|
||||
mounted() {
|
||||
document.addEventListener('scroll', this.handleScroll, { passive: true })
|
||||
},
|
||||
methods: {
|
||||
i18nRender,
|
||||
handleScroll () {
|
||||
handleScroll() {
|
||||
if (!this.autoHideHeader) {
|
||||
return
|
||||
}
|
||||
@@ -90,32 +96,32 @@ export default {
|
||||
if (!this.ticking) {
|
||||
this.ticking = true
|
||||
requestAnimationFrame(() => {
|
||||
if (this.oldScrollTop > scrollTop) {
|
||||
this.visible = true
|
||||
} else if (scrollTop > 300 && this.visible) {
|
||||
this.visible = false
|
||||
} else if (scrollTop < 300 && !this.visible) {
|
||||
this.visible = true
|
||||
}
|
||||
// if (this.oldScrollTop > scrollTop) {
|
||||
// // this.visible = true
|
||||
// } else if (scrollTop > 300 && this.visible) {
|
||||
// // this.visible = false
|
||||
// } else if (scrollTop < 300 && !this.visible) {
|
||||
// // this.visible = true
|
||||
// }
|
||||
this.oldScrollTop = scrollTop
|
||||
this.ticking = false
|
||||
})
|
||||
}
|
||||
},
|
||||
toggle () {
|
||||
toggle() {
|
||||
this.$emit('toggle')
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy () {
|
||||
beforeDestroy() {
|
||||
document.body.removeEventListener('scroll', this.handleScroll, true)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '../index.less';
|
||||
|
||||
.header-animat{
|
||||
.header-animat {
|
||||
position: relative;
|
||||
z-index: @ant-global-header-zindex;
|
||||
}
|
||||
@@ -125,7 +131,8 @@ export default {
|
||||
.showHeader-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
.showHeader-enter, .showHeader-leave-to {
|
||||
.showHeader-enter,
|
||||
.showHeader-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,86 +0,0 @@
|
||||
<template>
|
||||
<div :class="prefixCls">
|
||||
<a-tabs v-model="currentTab" @change="handleTabChange">
|
||||
<a-tab-pane v-for="v in icons" :tab="v.title" :key="v.key">
|
||||
<ul>
|
||||
<li v-for="(icon, key) in v.icons" :key="`${v.key}-${key}`" :class="{ 'active': selectedIcon==icon }" @click="handleSelectedIcon(icon)" >
|
||||
<a-icon :type="icon" :style="{ fontSize: '36px' }" />
|
||||
</li>
|
||||
</ul>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import icons from './icons'
|
||||
|
||||
export default {
|
||||
name: 'IconSelect',
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-pro-icon-selector'
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
value: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
selectedIcon: this.value || '',
|
||||
currentTab: 'directional',
|
||||
icons
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.selectedIcon = val
|
||||
this.autoSwitchTab()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.value) {
|
||||
this.autoSwitchTab()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSelectedIcon (icon) {
|
||||
this.selectedIcon = icon
|
||||
this.$emit('change', icon)
|
||||
},
|
||||
handleTabChange (activeKey) {
|
||||
this.currentTab = activeKey
|
||||
},
|
||||
autoSwitchTab () {
|
||||
icons.some(item => item.icons.some(icon => icon === this.value) && (this.currentTab = item.key))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "../index.less";
|
||||
|
||||
ul{
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
overflow-y: scroll;
|
||||
height: 250px;
|
||||
|
||||
li{
|
||||
display: inline-block;
|
||||
padding: @padding-sm;
|
||||
margin: 3px 0;
|
||||
border-radius: @border-radius-base;
|
||||
|
||||
&:hover, &.active{
|
||||
// box-shadow: 0px 0px 5px 2px @primary-color;
|
||||
cursor: pointer;
|
||||
color: @white;
|
||||
background-color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,48 +0,0 @@
|
||||
IconSelector
|
||||
====
|
||||
|
||||
> 图标选择组件,常用于为某一个数据设定一个图标时使用
|
||||
> eg: 设定菜单列表时,为每个菜单设定一个图标
|
||||
|
||||
该组件由 [@Saraka](https://github.com/saraka-tsukai) 封装
|
||||
|
||||
|
||||
|
||||
### 使用方式
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<icon-selector @change="handleIconChange"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconSelector from '@/components/IconSelector'
|
||||
|
||||
export default {
|
||||
name: 'YourView',
|
||||
components: {
|
||||
IconSelector
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleIconChange (icon) {
|
||||
console.log('change Icon', icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 事件
|
||||
|
||||
|
||||
| 名称 | 说明 | 类型 | 默认值 |
|
||||
| ------ | -------------------------- | ------ | ------ |
|
||||
| change | 当改变了 `icon` 选中项触发 | String | - |
|
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* 增加新的图标时,请遵循以下数据结构
|
||||
* Adding new icon please follow the data structure below
|
||||
*/
|
||||
export default [
|
||||
{
|
||||
key: 'directional',
|
||||
title: '方向性图标',
|
||||
icons: ['step-backward', 'step-forward', 'fast-backward', 'fast-forward', 'shrink', 'arrows-alt', 'down', 'up', 'left', 'right', 'caret-up', 'caret-down', 'caret-left', 'caret-right', 'up-circle', 'down-circle', 'left-circle', 'right-circle', 'double-right', 'double-left', 'vertical-left', 'vertical-right', 'forward', 'backward', 'rollback', 'enter', 'retweet', 'swap', 'swap-left', 'swap-right', 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right', 'play-circle', 'up-square', 'down-square', 'left-square', 'right-square', 'login', 'logout', 'menu-fold', 'menu-unfold', 'border-bottom', 'border-horizontal', 'border-inner', 'border-left', 'border-right', 'border-top', 'border-verticle', 'pic-center', 'pic-left', 'pic-right', 'radius-bottomleft', 'radius-bottomright', 'radius-upleft', 'fullscreen', 'fullscreen-exit']
|
||||
},
|
||||
{
|
||||
key: 'suggested',
|
||||
title: '提示建议性图标',
|
||||
icons: ['question', 'question-circle', 'plus', 'plus-circle', 'pause', 'pause-circle', 'minus', 'minus-circle', 'plus-square', 'minus-square', 'info', 'info-circle', 'exclamation', 'exclamation-circle', 'close', 'close-circle', 'close-square', 'check', 'check-circle', 'check-square', 'clock-circle', 'warning', 'issues-close', 'stop']
|
||||
},
|
||||
{
|
||||
key: 'editor',
|
||||
title: '编辑类图标',
|
||||
icons: ['edit', 'form', 'copy', 'scissor', 'delete', 'snippets', 'diff', 'highlight', 'align-center', 'align-left', 'align-right', 'bg-colors', 'bold', 'italic', 'underline', 'strikethrough', 'redo', 'undo', 'zoom-in', 'zoom-out', 'font-colors', 'font-size', 'line-height', 'colum-height', 'dash', 'small-dash', 'sort-ascending', 'sort-descending', 'drag', 'ordered-list', 'radius-setting']
|
||||
},
|
||||
{
|
||||
key: 'data',
|
||||
title: '数据类图标',
|
||||
icons: ['area-chart', 'pie-chart', 'bar-chart', 'dot-chart', 'line-chart', 'radar-chart', 'heat-map', 'fall', 'rise', 'stock', 'box-plot', 'fund', 'sliders']
|
||||
},
|
||||
{
|
||||
key: 'brand_logo',
|
||||
title: '网站通用图标',
|
||||
icons: ['lock', 'unlock', 'bars', 'book', 'calendar', 'cloud', 'cloud-download', 'code', 'copy', 'credit-card', 'delete', 'desktop', 'download', 'ellipsis', 'file', 'file-text', 'file-unknown', 'file-pdf', 'file-word', 'file-excel', 'file-jpg', 'file-ppt', 'file-markdown', 'file-add', 'folder', 'folder-open', 'folder-add', 'hdd', 'frown', 'meh', 'smile', 'inbox', 'laptop', 'appstore', 'link', 'mail', 'mobile', 'notification', 'paper-clip', 'picture', 'poweroff', 'reload', 'search', 'setting', 'share-alt', 'shopping-cart', 'tablet', 'tag', 'tags', 'to-top', 'upload', 'user', 'video-camera', 'home', 'loading', 'loading-3-quarters', 'cloud-upload', 'star', 'heart', 'environment', 'eye', 'camera', 'save', 'team', 'solution', 'phone', 'filter', 'exception', 'export', 'customer-service', 'qrcode', 'scan', 'like', 'dislike', 'message', 'pay-circle', 'calculator', 'pushpin', 'bulb', 'select', 'switcher', 'rocket', 'bell', 'disconnect', 'database', 'compass', 'barcode', 'hourglass', 'key', 'flag', 'layout', 'printer', 'sound', 'usb', 'skin', 'tool', 'sync', 'wifi', 'car', 'schedule', 'user-add', 'user-delete', 'usergroup-add', 'usergroup-delete', 'man', 'woman', 'shop', 'gift', 'idcard', 'medicine-box', 'red-envelope', 'coffee', 'copyright', 'trademark', 'safety', 'wallet', 'bank', 'trophy', 'contacts', 'global', 'shake', 'api', 'fork', 'dashboard', 'table', 'profile', 'alert', 'audit', 'branches', 'build', 'border', 'crown', 'experiment', 'fire', 'money-collect', 'property-safety', 'read', 'reconciliation', 'rest', 'security-scan', 'insurance', 'interation', 'safety-certificate', 'project', 'thunderbolt', 'block', 'cluster', 'deployment-unit', 'dollar', 'euro', 'pound', 'file-done', 'file-exclamation', 'file-protect', 'file-search', 'file-sync', 'gateway', 'gold', 'robot', 'shopping']
|
||||
},
|
||||
{
|
||||
key: 'application',
|
||||
title: '品牌和标识',
|
||||
icons: ['android', 'apple', 'windows', 'ie', 'chrome', 'github', 'aliwangwang', 'dingding', 'weibo-square', 'weibo-circle', 'taobao-circle', 'html5', 'weibo', 'twitter', 'wechat', 'youtube', 'alipay-circle', 'taobao', 'skype', 'qq', 'medium-workmark', 'gitlab', 'medium', 'linkedin', 'google-plus', 'dropbox', 'facebook', 'codepen', 'code-sandbox', 'amazon', 'google', 'codepen-circle', 'alipay', 'ant-design', 'aliyun', 'zhihu', 'slack', 'slack-square', 'behance', 'behance-square', 'dribbble', 'dribbble-square', 'instagram', 'yuque', 'alibaba', 'yahoo']
|
||||
}
|
||||
]
|
@@ -1,2 +0,0 @@
|
||||
import IconSelector from './IconSelector'
|
||||
export default IconSelector
|
@@ -1,27 +1,26 @@
|
||||
<template>
|
||||
<a-layout-sider
|
||||
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null ]"
|
||||
width="256px"
|
||||
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]"
|
||||
width="200px"
|
||||
:collapsible="collapsible"
|
||||
v-model="collapsed"
|
||||
:trigger="null">
|
||||
<logo />
|
||||
:trigger="null"
|
||||
>
|
||||
<logo :collapsed="collapsed" />
|
||||
<s-menu
|
||||
:collapsed="collapsed"
|
||||
:menu="menus"
|
||||
:theme="theme"
|
||||
:mode="mode"
|
||||
:i18n-render="i18nRender"
|
||||
@select="onSelect"
|
||||
style="padding: 16px 0px;"></s-menu>
|
||||
style="padding: 16px 0px;"
|
||||
></s-menu>
|
||||
</a-layout-sider>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Logo from '@/components/tools/Logo'
|
||||
import SMenu from './index'
|
||||
import { i18nRender } from '@/locales'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin'
|
||||
|
||||
export default {
|
||||
@@ -32,35 +31,33 @@ export default {
|
||||
mode: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'inline'
|
||||
default: 'inline',
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'dark'
|
||||
default: 'dark',
|
||||
},
|
||||
collapsible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
menus: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
i18nRender,
|
||||
onSelect (obj) {
|
||||
onSelect(obj) {
|
||||
this.$emit('menuSelect', obj)
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
}
|
||||
watch: {},
|
||||
}
|
||||
</script>
|
||||
|
@@ -1,104 +1,46 @@
|
||||
import { Menu, Icon } from 'ant-design-vue'
|
||||
import router, { resetRouter } from '@/router'
|
||||
import Menu from 'ant-design-vue/es/menu'
|
||||
import Icon from 'ant-design-vue/es/icon'
|
||||
import store from '@/store'
|
||||
import {
|
||||
subscribeCIType,
|
||||
subscribeTreeView,
|
||||
} from '@/modules/cmdb/api/preference'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
import { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
|
||||
import CMDBGrant from '@/modules/cmdb/components/cmdbGrant'
|
||||
|
||||
const menuProps = {
|
||||
menu: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'dark'
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'inline'
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
i18nRender: {
|
||||
type: Function,
|
||||
required: false
|
||||
}
|
||||
}
|
||||
const { Item, SubMenu } = Menu
|
||||
|
||||
const defaultI18nRender = (key) => `${key}`
|
||||
|
||||
// render
|
||||
const renderItem = (h, menu, i18nRender) => {
|
||||
if (!menu.hidden) {
|
||||
// const localeKey = `menu.${menu.name}`
|
||||
// const localeKey = menu.meta && menu.meta.title
|
||||
// i18nRender(localeKey)
|
||||
return menu.children && !menu.hideChildrenInMenu ? renderSubMenu(h, menu, i18nRender) : renderMenuItem(h, menu, i18nRender)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const renderMenuItem = (h, menu, i18nRender) => {
|
||||
const target = menu.meta.target || null
|
||||
const CustomTag = target && 'a' || 'router-link'
|
||||
const props = { to: { name: menu.name } }
|
||||
const attrs = { href: menu.path, target: menu.meta.target }
|
||||
|
||||
if (menu.children && menu.hideChildrenInMenu) {
|
||||
// 把有子菜单的 并且 父菜单是要隐藏子菜单的
|
||||
// 都给子菜单增加一个 hidden 属性
|
||||
// 用来给刷新页面时, selectedKeys 做控制用
|
||||
menu.children.forEach(item => {
|
||||
item.meta = Object.assign(item.meta, { hidden: true })
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu.Item {...{ key: menu.path }}>
|
||||
<CustomTag {...{ props, attrs }}>
|
||||
{renderIcon(h, menu.meta.icon)}
|
||||
<span>{i18nRender(menu.meta.title)}</span>
|
||||
</CustomTag>
|
||||
</Menu.Item>
|
||||
)
|
||||
}
|
||||
|
||||
const renderSubMenu = (h, menu, i18nRender) => {
|
||||
const itemArr = []
|
||||
if (!menu.hideChildrenInMenu) {
|
||||
menu.children.forEach(item => itemArr.push(renderItem(h, item, i18nRender)))
|
||||
}
|
||||
return (
|
||||
<Menu.SubMenu {...{ key: menu.path }}>
|
||||
<span slot="title">
|
||||
{renderIcon(h, menu.meta.icon)}
|
||||
<span>{i18nRender(menu.meta.title)}</span>
|
||||
</span>
|
||||
{itemArr}
|
||||
</Menu.SubMenu>
|
||||
)
|
||||
}
|
||||
|
||||
const renderIcon = (h, icon) => {
|
||||
if (icon === 'none' || icon === undefined) {
|
||||
return null
|
||||
}
|
||||
const props = {}
|
||||
typeof (icon) === 'object' ? props.component = icon : props.type = icon
|
||||
return (
|
||||
<Icon {...{ props }}/>
|
||||
)
|
||||
}
|
||||
|
||||
const SMenu = {
|
||||
export default {
|
||||
name: 'SMenu',
|
||||
props: menuProps,
|
||||
data () {
|
||||
props: {
|
||||
menu: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'dark'
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'inline'
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openKeys: [],
|
||||
selectedKeys: [],
|
||||
cachedOpenKeys: []
|
||||
cachedOpenKeys: [],
|
||||
resource_type: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -106,27 +48,73 @@ const SMenu = {
|
||||
const keys = []
|
||||
vm.menu.forEach(item => keys.push(item.path))
|
||||
return keys
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
resource_type: () => {
|
||||
return this.resource_type
|
||||
},
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$watch('collapsed', collapsed => {
|
||||
if (collapsed) {
|
||||
created() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
searchResourceType({ page_size: 9999, app_id: 'cmdb' }).then(res => {
|
||||
this.resource_type = { groups: res.groups, id2perms: res.id2perms }
|
||||
})
|
||||
this.updateMenu()
|
||||
},
|
||||
watch: {
|
||||
collapsed(val) {
|
||||
if (val) {
|
||||
this.cachedOpenKeys = this.openKeys.concat()
|
||||
this.openKeys = []
|
||||
} else {
|
||||
this.openKeys = this.cachedOpenKeys
|
||||
}
|
||||
})
|
||||
this.$watch('$route', () => {
|
||||
},
|
||||
$route: function () {
|
||||
this.updateMenu()
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
this.updateMenu()
|
||||
},
|
||||
},
|
||||
inject: ['reload'],
|
||||
methods: {
|
||||
// 取消订阅
|
||||
cancelAttributes(e, menu) {
|
||||
const that = this
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
this.$confirm({
|
||||
title: '警告',
|
||||
content: `确认取消订阅 ${menu.meta.title}?`,
|
||||
onOk() {
|
||||
const citypeId = menu.meta.typeId
|
||||
const unsubCIType = subscribeCIType(citypeId, '')
|
||||
const unsubTree = subscribeTreeView(citypeId, '')
|
||||
Promise.all([unsubCIType, unsubTree]).then(() => {
|
||||
that.$message.success('取消订阅成功')
|
||||
// 删除路由
|
||||
const href = window.location.href
|
||||
const hrefSplit = href.split('/')
|
||||
if (Number(hrefSplit[hrefSplit.length - 1]) === Number(citypeId)) {
|
||||
that.$router.push('/cmdb/preference')
|
||||
}
|
||||
const roles = store.getters.roles
|
||||
resetRouter()
|
||||
store.dispatch('GenerateRoutes', { roles }, { root: true }).then(() => {
|
||||
router.addRoutes(store.getters.appRoutes)
|
||||
})
|
||||
if (hrefSplit[hrefSplit.length - 1] === 'preference') {
|
||||
that.reload()
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
// select menu item
|
||||
onOpenChange (openKeys) {
|
||||
onOpenChange(openKeys) {
|
||||
// 在水平模式下时执行,并且不再执行后续
|
||||
if (this.mode === 'horizontal') {
|
||||
this.openKeys = openKeys
|
||||
@@ -140,8 +128,9 @@ const SMenu = {
|
||||
this.openKeys = latestOpenKey ? [latestOpenKey] : []
|
||||
}
|
||||
},
|
||||
updateMenu () {
|
||||
updateMenu() {
|
||||
const routes = this.$route.matched.concat()
|
||||
|
||||
const { hidden } = this.$route.meta
|
||||
if (routes.length >= 3 && hidden) {
|
||||
routes.pop()
|
||||
@@ -157,10 +146,145 @@ const SMenu = {
|
||||
}
|
||||
|
||||
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
|
||||
},
|
||||
// render
|
||||
renderItem(menu) {
|
||||
if (this.collapsed && menu.meta.disabled) {
|
||||
return null
|
||||
}
|
||||
if (!menu.hidden) {
|
||||
return menu.children && !menu.hideChildrenInMenu ? this.renderSubMenu(menu) : this.renderMenuItem(menu)
|
||||
}
|
||||
return null
|
||||
},
|
||||
renderMenuItem(menu) {
|
||||
const isShowDot = menu.path.substr(0, 22) === '/cmdb/instances/types/'
|
||||
const isShowGrant = menu.path.substr(0, 20) === '/cmdb/relationviews/'
|
||||
const target = menu.meta.target || null
|
||||
const tag = target && 'a' || 'router-link'
|
||||
const props = { to: { name: menu.name } }
|
||||
const attrs = { href: menu.meta.targetHref || menu.path, target: menu.meta.target }
|
||||
|
||||
if (menu.children && menu.hideChildrenInMenu) {
|
||||
// 把有子菜单的 并且 父菜单是要隐藏子菜单的
|
||||
// 都给子菜单增加一个 hidden 属性
|
||||
// 用来给刷新页面时, selectedKeys 做控制用
|
||||
menu.children.forEach(item => {
|
||||
item.meta = Object.assign(item.meta, { hidden: true })
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Item {...{ key: menu.path }} disabled={menu.meta.disabled || false}>
|
||||
<tag {...{ props, attrs }}>
|
||||
{this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })}
|
||||
<span>
|
||||
<span class={menu.meta.title.length > 10 ? 'scroll' : ''}>{menu.meta.title}</span>
|
||||
{isShowDot &&
|
||||
<a-popover
|
||||
overlayClassName="custom-menu-extra-submenu"
|
||||
placement="rightTop"
|
||||
arrowPointAtCenter
|
||||
autoAdjustOverflow={false}
|
||||
getPopupContainer={(trigger) => trigger}
|
||||
content={() =>
|
||||
<div>
|
||||
<div onClick={e => this.handlePerm(e, menu, 'CIType')} class="custom-menu-extra-submenu-item"><a-icon type="user-add" />授权</div>
|
||||
<div onClick={e => this.cancelAttributes(e, menu)} class="custom-menu-extra-submenu-item"><a-icon type="star" />取消订阅</div>
|
||||
</div>}
|
||||
>
|
||||
<a-icon type="menu" ref="extraEllipsis" class="custom-menu-extra-ellipsis"></a-icon>
|
||||
</a-popover>
|
||||
}
|
||||
{isShowGrant && <a-icon class="custom-menu-extra-ellipsis" onClick={e => this.handlePerm(e, menu, 'RelationView')} type="user-add" />}
|
||||
</span>
|
||||
</tag>
|
||||
{isShowDot && <CMDBGrant ref="cmdbGrantCIType" resourceType="CIType" app_id="cmdb" />}
|
||||
{isShowGrant && <CMDBGrant ref="cmdbGrantRelationView" resourceType="RelationView" app_id="cmdb" />}
|
||||
</Item>
|
||||
)
|
||||
},
|
||||
renderSubMenu(menu) {
|
||||
const itemArr = []
|
||||
if (!menu.hideChildrenInMenu) {
|
||||
menu.children.forEach(item => itemArr.push(this.renderItem(item)))
|
||||
}
|
||||
return (
|
||||
<SubMenu {...{ key: menu.path }}>
|
||||
<span slot="title">
|
||||
{this.renderIcon({ icon: menu.meta.icon, selectedIcon: menu.meta.selectedIcon, routeName: menu.name })}
|
||||
<span>{menu.meta.title}</span>
|
||||
</span>
|
||||
{itemArr}
|
||||
</SubMenu>
|
||||
)
|
||||
},
|
||||
renderIcon({ icon, selectedIcon, customIcon = undefined, name = undefined, typeId = undefined, routeName }) {
|
||||
if (typeId) {
|
||||
if (customIcon) {
|
||||
return <ops-icon
|
||||
style={{
|
||||
color: customIcon.split('$$')[1],
|
||||
}}
|
||||
type={customIcon.split('$$')[0]}
|
||||
/>
|
||||
}
|
||||
return <span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: '#d3d3d3',
|
||||
color: '#fff',
|
||||
textAlign: 'center',
|
||||
lineHeight: '14px',
|
||||
fontSize: '10px',
|
||||
marginRight: '10px'
|
||||
}}
|
||||
>{name[0].toUpperCase()}</span>
|
||||
}
|
||||
|
||||
if (icon === 'none' || icon === undefined) {
|
||||
return null
|
||||
}
|
||||
const props = {}
|
||||
if (this.$route.name === routeName && selectedIcon) {
|
||||
return <ops-icon type={selectedIcon}></ops-icon>
|
||||
} else if (icon.startsWith('ops-') || icon.startsWith('icon-xianxing') || icon.startsWith('icon-shidi')) {
|
||||
return <ops-icon type={icon}></ops-icon>
|
||||
} else {
|
||||
typeof (icon) === 'object' ? props.component = icon : props.type = icon
|
||||
return (
|
||||
<Icon {... { props }} />
|
||||
)
|
||||
}
|
||||
},
|
||||
handlePerm(e, menu, resource_type_name) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
roleHasPermissionToGrant({
|
||||
app_id: 'cmdb',
|
||||
resource_type_name,
|
||||
perm: 'grant',
|
||||
resource_name: menu.meta.name,
|
||||
}).then(res => {
|
||||
if (res.result) {
|
||||
console.log(menu)
|
||||
if (resource_type_name === 'CIType') {
|
||||
this.$refs.cmdbGrantCIType.open({ name: menu.meta.name, cmdbGrantType: 'ci', CITypeId: menu.meta?.typeId })
|
||||
} else {
|
||||
this.$refs.cmdbGrantRelationView.open({ name: menu.meta.name, cmdbGrantType: 'relation_view' })
|
||||
}
|
||||
} else {
|
||||
this.$message.error('权限不足!')
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
const { mode, theme, menu, i18nRender = defaultI18nRender } = this
|
||||
|
||||
render() {
|
||||
const { mode, theme, menu } = this
|
||||
const props = {
|
||||
mode: mode,
|
||||
theme: theme,
|
||||
@@ -168,7 +292,7 @@ const SMenu = {
|
||||
}
|
||||
const on = {
|
||||
select: obj => {
|
||||
this.selectedKeys = obj.selectedKeys
|
||||
// this.selectedKeys = obj.selectedKeys
|
||||
this.$emit('select', obj)
|
||||
},
|
||||
openChange: this.onOpenChange
|
||||
@@ -178,15 +302,13 @@ const SMenu = {
|
||||
if (item.hidden) {
|
||||
return null
|
||||
}
|
||||
return renderItem(h, item, i18nRender)
|
||||
return this.renderItem(item)
|
||||
})
|
||||
// {...{ props, on: on }}
|
||||
return (
|
||||
<Menu vModel={this.selectedKeys} {...{ props, on: on }}>
|
||||
<Menu class="ops-side-bar" selectedKeys={this.selectedKeys} {...{ props, on: on }}>
|
||||
{menuTree}
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default SMenu
|
||||
|
@@ -1,156 +0,0 @@
|
||||
import Menu from 'ant-design-vue/es/menu'
|
||||
import Icon from 'ant-design-vue/es/icon'
|
||||
|
||||
const { Item, SubMenu } = Menu
|
||||
|
||||
export default {
|
||||
name: 'SMenu',
|
||||
props: {
|
||||
menu: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'dark'
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'inline'
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
openKeys: [],
|
||||
selectedKeys: [],
|
||||
cachedOpenKeys: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rootSubmenuKeys: vm => {
|
||||
const keys = []
|
||||
vm.menu.forEach(item => keys.push(item.path))
|
||||
return keys
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.updateMenu()
|
||||
},
|
||||
watch: {
|
||||
collapsed (val) {
|
||||
if (val) {
|
||||
this.cachedOpenKeys = this.openKeys.concat()
|
||||
this.openKeys = []
|
||||
} else {
|
||||
this.openKeys = this.cachedOpenKeys
|
||||
}
|
||||
},
|
||||
$route: function () {
|
||||
this.updateMenu()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderIcon: function (h, icon) {
|
||||
if (icon === 'none' || icon === undefined) {
|
||||
return null
|
||||
}
|
||||
const props = {}
|
||||
typeof (icon) === 'object' ? props.component = icon : props.type = icon
|
||||
return h(Icon, { props: { ...props } })
|
||||
},
|
||||
renderMenuItem: function (h, menu, pIndex, index) {
|
||||
const target = menu.meta.target || null
|
||||
return h(Item, { key: menu.path ? menu.path : 'item_' + pIndex + '_' + index }, [
|
||||
h('router-link', { attrs: { to: { name: menu.name }, target: target } }, [
|
||||
this.renderIcon(h, menu.meta.icon),
|
||||
h('span', [menu.meta.title])
|
||||
])
|
||||
])
|
||||
},
|
||||
renderSubMenu: function (h, menu, pIndex, index) {
|
||||
const this2_ = this
|
||||
const subItem = [h('span', { slot: 'title' }, [this.renderIcon(h, menu.meta.icon), h('span', [menu.meta.title])])]
|
||||
const itemArr = []
|
||||
const pIndex_ = pIndex + '_' + index
|
||||
console.log('menu', menu)
|
||||
if (!menu.hideChildrenInMenu) {
|
||||
menu.children.forEach(function (item, i) {
|
||||
itemArr.push(this2_.renderItem(h, item, pIndex_, i))
|
||||
})
|
||||
}
|
||||
return h(SubMenu, { key: menu.path ? menu.path : 'submenu_' + pIndex + '_' + index }, subItem.concat(itemArr))
|
||||
},
|
||||
renderItem: function (h, menu, pIndex, index) {
|
||||
if (!menu.hidden) {
|
||||
return menu.children && !menu.hideChildrenInMenu
|
||||
? this.renderSubMenu(h, menu, pIndex, index)
|
||||
: this.renderMenuItem(h, menu, pIndex, index)
|
||||
}
|
||||
},
|
||||
renderMenu: function (h, menuTree) {
|
||||
const this2_ = this
|
||||
const menuArr = []
|
||||
menuTree.forEach(function (menu, i) {
|
||||
if (!menu.hidden) {
|
||||
menuArr.push(this2_.renderItem(h, menu, '0', i))
|
||||
}
|
||||
})
|
||||
return menuArr
|
||||
},
|
||||
onOpenChange (openKeys) {
|
||||
const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
|
||||
if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
|
||||
this.openKeys = openKeys
|
||||
} else {
|
||||
this.openKeys = latestOpenKey ? [latestOpenKey] : []
|
||||
}
|
||||
},
|
||||
updateMenu () {
|
||||
const routes = this.$route.matched.concat()
|
||||
|
||||
if (routes.length >= 4 && this.$route.meta.hidden) {
|
||||
routes.pop()
|
||||
this.selectedKeys = [routes[2].path]
|
||||
} else {
|
||||
this.selectedKeys = [routes.pop().path]
|
||||
}
|
||||
|
||||
const openKeys = []
|
||||
if (this.mode === 'inline') {
|
||||
routes.forEach(item => {
|
||||
openKeys.push(item.path)
|
||||
})
|
||||
}
|
||||
|
||||
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
return h(
|
||||
Menu,
|
||||
{
|
||||
props: {
|
||||
theme: this.$props.theme,
|
||||
mode: this.$props.mode,
|
||||
openKeys: this.openKeys,
|
||||
selectedKeys: this.selectedKeys
|
||||
},
|
||||
on: {
|
||||
openChange: this.onOpenChange,
|
||||
select: obj => {
|
||||
this.selectedKeys = obj.selectedKeys
|
||||
this.$emit('select', obj)
|
||||
}
|
||||
}
|
||||
},
|
||||
this.renderMenu(h, this.menu)
|
||||
)
|
||||
}
|
||||
}
|
2
cmdb-ui/src/components/MonitorNodeSetting/index.js
Normal file
2
cmdb-ui/src/components/MonitorNodeSetting/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import MonitorNodeSetting from './index.vue'
|
||||
export default MonitorNodeSetting
|
168
cmdb-ui/src/components/MonitorNodeSetting/index.vue
Normal file
168
cmdb-ui/src/components/MonitorNodeSetting/index.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-form-item v-for="(node, index) in nodes" :key="node.id">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="6" :offset="1">
|
||||
<a-form-item :label="index ? '' : 'ip地址'">
|
||||
<a-input
|
||||
allowClear
|
||||
size="small"
|
||||
v-decorator="[
|
||||
`node_ip_${node.id}`,
|
||||
{
|
||||
rules: [
|
||||
{ required: false, message: '请输入ip地址' },
|
||||
{
|
||||
pattern:
|
||||
'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$',
|
||||
message: 'ip地址格式错误',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
]"
|
||||
placeholder="请输入ip地址"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6">
|
||||
<a-form-item :label="index ? '' : 'community'" colon>
|
||||
<a-input
|
||||
allowClear
|
||||
size="small"
|
||||
v-decorator="[
|
||||
`node_community_${node.id}`,
|
||||
{
|
||||
rules: [{ required: false, message: '请输入community' }],
|
||||
},
|
||||
]"
|
||||
placeholder="请输入community"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6">
|
||||
<a-form-item :label="index ? '' : '版本'" colon>
|
||||
<a-select
|
||||
size="small"
|
||||
v-decorator="[
|
||||
`node_version_${node.id}`,
|
||||
{
|
||||
rules: [{ required: false, message: '请输入版本' }],
|
||||
},
|
||||
]"
|
||||
placeholder="请选择版本"
|
||||
>
|
||||
<a-select-option value="1">
|
||||
v1
|
||||
</a-select-option>
|
||||
<a-select-option value="2c">
|
||||
v2c
|
||||
</a-select-option>
|
||||
<a-select-option value="3">
|
||||
v3
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<a-form-item :label="index ? '' : ' '" :colon="false">
|
||||
<a @click="() => removeNode(node.id, 1)" :style="{ color: 'red' }">
|
||||
<a-icon type="delete" />
|
||||
</a>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item style="text-align: center">
|
||||
<a-button type="dashed" style="width: 30%;" @click="addNode">
|
||||
<a-icon type="plus" />
|
||||
添加节点
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export default {
|
||||
name: 'MonitorNodeSetting',
|
||||
props: {
|
||||
initNodes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nodes: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initNodesFunc() {
|
||||
this.nodes = _.cloneDeep(this.initNodes)
|
||||
},
|
||||
addNode() {
|
||||
const newNode = {
|
||||
id: uuidv4(),
|
||||
ip: '',
|
||||
community: '',
|
||||
version: '',
|
||||
}
|
||||
this.nodes.push(newNode)
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
[`node_ip_${newNode.id}`]: newNode.ip,
|
||||
[`node_community_${newNode.id}`]: newNode.community,
|
||||
[`node_version_${newNode.id}`]: newNode.version,
|
||||
})
|
||||
})
|
||||
},
|
||||
removeNode(removeId, minLength) {
|
||||
if (this.nodes.length <= minLength) {
|
||||
this.$message.error('不可再删除!')
|
||||
return
|
||||
}
|
||||
const _idx = this.nodes.findIndex((item) => item.id === removeId)
|
||||
if (_idx > -1) {
|
||||
this.nodes.splice(_idx, 1)
|
||||
}
|
||||
},
|
||||
getInfoValuesFromForm(values) {
|
||||
return this.nodes.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
ip: values[`node_ip_${item.id}`],
|
||||
community: values[`node_community_${item.id}`],
|
||||
version: values[`node_version_${item.id}`],
|
||||
}
|
||||
})
|
||||
},
|
||||
setNodeField() {
|
||||
if (this.nodes && this.nodes.length) {
|
||||
this.nodes.forEach((item) => {
|
||||
this.form.setFieldsValue({
|
||||
[`node_ip_${item.id}`]: item.ip,
|
||||
[`node_community_${item.id}`]: item.community,
|
||||
[`node_version_${item.id}`]: item.version,
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
getNodeValue() {
|
||||
const values = this.form.getFieldsValue()
|
||||
return this.getInfoValuesFromForm(values)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<a-popover
|
||||
v-model="visible"
|
||||
trigger="click"
|
||||
placement="bottomRight"
|
||||
overlayClassName="header-notice-wrapper"
|
||||
:getPopupContainer="() => $refs.noticeRef.parentElement"
|
||||
:autoAdjustOverflow="true"
|
||||
:arrowPointAtCenter="true"
|
||||
:overlayStyle="{ width: '300px', top: '50px' }"
|
||||
>
|
||||
<template slot="content">
|
||||
<a-spin :spinning="loadding">
|
||||
<a-tabs>
|
||||
<a-tab-pane tab="通知" key="1">
|
||||
<a-list>
|
||||
<a-list-item>
|
||||
<a-list-item-meta title="你收到了 14 份新周报" description="一年前">
|
||||
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"/>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前">
|
||||
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前">
|
||||
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="消息" key="2">
|
||||
123
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="待办" key="3">
|
||||
123
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-spin>
|
||||
</template>
|
||||
<span @click="fetchNotice" class="header-notice" ref="noticeRef">
|
||||
<a-badge count="12">
|
||||
<a-icon style="font-size: 16px; padding: 4px" type="bell" />
|
||||
</a-badge>
|
||||
</span>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HeaderNotice',
|
||||
data () {
|
||||
return {
|
||||
loadding: false,
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchNotice () {
|
||||
if (!this.visible) {
|
||||
this.loadding = true
|
||||
setTimeout(() => {
|
||||
this.loadding = false
|
||||
}, 2000)
|
||||
} else {
|
||||
this.loadding = false
|
||||
}
|
||||
this.visible = !this.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
.header-notice-wrapper {
|
||||
top: 50px !important;
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
.header-notice{
|
||||
display: inline-block;
|
||||
transition: all 0.3s;
|
||||
|
||||
span {
|
||||
vertical-align: initial;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,2 +0,0 @@
|
||||
import NoticeIcon from './NoticeIcon'
|
||||
export default NoticeIcon
|
@@ -1,54 +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: 'ant-pro-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 "index";
|
||||
</style>
|
@@ -1,3 +0,0 @@
|
||||
import NumberInfo from './NumberInfo'
|
||||
|
||||
export default NumberInfo
|
@@ -1,55 +0,0 @@
|
||||
@import "../index";
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
# NumberInfo 数据文本
|
||||
|
||||
常用在数据卡片中,用于突出展示某个业务数据。
|
||||
|
||||
|
||||
|
||||
引用方式:
|
||||
|
||||
```javascript
|
||||
import NumberInfo from '@/components/NumberInfo'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NumberInfo
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 代码演示 [demo](https://pro.loacg.com/test/home)
|
||||
|
||||
```html
|
||||
<number-info
|
||||
:sub-title="() => { return 'Visits this week' }"
|
||||
:total="12321"
|
||||
status="up"
|
||||
:sub-total="17.1"></number-info>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## API
|
||||
|
||||
参数 | 说明 | 类型 | 默认值
|
||||
----|------|-----|------
|
||||
title | 标题 | ReactNode\|string | -
|
||||
subTitle | 子标题 | ReactNode\|string | -
|
||||
total | 总量 | ReactNode\|string | -
|
||||
subTotal | 子总量 | ReactNode\|string | -
|
||||
status | 增加状态 | 'up \| down' | -
|
||||
theme | 状态样式 | string | 'light'
|
||||
gap | 设置数字和描述之间的间距(像素)| number | 8
|
2
cmdb-ui/src/components/OpsTable/index.js
Normal file
2
cmdb-ui/src/components/OpsTable/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import OpsTable from './index.vue'
|
||||
export default OpsTable
|
121
cmdb-ui/src/components/OpsTable/index.vue
Normal file
121
cmdb-ui/src/components/OpsTable/index.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable">
|
||||
<slot></slot>
|
||||
<template #empty>
|
||||
<slot name="empty">
|
||||
<div>
|
||||
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>暂无数据</div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<template #loading>
|
||||
<slot name="loading"></slot>
|
||||
</template>
|
||||
</vxe-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
// 该组件使用方法与vxe-table一致,但调用它的方法时,需先调用getVxetableRef()获取到vxe-table实体
|
||||
export default {
|
||||
name: 'OpsTable',
|
||||
data() {
|
||||
return {
|
||||
// isShifting: false,
|
||||
// lastIndex: -1,
|
||||
lastSelected: [],
|
||||
currentSelected: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
new$listeners() {
|
||||
if (!Object.keys(this.$listeners).length) {
|
||||
return this.$listeners
|
||||
}
|
||||
return Object.assign(this.$listeners, {
|
||||
// 在这里覆盖原有的change事件
|
||||
// 'checkbox-change': this.selectChangeEvent,
|
||||
'checkbox-range-change': this.checkboxRangeChange,
|
||||
'checkbox-range-start': this.checkboxRangeStart,
|
||||
'checkbox-range-end': this.checkboxRangeEnd,
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// window.onkeydown = (e) => {
|
||||
// if (e.key === 'Shift') {
|
||||
// this.isShifting = true
|
||||
// }
|
||||
// }
|
||||
// window.onkeyup = (e) => {
|
||||
// if (e.key === 'Shift') {
|
||||
// this.isShifting = false
|
||||
// this.lastIndex = -1
|
||||
// }
|
||||
// }
|
||||
},
|
||||
beforeDestroy() {
|
||||
// window.onkeydown = ''
|
||||
// window.onkeyup = ''
|
||||
},
|
||||
methods: {
|
||||
getVxetableRef() {
|
||||
return this.$refs.xTable
|
||||
},
|
||||
// selectChangeEvent(e) {
|
||||
// const xTable = this.$refs.xTable
|
||||
// const { lastIndex } = this
|
||||
// const currentIndex = e.rowIndex
|
||||
// const { tableData } = xTable.getTableData()
|
||||
// if (lastIndex > -1 && this.isShifting) {
|
||||
// let start = lastIndex
|
||||
// let end = currentIndex
|
||||
// if (lastIndex > currentIndex) {
|
||||
// start = currentIndex
|
||||
// end = lastIndex
|
||||
// }
|
||||
// const rangeData = tableData.slice(start, end + 1)
|
||||
// xTable.setCheckboxRow(rangeData, true)
|
||||
// }
|
||||
// this.lastIndex = currentIndex
|
||||
// this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() })
|
||||
// },
|
||||
checkboxRangeStart(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
const lastSelected = xTable.getCheckboxRecords()
|
||||
const selectedReserve = xTable.getCheckboxReserveRecords()
|
||||
this.lastSelected = [...lastSelected, ...selectedReserve]
|
||||
this.$emit('checkbox-range-start', e)
|
||||
},
|
||||
checkboxRangeChange(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
xTable.setCheckboxRow(this.lastSelected, true)
|
||||
this.currentSelected = e.records
|
||||
// this.lastSelected = [...new Set([...this.lastSelected, ...e.records])]
|
||||
this.$emit('checkbox-range-change', {
|
||||
...e,
|
||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||
})
|
||||
},
|
||||
checkboxRangeEnd(e) {
|
||||
const xTable = this.$refs.xTable
|
||||
const isAllSelected = this.currentSelected.every((item) => {
|
||||
const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item))
|
||||
return _idx > -1
|
||||
})
|
||||
if (isAllSelected) {
|
||||
xTable.setCheckboxRow(this.currentSelected, false)
|
||||
}
|
||||
this.currentSelected = []
|
||||
this.lastSelected = []
|
||||
this.$emit('checkbox-range-end', {
|
||||
...e,
|
||||
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="page-header">
|
||||
<div class="page-header-index-wide">
|
||||
<s-breadcrumb :i18n-render="i18nRender" />
|
||||
<s-breadcrumb v-if="isShowBreadcrumb"/>
|
||||
<div class="detail">
|
||||
<div class="main" v-if="!$route.meta.hiddenHeaderContent">
|
||||
<div class="row">
|
||||
<img v-if="logo" :src="logo" class="logo"/>
|
||||
<img v-if="logo" :src="logo" class="logo" />
|
||||
<h1 v-if="title" class="title">{{ title }}</h1>
|
||||
<div class="action">
|
||||
<slot name="action"></slot>
|
||||
@@ -32,38 +32,37 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
import Breadcrumb from '@/components/tools/Breadcrumb'
|
||||
import { i18nRender } from '@/locales'
|
||||
|
||||
export default {
|
||||
name: 'PageHeader',
|
||||
components: {
|
||||
's-breadcrumb': Breadcrumb
|
||||
's-breadcrumb': Breadcrumb,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: [String, Boolean],
|
||||
default: true,
|
||||
required: false
|
||||
required: false,
|
||||
},
|
||||
logo: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
required: false,
|
||||
},
|
||||
avatar: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
required: false,
|
||||
},
|
||||
isShowBreadcrumb: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods () {
|
||||
i18nRender
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -155,7 +155,7 @@
|
||||
<a-alert type="warning" :style="{ marginTop: '24px' }">
|
||||
<span slot="message">
|
||||
配置栏只在开发环境用于预览,生产环境不会展现,请手动修改配置文件
|
||||
<a href="https://github.com/sendya/ant-design-pro-vue/blob/master/src/config/defaultSettings.js" target="_blank">src/config/defaultSettings.js</a>
|
||||
<a href="https://github.com/sendya/ant-design-pro-vue/blob/master/src/config/setting.js" target="_blank">src/config/setting.js</a>
|
||||
</span>
|
||||
</a-alert>
|
||||
</div>
|
||||
@@ -169,15 +169,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DetailList } from '@/components'
|
||||
import SettingItem from './SettingItem'
|
||||
import config from '@/config/defaultSettings'
|
||||
import config from '@/config/setting'
|
||||
import { updateTheme, updateColorWeak, colorList } from './settingConfig'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DetailList,
|
||||
SettingItem
|
||||
},
|
||||
mixins: [mixin, mixinDevice],
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { message } from 'ant-design-vue/es'
|
||||
// import defaultSettings from '../defaultSettings';
|
||||
// import setting from '../setting';
|
||||
import themeColor from './themeColor.js'
|
||||
|
||||
// let lessNodesAppended
|
||||
|
125
cmdb-ui/src/components/SidebarList/SidebarList.vue
Normal file
125
cmdb-ui/src/components/SidebarList/SidebarList.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
:class="{
|
||||
'sidebar-list-item': true,
|
||||
'sidebar-list-item-dotline': dotLine,
|
||||
'sidebar-list-item-selected': selected[`${value}`] === item[`${value}`],
|
||||
}"
|
||||
v-for="item in list"
|
||||
:key="item[`${value}`]"
|
||||
@click="
|
||||
() => {
|
||||
selected = item
|
||||
$emit('clickItem', item)
|
||||
}
|
||||
"
|
||||
>
|
||||
<div class="sidebar-list-label" :title="item[`${label}`]">
|
||||
<slot name="icon" :item="item"></slot>
|
||||
<slot name="label" :item="item">{{ item[`${label}`] }}</slot>
|
||||
</div>
|
||||
<a-space class="sidebar-list-action"><slot name="action"> </slot></a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SidebarList',
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => {},
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: 'name',
|
||||
},
|
||||
dotLine: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selected: {},
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
setSelected(item) {
|
||||
this.selected = item
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.sidebar-list-item {
|
||||
.ops_popover_item();
|
||||
margin: 2px 0;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.sidebar-list-label {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.sidebar-list-action {
|
||||
margin-left: auto;
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
.sidebar-list-action {
|
||||
display: inline-flex;
|
||||
}
|
||||
.sidebar-list-label {
|
||||
width: calc(100% - 36px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-list-item-selected {
|
||||
.ops_popover_item_selected();
|
||||
background-color: transparent;
|
||||
}
|
||||
.sidebar-list-item.sidebar-list-item-selected::before {
|
||||
background-color: #custom_colors[color_1];
|
||||
}
|
||||
.sidebar-list-item-dotline {
|
||||
padding-left: 20px;
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: 10px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background-color: #cacaca;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-list-item-dotline:not(:last-child)::after {
|
||||
content: '';
|
||||
width: 1px;
|
||||
height: 31px;
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
background-color: #cacaca;
|
||||
top: 15px;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
2
cmdb-ui/src/components/SidebarList/index.js
Normal file
2
cmdb-ui/src/components/SidebarList/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import SidebarList from './SidebarList'
|
||||
export default SidebarList
|
179
cmdb-ui/src/components/SplitPane/SplitPane.vue
Normal file
179
cmdb-ui/src/components/SplitPane/SplitPane.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div ref="splitPane" class="split-pane" :class="direction + ' ' + appName" :style="{ flexDirection: direction }">
|
||||
<div class="pane pane-one" ref="one" :style="lengthType + ':' + paneLengthValue1">
|
||||
<slot name="one"></slot>
|
||||
</div>
|
||||
|
||||
<div class="spliter-wrap">
|
||||
<a-button
|
||||
v-show="collapsable"
|
||||
:icon="isExpanded ? 'left' : 'right'"
|
||||
class="collapse-btn"
|
||||
@click="handleExpand"
|
||||
></a-button>
|
||||
<div
|
||||
class="pane-trigger"
|
||||
@mousedown="handleMouseDown"
|
||||
:style="{ backgroundColor: triggerColor, width: `${triggerLength}px` }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="pane pane-two" ref="two" :style="lengthType + ':' + paneLengthValue2">
|
||||
<slot name="two"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SplitPane',
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row',
|
||||
},
|
||||
|
||||
min: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
|
||||
max: {
|
||||
type: Number,
|
||||
default: 90,
|
||||
},
|
||||
|
||||
paneLengthPixel: {
|
||||
type: Number,
|
||||
default: 220,
|
||||
},
|
||||
|
||||
triggerLength: {
|
||||
type: Number,
|
||||
default: 8,
|
||||
},
|
||||
|
||||
appName: {
|
||||
type: String,
|
||||
default: 'viewer',
|
||||
},
|
||||
|
||||
collapsable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
triggerColor: {
|
||||
type: String,
|
||||
default: '#f0f2f5',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
triggerLeftOffset: 0, // 鼠标距滑动器左(顶)侧偏移量
|
||||
isExpanded: localStorage.getItem(`${this.appName}-isExpanded`)
|
||||
? JSON.parse(localStorage.getItem(`${this.appName}-isExpanded`))
|
||||
: false,
|
||||
parentContainer: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lengthType() {
|
||||
return this.direction === 'row' ? 'width' : 'height'
|
||||
},
|
||||
|
||||
minLengthType() {
|
||||
return this.direction === 'row' ? 'minWidth' : 'minHeight'
|
||||
},
|
||||
|
||||
paneLengthValue1() {
|
||||
return `calc(${this.paneLengthPercent}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthValue2() {
|
||||
const rest = 100 - this.paneLengthPercent
|
||||
return `calc(${rest}% - ${this.triggerLength / 2 + 'px'})`
|
||||
},
|
||||
|
||||
paneLengthPercent() {
|
||||
const clientRectWidth = this.parentContainer
|
||||
? this.parentContainer.clientWidth
|
||||
: document.documentElement.getBoundingClientRect().width
|
||||
return (this.paneLengthPixel / clientRectWidth) * 100
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
isExpanded(newValue) {
|
||||
if (newValue) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.parentContainer = document.querySelector(`.${this.appName}`)
|
||||
if (this.isExpanded) {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = 'none'
|
||||
} else {
|
||||
document.querySelector(`.${this.appName} .pane-two`).style.display = ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 按下滑动器
|
||||
handleMouseDown(e) {
|
||||
document.addEventListener('mousemove', this.handleMouseMove)
|
||||
document.addEventListener('mouseup', this.handleMouseUp)
|
||||
if (this.direction === 'row') {
|
||||
this.triggerLeftOffset = e.pageX - e.srcElement.getBoundingClientRect().left
|
||||
} else {
|
||||
this.triggerLeftOffset = e.pageY - e.srcElement.getBoundingClientRect().top
|
||||
}
|
||||
},
|
||||
|
||||
// 按下滑动器后移动鼠标
|
||||
handleMouseMove(e) {
|
||||
this.isExpanded = false
|
||||
this.$emit('expand', this.isExpanded)
|
||||
const clientRect = this.$refs.splitPane.getBoundingClientRect()
|
||||
let paneLengthPixel = 0
|
||||
|
||||
if (this.direction === 'row') {
|
||||
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
} else {
|
||||
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
|
||||
paneLengthPixel = offset
|
||||
}
|
||||
|
||||
if (paneLengthPixel < this.min) {
|
||||
paneLengthPixel = this.min
|
||||
}
|
||||
if (paneLengthPixel > this.max) {
|
||||
paneLengthPixel = this.max
|
||||
}
|
||||
|
||||
this.$emit('update:paneLengthPixel', paneLengthPixel)
|
||||
|
||||
localStorage.setItem(`${this.appName}-paneLengthPixel`, paneLengthPixel)
|
||||
},
|
||||
|
||||
// 松开滑动器
|
||||
handleMouseUp() {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove)
|
||||
},
|
||||
|
||||
handleExpand() {
|
||||
this.isExpanded = !this.isExpanded
|
||||
this.$emit('expand', this.isExpanded)
|
||||
localStorage.setItem(`${this.appName}-isExpanded`, this.isExpanded)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import './index.less';
|
||||
</style>
|
2
cmdb-ui/src/components/SplitPane/index.js
Normal file
2
cmdb-ui/src/components/SplitPane/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import SplitPane from './SplitPane'
|
||||
export default SplitPane
|
48
cmdb-ui/src/components/SplitPane/index.less
Normal file
48
cmdb-ui/src/components/SplitPane/index.less
Normal file
@@ -0,0 +1,48 @@
|
||||
.split-pane {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.split-pane .pane-two {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.split-pane .pane-trigger {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.split-pane.row .pane-one {
|
||||
width: 20%;
|
||||
height: 100%;
|
||||
// overflow-y: auto;
|
||||
}
|
||||
|
||||
.split-pane.column .pane {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.split-pane.row .pane-trigger {
|
||||
width: 8px;
|
||||
height: 100%;
|
||||
cursor: e-resize;
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAPCAYAAADDNm69AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAeSURBVBhXY/4PBMzMzA379u1rANFMDGhgGAswMAAAn6EH6K9ktYAAAAAASUVORK5CYII=')
|
||||
1px 50% no-repeat #f0f2f5;
|
||||
}
|
||||
|
||||
.split-pane .collapse-btn {
|
||||
width: 25px;
|
||||
height: 70px;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: calc(50% - 35px);
|
||||
background-color: #f0f2f5;
|
||||
border-color: transparent;
|
||||
border-radius: 8px 0px 0px 8px;
|
||||
.anticon {
|
||||
color: #7cb0fe;
|
||||
}
|
||||
}
|
||||
|
||||
.split-pane .spliter-wrap {
|
||||
position: relative;
|
||||
}
|
@@ -1,122 +0,0 @@
|
||||
<template>
|
||||
<div :class="[prefixCls, lastCls, blockCls, gridCls]">
|
||||
<div v-if="title" class="antd-pro-components-standard-form-row-index-label">
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
<div class="antd-pro-components-standard-form-row-index-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const classes = [
|
||||
'antd-pro-components-standard-form-row-index-standardFormRowBlock',
|
||||
'antd-pro-components-standard-form-row-index-standardFormRowGrid',
|
||||
'antd-pro-components-standard-form-row-index-standardFormRowLast'
|
||||
]
|
||||
export default {
|
||||
name: 'StandardFormRow',
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'antd-pro-components-standard-form-row-index-standardFormRow'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
last: {
|
||||
type: Boolean
|
||||
},
|
||||
block: {
|
||||
type: Boolean
|
||||
},
|
||||
grid: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lastCls () {
|
||||
return this.last ? classes[2] : null
|
||||
},
|
||||
blockCls () {
|
||||
return this.block ? classes[0] : null
|
||||
},
|
||||
gridCls () {
|
||||
return this.grid ? classes[1] : null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '../index.less';
|
||||
|
||||
.antd-pro-components-standard-form-row-index-standardFormRow {
|
||||
display: flex;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px dashed @border-color-split;
|
||||
|
||||
/deep/ .ant-form-item {
|
||||
margin-right: 24px;
|
||||
}
|
||||
/deep/ .ant-form-item-label label {
|
||||
margin-right: 0;
|
||||
color: @text-color;
|
||||
}
|
||||
/deep/ .ant-form-item-label,
|
||||
.ant-form-item-control {
|
||||
padding: 0;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.antd-pro-components-standard-form-row-index-label {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 24px;
|
||||
color: @heading-color;
|
||||
font-size: @font-size-base;
|
||||
text-align: right;
|
||||
& > span {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
&::after {
|
||||
content: ':';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.antd-pro-components-standard-form-row-index-content {
|
||||
flex: 1 1 0;
|
||||
/deep/ .ant-form-item:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.antd-pro-components-standard-form-row-index-standardFormRowLast {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.antd-pro-components-standard-form-row-index-standardFormRowBlock {
|
||||
/deep/ .ant-form-item,
|
||||
div.ant-form-item-control-wrapper {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.antd-pro-components-standard-form-row-index-standardFormRowGrid {
|
||||
/deep/ .ant-form-item,
|
||||
div.ant-form-item-control-wrapper {
|
||||
display: block;
|
||||
}
|
||||
/deep/ .ant-form-item-label {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@@ -1,3 +0,0 @@
|
||||
import StandardFormRow from './StandardFormRow'
|
||||
|
||||
export default StandardFormRow
|
@@ -1,328 +0,0 @@
|
||||
import T from 'ant-design-vue/es/table/Table'
|
||||
import get from 'lodash.get'
|
||||
import i18n from '@/locales'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
needTotalList: [],
|
||||
|
||||
selectedRows: [],
|
||||
selectedRowKeys: [],
|
||||
|
||||
localLoading: false,
|
||||
localDataSource: [],
|
||||
localPagination: Object.assign({}, this.pagination)
|
||||
}
|
||||
},
|
||||
props: Object.assign({}, T.props, {
|
||||
rowKey: {
|
||||
type: [String, Function],
|
||||
default: 'key'
|
||||
},
|
||||
loaded: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
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'
|
||||
},
|
||||
alert: {
|
||||
type: [Object, Boolean],
|
||||
default: null
|
||||
},
|
||||
rowSelection: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
/** @Deprecated */
|
||||
showAlertInfo: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showPagination: {
|
||||
type: String | Boolean,
|
||||
default: 'auto'
|
||||
},
|
||||
/**
|
||||
* enable page URI mode
|
||||
*
|
||||
* e.g:
|
||||
* /users/1
|
||||
* /users/2
|
||||
* /users/3?queryParam=test
|
||||
* ...
|
||||
*/
|
||||
pageURI: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}),
|
||||
watch: {
|
||||
'localPagination.current' (val) {
|
||||
this.pageURI && this.$router.push({
|
||||
...this.$route,
|
||||
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
|
||||
})
|
||||
},
|
||||
'$route.path': function (newPath, oldPath) {
|
||||
if (oldPath.indexOf(newPath) === -1) {
|
||||
this.refresh(true)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const { pageNo } = this.$route.params
|
||||
const localPageNum = this.pageURI && (pageNo && parseInt(pageNo)) || this.pageNum
|
||||
this.localPagination = ['auto', true].includes(this.showPagination) && Object.assign({}, this.localPagination, {
|
||||
current: localPageNum,
|
||||
pageSize: this.pageSize,
|
||||
showSizeChanger: this.showSizeChanger
|
||||
}) || false
|
||||
console.log('this.localPagination', this.localPagination)
|
||||
this.needTotalList = this.initTotalList(this.columns)
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 表格重新加载方法
|
||||
* 如果参数为 true, 则强制刷新到第一页
|
||||
* @param Boolean bool
|
||||
*/
|
||||
refresh (bool = false) {
|
||||
bool && (this.localPagination = Object.assign({}, {
|
||||
current: 1, pageSize: this.pageSize
|
||||
}))
|
||||
this.loadData()
|
||||
},
|
||||
/**
|
||||
* 加载数据方法
|
||||
* @param {Object} pagination 分页选项器
|
||||
* @param {Object} filters 过滤条件
|
||||
* @param {Object} sorter 排序条件
|
||||
*/
|
||||
loadData (pagination, filters, sorter) {
|
||||
this.localLoading = true
|
||||
const parameter = Object.assign({
|
||||
pageNo: (pagination && pagination.current) ||
|
||||
this.showPagination && this.localPagination.current || this.pageNum,
|
||||
pageSize: (pagination && pagination.pageSize) ||
|
||||
this.showPagination && this.localPagination.pageSize || this.pageSize
|
||||
},
|
||||
(sorter && sorter.field && {
|
||||
sortField: sorter.field
|
||||
}) || {},
|
||||
(sorter && sorter.order && {
|
||||
sortOrder: sorter.order
|
||||
}) || {}, {
|
||||
...filters
|
||||
}
|
||||
)
|
||||
const result = this.data(parameter)
|
||||
// 对接自己的通用数据接口需要修改下方代码中的 r.pageNo, r.totalCount, r.data
|
||||
// eslint-disable-next-line
|
||||
if ((typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') {
|
||||
result.then(r => {
|
||||
this.localPagination = this.showPagination && Object.assign({}, this.localPagination, {
|
||||
current: r.pageNo, // 返回结果中的当前分页数
|
||||
total: r.totalCount, // 返回结果中的总记录数
|
||||
showSizeChanger: this.showSizeChanger,
|
||||
pageSize: (pagination && pagination.pageSize) ||
|
||||
this.localPagination.pageSize
|
||||
}) || false
|
||||
// 为防止删除数据后导致页面当前页面数据长度为 0 ,自动翻页到上一页
|
||||
if (r.data.length === 0 && this.showPagination && this.localPagination.current > 1) {
|
||||
this.localPagination.current--
|
||||
this.loadData()
|
||||
return
|
||||
}
|
||||
|
||||
// 这里用于判断接口是否有返回 r.totalCount 且 this.showPagination = true 且 pageNo 和 pageSize 存在 且 totalCount 小于等于 pageNo * pageSize 的大小
|
||||
// 当情况满足时,表示数据不满足分页大小,关闭 table 分页功能
|
||||
try {
|
||||
if ((['auto', true].includes(this.showPagination) && r.totalCount <= (r.pageNo * this.localPagination.pageSize))) {
|
||||
this.localPagination.hideOnSinglePage = false
|
||||
}
|
||||
} catch (e) {
|
||||
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
|
||||
},
|
||||
/**
|
||||
* 用于更新已选中的列表数据 total 统计
|
||||
* @param selectedRowKeys
|
||||
* @param selectedRows
|
||||
*/
|
||||
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)
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 清空 table 已选中项
|
||||
*/
|
||||
clearSelected () {
|
||||
if (this.rowSelection) {
|
||||
this.rowSelection.onChange([], [])
|
||||
this.updateSelect([], [])
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 处理交给 table 使用者去处理 clear 事件时,内部选中统计同时调用
|
||||
* @param callback
|
||||
* @returns {*}
|
||||
*/
|
||||
renderClear (callback) {
|
||||
if (this.selectedRowKeys.length <= 0) return null
|
||||
return (
|
||||
<a style="margin-left: 24px" onClick={() => {
|
||||
callback()
|
||||
this.clearSelected()
|
||||
}}>{ i18n.t('table.clear') }</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
|
||||
|
||||
// 绘制 alert 组件
|
||||
return (
|
||||
<a-alert showIcon={true} style="margin-bottom: 16px">
|
||||
<template slot="message">
|
||||
<span style="margin-right: 12px">{ i18n.t('table.selected') }: <a style="font-weight: 600">{this.selectedRows.length}</a></span>
|
||||
{needTotalItems}
|
||||
{clearItem}
|
||||
</template>
|
||||
</a-alert>
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
if (!this.loaded) {
|
||||
return (
|
||||
<div style="width: 100%; height:160px; text-align: center; line-height:160px">
|
||||
<a-spin tip="Loading...">
|
||||
</a-spin>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
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) {
|
||||
// 如果需要使用alert,则重新绑定 rowSelection 事件
|
||||
props[k] = {
|
||||
...this.rowSelection,
|
||||
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) {
|
||||
// 如果没打算开启 rowSelection 则清空默认的选择项
|
||||
props[k] = null
|
||||
return props[k]
|
||||
}
|
||||
}
|
||||
this[k] && (props[k] = this[k])
|
||||
return props[k]
|
||||
})
|
||||
|
||||
console.log('re-render table', new Date())
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user