前后端全面升级

This commit is contained in:
pycook
2023-07-10 17:42:15 +08:00
parent c444fed436
commit db5ff60aff
629 changed files with 97789 additions and 23995 deletions

View File

@@ -1,46 +1,70 @@
<template>
<a-config-provider :locale="locale">
<div id="app">
<div id="app" :class="{ 'ops-fullscreen': isOpsFullScreen, 'ops-only-topmenu': isOpsOnlyTopMenu }">
<router-view v-if="alive" />
</div>
</a-config-provider>
</template>
<script>
import i18n from '@/locales'
import { mapActions } from 'vuex'
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
import { AppDeviceEnquire } from '@/utils/mixin'
import { mixin } from '@/store/i18n-mixin'
import { debounce } from './utils/util'
export default {
mixins: [AppDeviceEnquire, mixin],
provide () {
mixins: [AppDeviceEnquire],
provide() {
return {
reload: this.reload
reload: this.reload,
}
},
data () {
data() {
return {
locale: {},
alive: true
locale: zhCN,
alive: true,
timer: null,
}
},
created () {
this.$watch('currentLang', () => {
this.locale = i18n.getLocaleMessage(this.currentLang).antLocale
})
computed: {
isOpsFullScreen() {
return this.$route.name === 'cmdb_screen'
},
isOpsOnlyTopMenu() {
return ['fullscreen_index', 'setting_person'].includes(this.$route.name)
},
},
created() {
this.timer = setInterval(() => {
this.setTime(new Date().getTime())
}, 1000)
},
mounted() {
this.$store.dispatch('setWindowSize')
window.addEventListener(
'resize',
debounce(() => {
this.$store.dispatch('setWindowSize')
})
)
},
beforeDestroy() {
clearInterval(this.timer)
},
methods: {
reload () {
...mapActions(['setTime']),
reload() {
this.alive = false
this.$nextTick(() => {
this.alive = true
})
}
}
},
},
}
</script>
<style>
<style lang="less">
@import './style/index.less';
#app {
height: 100%;
}

View File

@@ -1,34 +0,0 @@
import { axios } from '@/utils/request'
const urlPrefix = '/v1/acl'
export function searchRole (params) {
return axios({
url: urlPrefix + `/roles`,
method: 'GET',
params: params
})
}
export function addRole (params) {
return axios({
url: urlPrefix + '/roles',
method: 'POST',
data: params
})
}
export function updateRoleById (id, params) {
return axios({
url: urlPrefix + `/roles/${id}`,
method: 'PUT',
data: params
})
}
export function deleteRoleById (id) {
return axios({
url: urlPrefix + `/roles/${id}`,
method: 'DELETE'
})
}

View File

@@ -1,56 +0,0 @@
import { axios } from '@/utils/request'
const urlPrefix = '/v1/acl'
export function getResourcePerms (resourceID) {
return axios({
url: urlPrefix + `/resources/${resourceID}/permissions`,
method: 'GET'
})
}
export function getResourceTypePerms (typeID) {
return axios({
url: urlPrefix + `/resource_types/${typeID}/perms`,
method: 'GET'
})
}
export function getResourceGroupPerms (resourceGroupID) {
return axios({
url: urlPrefix + `/resource_groups/${resourceGroupID}/permissions`,
method: 'GET'
})
}
export function setRoleResourcePerm (rid, resourceID, params) {
return axios({
url: urlPrefix + `/roles/${rid}/resources/${resourceID}/grant`,
method: 'POST',
data: params
})
}
export function setRoleResourceGroupPerm (rid, resourceGroupID, params) {
return axios({
url: urlPrefix + `/roles/${rid}/resource_groups/${resourceGroupID}/grant`,
method: 'POST',
data: params
})
}
export function deleteRoleResourcePerm (rid, resourceID, params) {
return axios({
url: urlPrefix + `/roles/${rid}/resources/${resourceID}/revoke`,
method: 'POST',
data: params
})
}
export function deleteRoleResourceGroupPerm (rid, resourceGroupID, params) {
return axios({
url: urlPrefix + `/roles/${rid}/resource_groups/${resourceGroupID}/revoke`,
method: 'POST',
data: params
})
}

View File

@@ -1,66 +0,0 @@
import { axios } from '@/utils/request'
const urlPrefix = '/v1/acl'
export function searchRole (params) {
return axios({
url: urlPrefix + `/roles`,
method: 'GET',
params: params
})
}
export function addRole (params) {
return axios({
url: urlPrefix + '/roles',
method: 'POST',
data: params
})
}
export function updateRoleById (id, params) {
return axios({
url: urlPrefix + `/roles/${id}`,
method: 'PUT',
data: params
})
}
export function deleteRoleById (id) {
return axios({
url: urlPrefix + `/roles/${id}`,
method: 'DELETE'
})
}
export function addParentRole (id, otherID) {
return axios({
url: urlPrefix + `/roles/${id}/parents`,
method: 'POST',
data: { parent_id: otherID }
})
}
export function addChildRole (id, otherID) {
return axios({
url: urlPrefix + `/roles/${otherID}/parents`,
method: 'POST',
data: { parent_id: id }
})
}
export function delParentRole (cid, pid) {
return axios({
url: urlPrefix + `/roles/${cid}/parents`,
method: 'DELETE',
data: { parent_id: pid }
})
}
export function delChildRole (pid, cid) {
return axios({
url: urlPrefix + `/roles/${cid}/parents`,
method: 'DELETE',
data: { parent_id: pid }
})
}

View File

@@ -1,38 +0,0 @@
import { axios } from '@/utils/request'
export function getFirstCIs (ciId) {
return axios({
url: '/v0.1/ci_relations/' + ciId + '/first_cis',
method: 'GET'
})
}
export function getSecondCIs (ciId) {
return axios({
url: '/v0.1/ci_relations/' + ciId + '/second_cis',
method: 'GET'
})
}
export function searchCIRelation (params) {
return axios({
url: `/v0.1/ci_relations/s?${params}`,
method: 'GET'
})
}
export function statisticsCIRelation (params) {
return axios({
url: '/v0.1/ci_relations/statistics',
method: 'GET',
params: params
})
}
export function batchUpdateCIRelation (ciIds, parents) {
return axios({
url: '/v0.1/ci_relations/batch',
method: 'POST',
data: { ci_ids: ciIds, parents: parents }
})
}

View File

@@ -1,123 +0,0 @@
import { axios } from '@/utils/request'
/**
* get all CI Type
* @param parameter
* @returns {AxiosPromise}
*/
export function getCITypes (parameter) {
return axios({
url: '/v0.1/ci_types',
method: 'GET',
params: parameter
})
}
/**
* get a CI Type
* @param CITypeName
* @param parameter
* @returns {AxiosPromise}
*/
export function getCIType (CITypeName, parameter) {
return axios({
url: `/v0.1/ci_types/${CITypeName}`,
method: 'GET',
params: parameter
})
}
/**
* Create CI Type
* @param data
* @returns {AxiosPromise}
*/
export function createCIType (data) {
return axios({
url: '/v0.1/ci_types',
method: 'POST',
data: data
})
}
/**
* Update CI Type
* @param CITypeId
* @param data
* @returns {AxiosPromise}
*/
export function updateCIType (CITypeId, data) {
return axios({
url: `/v0.1/ci_types/${CITypeId}`,
method: 'PUT',
data: data
})
}
/**
* Delete CI Type
* @param CITypeId
* @returns {AxiosPromise}
*/
export function deleteCIType (CITypeId) {
return axios({
url: `/v0.1/ci_types/${CITypeId}`,
method: 'DELETE'
})
}
/**
* Gets a grouping of a CI Type
* @param CITypeId
* @param data
* @returns {AxiosPromise}
*/
export function getCITypeGroupById (CITypeId, data) {
return axios({
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
method: 'GET',
params: data
})
}
/**
* Save a group of CI Type
* @param CITypeId
* @param data
* @returns {AxiosPromise}
*/
export function createCITypeGroupById (CITypeId, data) {
return axios({
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
method: 'POST',
data: data
})
}
/**
* Changes the grouping of a CI Type
* @param groupId
* @param data
* @returns {AxiosPromise}
*/
export function updateCITypeGroupById (groupId, data) {
return axios({
url: `/v0.1/ci_types/attribute_groups/${groupId}`,
method: 'PUT',
data: data
})
}
/**
* Removes a group for a CI Type
* @param groupId
* @param data
* @returns {AxiosPromise}
*/
export function deleteCITypeGroupById (groupId, data) {
return axios({
url: `/v0.1/ci_types/attribute_groups/${groupId}`,
method: 'delete',
data: data
})
}

View File

@@ -1,39 +0,0 @@
import { axios } from '@/utils/request'
export function getCITypeChildren (CITypeID, parameter) {
return axios({
url: '/v0.1/ci_type_relations/' + CITypeID + '/children',
method: 'GET',
params: parameter
})
}
export function getCITypeParent (CITypeID) {
return axios({
url: '/v0.1/ci_type_relations/' + CITypeID + '/parents',
method: 'GET'
})
}
export function getCITypeRelations () {
return axios({
url: '/v0.1/ci_type_relations',
method: 'GET'
})
}
export function createRelation (parentId, childrenId, relationTypeId) {
return axios({
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
method: 'POST',
data: { relation_type_id: relationTypeId }
})
}
export function deleteRelation (parentId, childrenId) {
return axios({
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
method: 'DELETE'
})
}

View File

@@ -1,41 +0,0 @@
import { axios } from '@/utils/request'
const urlPrefix = '/v0.1'
export function searchCI (params) {
return axios({
url: urlPrefix + `/ci/s?${params}`,
method: 'GET'
})
}
export function addCI (params) {
return axios({
url: urlPrefix + '/ci',
method: 'POST',
data: params
})
}
export function updateCI (id, params) {
return axios({
url: urlPrefix + `/ci/${id}`,
method: 'PUT',
data: params
})
}
export function deleteCI (ciId) {
return axios({
url: urlPrefix + `/ci/${ciId}`,
method: 'DELETE'
})
}
// Get a single CI instance
export function getCIById (ciId) {
return axios({
url: urlPrefix + `/ci/${ciId}`,
method: 'GET'
})
}

View File

@@ -1,8 +0,0 @@
import { axios } from '@/utils/request'
export function getCIHistory (ciId) {
return axios({
url: `/v0.1/history/ci/${ciId}`,
method: 'GET'
})
}

View File

@@ -1,64 +0,0 @@
import { axios } from '@/utils/request'
export function getPreference (instance = true, tree = null) {
return axios({
url: '/v0.1/preference/ci_types',
method: 'GET',
params: { instance: instance, tree: tree }
})
}
export function getSubscribeAttributes (ciTypeId) {
return axios({
url: `/v0.1/preference/ci_types/${ciTypeId}/attributes`,
method: 'GET'
})
}
export function getSubscribeTreeView () {
return axios({
url: '/v0.1/preference/tree/view',
method: 'GET'
})
}
export function subscribeCIType (ciTypeId, attrs) {
return axios({
url: `/v0.1/preference/ci_types/${ciTypeId}/attributes`,
method: 'POST',
data: {
attr: attrs
}
})
}
export function subscribeTreeView (ciTypeId, levels) {
return axios({
url: `/v0.1/preference/tree/view`,
method: 'POST',
data: { type_id: ciTypeId, levels: levels }
})
}
export function getRelationView () {
return axios({
url: `/v0.1/preference/relation/view`,
method: 'GET'
})
}
export function deleteRelationView (viewName) {
return axios({
url: `/v0.1/preference/relation/view`,
method: 'DELETE',
data: { name: viewName }
})
}
export function subscribeRelationView (payload) {
return axios({
url: `/v0.1/preference/relation/view`,
method: 'POST',
data: payload
})
}

View File

@@ -0,0 +1,97 @@
import { axios } from '@/utils/request'
export function getCompanyInfo() {
return axios({
url: '/common-setting/v1/company/info',
method: 'get',
})
}
export function postCompanyInfo(parameter) {
return axios({
url: '/common-setting/v1/company/info',
method: 'post',
data: parameter,
})
}
export function putCompanyInfo(id, parameter) {
return axios({
url: `/common-setting/v1/company/info/${id}`,
method: 'put',
data: parameter,
})
}
export function postImageFile(parameter) {
return axios({
url: '/common-setting/v1/file',
method: 'post',
data: parameter,
})
}
export function getDepartmentList(params) {
// ?department_parent_id=-1 查询第一级部门下面的id根据实际的传
return axios({
url: '/common-setting/v1/department',
method: 'get',
params: params
})
}
export function getAllDepartmentList(params) { // is_tree
return axios({
url: '/common-setting/v1/department/all',
method: 'get',
params: params
})
}
export function postDepartment(departmentData) {
// 创建部门参数
// department_name
// department_director_id 部门负责人ID, 默认 0
// department_parent_id 上级部门ID 默认0 不为0时必须是已存在的部门ID
return axios({
url: '/common-setting/v1/department',
method: 'post',
data: departmentData,
})
}
export function putDepartmentById(department_id, departmentData) {
// 修改部门参数departmentData
// department_name
// department_director_id 部门负责人ID, 默认 0
// department_parent_id 上级部门ID 默认0 不为0时必须是已存在的部门ID
return axios({
url: `/common-setting/v1/department/${department_id}`,
method: 'put',
data: departmentData,
})
}
export function deleteDepartmentById(department_id) {
return axios({
url: `/common-setting/v1/department/${department_id}`,
method: 'delete',
})
}
export function getParentDepartmentList(department_id) {
return axios({
url: '/common-setting/v1/department/allow_parent',
method: 'get',
params: department_id,
})
}
// 获取全部部门和员工的树状结构
export function getAllDepAndEmployee(params) {
return axios({
url: '/common-setting/v1/department/all_with_employee',
method: 'get',
params
})
}
// 更新部门排序
export function updateDepartmentsSort(data) {
return axios({
url: '/common-setting/v1/department/update_sort',
method: 'put',
data
})
}

119
cmdb-ui/src/api/employee.js Normal file
View File

@@ -0,0 +1,119 @@
import { axios } from '@/utils/request'
export function getEmployeeList(params) {
return axios({
url: '/common-setting/v1/employee',
method: 'get',
params: params,
})
}
// export function getEmployeeList(params, orderBy) {
// return axios({
// url: '/common-setting/v1/employee' + '/' + orderBy,
// method: 'get',
// params: params,
// })
// }
export function postEmployee(data) {
return axios({
url: '/common-setting/v1/employee',
method: 'post',
data: data,
})
}
export function getEmployeeCount(params) {
return axios({
url: '/common-setting/v1/employee/count',
method: 'get',
params: params,
})
}
export function deleteEmployee(_id) {
return axios({
url: `/common-setting/v1/employee/${_id}`,
method: 'delete',
})
}
export function putEmployee(_id, data) {
return axios({
url: `/common-setting/v1/employee/${_id}`,
method: 'put',
data: data,
})
}
export function batchEditEmployee(data) {
return axios({
url: '/common-setting/v1/employee/batch',
method: 'post',
data: data,
})
}
export function importEmployee(data) {
return axios({
url: '/common-setting/v1/employee/import',
method: 'post',
data
})
}
export function getEmployeeByUid(uid) {
return axios({
url: `/common-setting/v1/employee/by_uid/${uid}`,
method: 'get',
})
}
export function updateEmployeeByUid(uid, data) {
return axios({
url: `/common-setting/v1/employee/by_uid/${uid}`,
method: 'put',
data
})
}
export function updatePasswordByUid(uid, data) {
return axios({
url: `/common-setting/v1/employee/by_uid/change_password/${uid}`,
method: 'put',
data
})
}
export function bindWxByUid(uid) {
return axios({
url: `/common-setting/v1/employee/by_uid/bind_work_wechat/${uid}`,
method: 'put',
})
}
export function getAllPosition() {
return axios({
url: `/common-setting/v1/employee/position`,
method: 'get',
})
}
export function getEmployeeByEmployeeId(employee_id) {
return axios({
url: `/common-setting/v1/employee/${employee_id}`,
method: 'get',
})
}
// 下载员工列表
export function downloadAllEmployee(params) {
return axios({
url: `/common-setting/v1/employee/export_all`,
method: 'get',
params,
responseType: 'blob'
})
}
export function getEmployeeListByFilter(data) {
return axios({
url: '/common-setting/v1/employee/filter',
method: 'post',
data
})
}

View File

@@ -1,14 +1,15 @@
import config from '@/config/defaultSettings'
import config from '@/config/setting'
const api = {
Login: config.useSSO ? config.ssoLoginRedirectUrl : '/login',
Logout: config.useSSO ? config.ssoLogoutRedirectUrl : '/logout',
Login: config.useSSO ? '/api/sso/login' : '/v1/acl/login',
Logout: config.useSSO ? '/api/sso/logout' : '/v1/acl/logout',
ForgePassword: '/auth/forge-password',
Register: '/auth/register',
twoStepCode: '/auth/2step-code',
SendSms: '/account/sms',
SendSmsErr: '/account/sms_err',
// get my info
UserInfo: '/v1/acl/users/info'
// UserInfo: '/v1/perms/user/info'
UserInfo: process.env.VUE_APP_IS_OUTER === 'false' ? '/v1/perms/user/info' : '/v1/acl/users/info',
}
export default api

View File

@@ -1,6 +1,6 @@
import api from './index'
import { axios } from '@/utils/request'
import config from '@/config/defaultSettings'
import config from '@/config/setting'
/**
* login func
* parameter: {
@@ -12,15 +12,19 @@ import config from '@/config/defaultSettings'
* @param parameter
* @returns {*}
*/
export function login (parameter) {
return axios({
url: api.Login,
method: 'post',
data: parameter
})
export function login(data) {
if (config.useSSO) {
window.location.href = config.ssoLoginUrl
} else {
return axios({
url: api.Login,
method: 'POST',
data: data
})
}
}
export function getSmsCaptcha (parameter) {
export function getSmsCaptcha(parameter) {
return axios({
url: api.SendSms,
method: 'post',
@@ -28,7 +32,7 @@ export function getSmsCaptcha (parameter) {
})
}
export function getInfo () {
export function getInfo() {
return axios({
url: api.UserInfo,
method: 'get',
@@ -38,11 +42,9 @@ export function getInfo () {
})
}
export function logout () {
console.log('logout........', config.useSSO)
export function logout() {
if (config.useSSO) {
window.location.href = api.Logout
return Promise.resolve()
window.location.replace(api.Logout)
} else {
return axios({
url: api.Logout,
@@ -58,10 +60,18 @@ export function logout () {
* get user 2step code open?
* @param parameter {*}
*/
export function get2step (parameter) {
export function get2step(parameter) {
return axios({
url: api.twoStepCode,
method: 'post',
data: parameter
})
}
export function getAllUsers(params) {
return axios({
url: '/v1/acl/users',
method: 'GET',
params
})
}

View File

@@ -1,62 +0,0 @@
import { axios } from '@/utils/request'
const api = {
user: '/user',
role: '/role',
service: '/service',
permission: '/permission',
permissionNoPager: '/permission/no-pager',
orgTree: '/org/tree'
}
export default api
export function getUserList (parameter) {
return axios({
url: api.user,
method: 'get',
params: parameter
})
}
export function getRoleList (parameter) {
return axios({
url: api.role,
method: 'get',
params: parameter
})
}
export function getServiceList (parameter) {
return axios({
url: api.service,
method: 'get',
params: parameter
})
}
export function getPermissions (parameter) {
return axios({
url: api.permissionNoPager,
method: 'get',
params: parameter
})
}
export function getOrgTree (parameter) {
return axios({
url: api.orgTree,
method: 'get',
params: parameter
})
}
// id == 0 add post
// id != 0 update put
export function saveService (parameter) {
return axios({
url: api.service,
method: parameter.id === 0 ? 'post' : 'put',
data: parameter
})
}

View File

@@ -0,0 +1,55 @@
import { axios } from '@/utils/request'
export const getNoticeApps = () => {
return axios({
url: `/common-setting/v1/message/apps`,
method: 'get',
})
}
export const getNoticeCategoriesByApp = (app_name) => {
return axios({
url: `/common-setting/v1/message/${app_name}/categories`,
method: 'get',
})
}
export const getMessage = (params) => {
return axios({
url: `/common-setting/v1/message`,
method: 'get',
params
})
}
export const postMessage = (data) => {
return axios({
url: `/common-setting/v1/message`,
method: 'post',
data
})
}
export const updateMessage = (id, data) => {
return axios({
url: `/common-setting/v1/message/${id}`,
method: 'put',
data
})
}
export const getUnreadMessageCount = (params) => {
return axios({
url: `/common-setting/v1/message/unread`,
method: 'get',
params
})
}
export const batchUpdateMessage = (data) => {
return axios({
url: `/common-setting/v1/message/batch`,
method: 'post',
data
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1551058675966" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7872" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M85.333333 512h85.333334a340.736 340.736 0 0 1 99.712-241.621333 337.493333 337.493333 0 0 1 108.458666-72.96 346.453333 346.453333 0 0 1 261.546667-1.749334A106.154667 106.154667 0 0 0 746.666667 298.666667C805.802667 298.666667 853.333333 251.136 853.333333 192S805.802667 85.333333 746.666667 85.333333c-29.397333 0-55.978667 11.776-75.221334 30.933334-103.722667-41.514667-222.848-40.874667-325.76 2.517333a423.594667 423.594667 0 0 0-135.68 91.264 423.253333 423.253333 0 0 0-91.306666 135.637333A426.88 426.88 0 0 0 85.333333 512z m741.248 133.205333c-17.109333 40.618667-41.685333 77.141333-72.96 108.416s-67.797333 55.850667-108.458666 72.96a346.453333 346.453333 0 0 1-261.546667 1.749334A106.154667 106.154667 0 0 0 277.333333 725.333333C218.197333 725.333333 170.666667 772.864 170.666667 832S218.197333 938.666667 277.333333 938.666667c29.397333 0 55.978667-11.776 75.221334-30.933334A425.173333 425.173333 0 0 0 512 938.666667a425.941333 425.941333 0 0 0 393.258667-260.352A426.325333 426.325333 0 0 0 938.666667 512h-85.333334a341.034667 341.034667 0 0 1-26.752 133.205333z" p-id="7873"></path><path d="M512 318.378667c-106.752 0-193.621333 86.869333-193.621333 193.621333S405.248 705.621333 512 705.621333s193.621333-86.869333 193.621333-193.621333S618.752 318.378667 512 318.378667z m0 301.909333c-59.690667 0-108.288-48.597333-108.288-108.288S452.309333 403.712 512 403.712s108.288 48.597333 108.288 108.288-48.597333 108.288-108.288 108.288z" p-id="7874"></path></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1663902596358" class="icon" style="width:20px;height:20px;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4084" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024"><path d="M320 306.6c0 7.4-6 13.4-13.4 13.4l-101 0c-7.4 0-13.4-6-13.4-13.4l0-101c0-7.4 6-13.4 13.4-13.4l101 0c7.4 0 13.4 6 13.4 13.4L320 306.6z" p-id="4085"></path><path d="M576 306.6c0 7.4-6 13.4-13.4 13.4l-101 0c-7.4 0-13.4-6-13.4-13.4l0-101c0-7.4 6-13.4 13.4-13.4l101 0c7.4 0 13.4 6 13.4 13.4L576 306.6z" p-id="4086"></path><path d="M832 306.6c0 7.4-6 13.4-13.4 13.4l-101 0c-7.4 0-13.4-6-13.4-13.4l0-101c0-7.4 6-13.4 13.4-13.4l101 0c7.4 0 13.4 6 13.4 13.4L832 306.6z" p-id="4087"></path><path d="M320 562.6c0 7.4-6 13.4-13.4 13.4l-101 0c-7.4 0-13.4-6-13.4-13.4l0-101c0-7.4 6-13.4 13.4-13.4l101 0c7.4 0 13.4 6 13.4 13.4L320 562.6z" p-id="4088"></path><path d="M576 562.6c0 7.4-6 13.4-13.4 13.4l-101 0c-7.4 0-13.4-6-13.4-13.4l0-101c0-7.4 6-13.4 13.4-13.4l101 0c7.4 0 13.4 6 13.4 13.4L576 562.6z" p-id="4089"></path><path d="M832 562.6c0 7.4-6 13.4-13.4 13.4l-101 0c-7.4 0-13.4-6-13.4-13.4l0-101c0-7.4 6-13.4 13.4-13.4l101 0c7.4 0 13.4 6 13.4 13.4L832 562.6z" p-id="4090"></path><path d="M320 818.6c0 7.4-6 13.4-13.4 13.4l-101 0c-7.4 0-13.4-6-13.4-13.4l0-101c0-7.4 6-13.4 13.4-13.4l101 0c7.4 0 13.4 6 13.4 13.4L320 818.6z" p-id="4091"></path><path d="M576 818.6c0 7.4-6 13.4-13.4 13.4l-101 0c-7.4 0-13.4-6-13.4-13.4l0-101c0-7.4 6-13.4 13.4-13.4l101 0c7.4 0 13.4 6 13.4 13.4L576 818.6z" p-id="4092"></path><path d="M832 818.6c0 7.4-6 13.4-13.4 13.4l-101 0c-7.4 0-13.4-6-13.4-13.4l0-101c0-7.4 6-13.4 13.4-13.4l101 0c7.4 0 13.4 6 13.4 13.4L832 818.6z" p-id="4093"></path></svg><?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1652149253074" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2678" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }</style></defs><path d="M512 505.6c-2.133333 0-4.266667 0-8.533333-2.133333L298.666667 426.666667v298.666666h426.666666V426.666667l-204.8 76.8c-4.266667 0-6.4 2.133333-8.533333 2.133333z" p-id="2679"></path><path d="M298.666667 379.733333l213.333333 81.066667 213.333333-81.066667V298.666667H298.666667z" p-id="2680"></path><path d="M512 0C228.266667 0 0 228.266667 0 512s228.266667 512 512 512 512-228.266667 512-512S795.733333 0 512 0z m256 725.333333c0 23.466667-19.2 42.666667-42.666667 42.666667H298.666667c-23.466667 0-42.666667-19.2-42.666667-42.666667V298.666667c0-23.466667 19.2-42.666667 42.666667-42.666667h426.666666c23.466667 0 42.666667 19.2 42.666667 42.666667v426.666666z" p-id="2681"></path></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,14 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0H2.5V1.25H1.25V2.5H0V1C0 0.447715 0.447715 0 1 0ZM0 7.5V9C0 9.55229 0.447715 10 1 10H2.5V8.75H1.25V7.5H0ZM8.75 7.5V8.75H7.5V10H9C9.55229 10 10 9.55228 10 9V7.5H8.75ZM10 2.5V1C10 0.447715 9.55228 0 9 0H7.5V1.25H8.75V2.5H10Z" fill="url(#paint0_linear_124_16807)"/>
<rect x="2.5" y="3.125" width="5" height="3.75" fill="url(#paint1_linear_124_16807)"/>
<defs>
<linearGradient id="paint0_linear_124_16807" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F84FF"/>
<stop offset="1" stop-color="#85CBFF"/>
</linearGradient>
<linearGradient id="paint1_linear_124_16807" x1="5" y1="3.125" x2="5" y2="6.875" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F84FF"/>
<stop offset="1" stop-color="#85CBFF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 916 B

View File

@@ -0,0 +1,14 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.5" y="2.5" width="5" height="5" rx="0.5" fill="url(#paint0_linear_124_16808)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447715 0 0 0.447715 0 1V9C0 9.55229 0.447715 10 1 10H9C9.55229 10 10 9.55228 10 9V1C10 0.447715 9.55228 0 9 0H1ZM8.75 1.25H1.25V8.75H8.75V1.25Z" fill="url(#paint1_linear_124_16808)"/>
<defs>
<linearGradient id="paint0_linear_124_16808" x1="5" y1="2.5" x2="5" y2="7.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#5187FF"/>
<stop offset="1" stop-color="#84C9FF"/>
</linearGradient>
<linearGradient id="paint1_linear_124_16808" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5187FF"/>
<stop offset="1" stop-color="#84C9FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 840 B

View File

@@ -0,0 +1,9 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.56845 8.8409C3.06335 8.78963 1.86719 8.05799 2.06279 6.48243C2.1538 5.75105 2.64549 5.3214 3.34457 5.16041C3.67173 5.08909 4.00806 5.06954 4.34128 5.10247C4.40203 5.10811 4.44843 5.11401 4.47689 5.11837L4.51586 5.12631C4.64379 5.15574 4.77263 5.18104 4.90218 5.20219C5.26786 5.2651 5.63914 5.28941 6.0099 5.27474C6.8046 5.23219 7.21015 4.97429 7.23092 4.41672C7.25424 3.79429 6.76332 3.29619 5.86659 2.91832C5.52815 2.77793 5.17843 2.66645 4.82117 2.58506C4.70325 2.55755 4.58482 2.53328 4.46587 2.51226C4.30323 2.94847 3.9867 3.31016 3.57591 3.5292C3.16512 3.74824 2.68841 3.80952 2.23557 3.70149C1.90324 3.61651 1.60053 3.44214 1.36029 3.1973C1.12004 2.95245 0.951447 2.64649 0.872793 2.3126C0.794138 1.97872 0.808429 1.62967 0.914116 1.30333C1.0198 0.976995 1.21285 0.685836 1.4723 0.461451C1.73176 0.237065 2.04771 0.0880244 2.38588 0.0305017C2.72404 -0.0270211 3.07151 0.00917138 3.39056 0.135152C3.70961 0.261132 3.98807 0.472088 4.19571 0.745127C4.40335 1.01817 4.53225 1.34286 4.56841 1.68397C4.6812 1.70269 4.83374 1.73217 5.01524 1.77421C5.42003 1.86601 5.81625 1.99216 6.1996 2.15131C7.38191 2.64966 8.1156 3.39463 8.07638 4.4462C8.03639 5.53187 7.23425 6.04253 6.0563 6.10533C5.62418 6.12373 5.19132 6.09614 4.76503 6.02304C4.61925 5.99997 4.47398 5.9716 4.32923 5.93793C4.30731 5.93532 4.28534 5.9331 4.26335 5.93127C4.02033 5.90687 3.77501 5.92018 3.53606 5.97075C3.15153 6.05893 2.94311 6.24146 2.90056 6.58267C2.78725 7.49504 3.47915 7.94443 5.42694 8.00416C5.44492 7.65558 5.5586 7.3187 5.75548 7.03049C5.95237 6.74229 6.22485 6.51389 6.54303 6.37039C6.8612 6.22689 7.21277 6.17383 7.55912 6.21703C7.90548 6.26023 8.23323 6.39802 8.50641 6.61528C8.77959 6.83254 8.98763 7.12086 9.10769 7.4486C9.22775 7.77634 9.25519 8.13082 9.187 8.47314C9.11881 8.81545 8.95763 9.13235 8.72114 9.38907C8.48465 9.64578 8.18201 9.83237 7.84643 9.92836C7.39921 10.0556 6.92094 10.0153 6.50129 9.81515C6.08164 9.61495 5.74941 9.26855 5.56691 8.8409H5.56845Z" fill="url(#paint0_linear_124_16804)"/>
<defs>
<linearGradient id="paint0_linear_124_16804" x1="5.02318" y1="0.00390625" x2="5.02318" y2="10.0013" gradientUnits="userSpaceOnUse">
<stop stop-color="#497DFF"/>
<stop offset="1" stop-color="#8CD5FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,9 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.01211 4.50621L2.7769 5.74077C2.712 5.80565 2.66051 5.88268 2.62538 5.96747C2.59025 6.05225 2.57217 6.14313 2.57217 6.2349C2.57217 6.32668 2.59025 6.41755 2.62538 6.50234C2.66051 6.58712 2.712 6.66416 2.7769 6.72904L3.27085 7.223C3.33573 7.28791 3.41276 7.3394 3.49754 7.37453C3.58232 7.40966 3.67319 7.42774 3.76496 7.42774C3.85674 7.42774 3.94761 7.40966 4.03239 7.37453C4.11717 7.3394 4.1942 7.28791 4.25908 7.223L5.49394 5.98775C5.6237 6.1175 5.72663 6.27155 5.79686 6.44109C5.86708 6.61063 5.90323 6.79234 5.90323 6.97585C5.90323 7.15935 5.86708 7.34106 5.79686 7.5106C5.72663 7.68014 5.6237 7.83419 5.49394 7.96394L3.76479 9.69316C3.56827 9.88963 3.30176 10 3.02387 10C2.74599 10 2.47948 9.88963 2.28296 9.69316L0.306832 7.71696C0.110368 7.52043 0 7.25391 0 6.97602C0 6.69813 0.110368 6.43161 0.306832 6.23508L2.03599 4.50586C2.16574 4.3761 2.31978 4.27317 2.48931 4.20294C2.65884 4.13271 2.84055 4.09657 3.02405 4.09657C3.20755 4.09657 3.38925 4.13271 3.55879 4.20294C3.72832 4.27317 3.88236 4.3761 4.01211 4.50586V4.50621ZM5.98789 5.49414L7.2231 4.25923C7.288 4.19435 7.33949 4.11732 7.37462 4.03253C7.40975 3.94775 7.42783 3.85687 7.42783 3.7651C7.42783 3.67332 7.40975 3.58245 7.37462 3.49766C7.33949 3.41288 7.288 3.33584 7.2231 3.27096L6.72915 2.777C6.66428 2.71209 6.58724 2.6606 6.50246 2.62547C6.41768 2.59034 6.32681 2.57226 6.23504 2.57226C6.14326 2.57226 6.05239 2.59034 5.96761 2.62547C5.88283 2.6606 5.8058 2.71209 5.74092 2.777L4.50606 4.01225C4.3763 3.8825 4.27337 3.72845 4.20314 3.55891C4.13292 3.38937 4.09677 3.20766 4.09677 3.02415C4.09677 2.84065 4.13292 2.65894 4.20314 2.4894C4.27337 2.31986 4.3763 2.16581 4.50606 2.03606L6.23521 0.306843C6.43173 0.110371 6.69824 0 6.97613 0C7.25401 0 7.52052 0.110371 7.71704 0.306843L9.69317 2.28304C9.88963 2.47957 10 2.74609 10 3.02398C10 3.30187 9.88963 3.56839 9.69317 3.76492L7.96401 5.49414C7.83426 5.6239 7.68022 5.72683 7.51069 5.79706C7.34116 5.86729 7.15945 5.90343 6.97595 5.90343C6.79245 5.90343 6.61075 5.86729 6.44121 5.79706C6.27168 5.72683 6.11764 5.6239 5.98789 5.49414ZM3.51817 5.9881L5.98789 3.51829C6.05339 3.45274 6.14225 3.4159 6.23491 3.41586C6.32758 3.41583 6.41646 3.45261 6.48201 3.51812C6.54755 3.58362 6.5844 3.67248 6.58443 3.76515C6.58446 3.85782 6.54768 3.9467 6.48218 4.01225L4.01211 6.48206C3.94661 6.54761 3.85775 6.58445 3.76509 6.58449C3.67242 6.58452 3.58354 6.54774 3.51799 6.48223C3.45245 6.41673 3.4156 6.32787 3.41557 6.2352C3.41554 6.14253 3.45232 6.05365 3.51782 5.9881H3.51817Z" fill="url(#paint0_linear_124_16775)"/>
<defs>
<linearGradient id="paint0_linear_124_16775" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,9 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.31822 4.16667H2.54549V2.5C2.54549 1.11458 3.63981 0 5.00003 0C6.36026 0 7.45458 1.11458 7.45458 2.5V4.16667H8.68185C8.90685 4.16667 9.09094 4.35417 9.09094 4.58333V9.58333C9.09094 9.8125 8.90685 10 8.68185 10H1.31822C1.09322 10 0.909124 9.8125 0.909124 9.58333V4.58333C0.909124 4.35417 1.09322 4.16667 1.31822 4.16667ZM5.00003 7.91667C5.45003 7.91667 5.81822 7.54167 5.81822 7.08333C5.81822 6.625 5.45003 6.25 5.00003 6.25C4.55003 6.25 4.18185 6.625 4.18185 7.08333C4.18185 7.54167 4.55003 7.91667 5.00003 7.91667ZM3.36367 4.16667H6.6364V2.5C6.6364 1.58333 5.90003 0.833333 5.00003 0.833333C4.10003 0.833333 3.36367 1.58333 3.36367 2.5V4.16667Z" fill="url(#paint0_linear_124_16805)"/>
<defs>
<linearGradient id="paint0_linear_124_16805" x1="5.00003" y1="0" x2="5.00003" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#4D82FF"/>
<stop offset="1" stop-color="#88CFFF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1022 B

View File

@@ -0,0 +1,9 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.91242 9.46382C3.91242 9.57428 3.82288 9.66382 3.71242 9.66382H2.35075C2.2403 9.66382 2.15075 9.57428 2.15075 9.46382V3.55962C2.15075 3.44916 2.06121 3.35962 1.95075 3.35962H0.539905C0.354312 3.35962 0.268806 3.12879 0.40961 3.00788L3.58212 0.283626C3.71182 0.172253 3.91242 0.264405 3.91242 0.43536V9.46382ZM6.08758 0.567715C6.08758 0.457258 6.17712 0.367716 6.28758 0.367716H7.64925C7.7597 0.367716 7.84925 0.457259 7.84925 0.567716V6.4411C7.84925 6.55156 7.93879 6.6411 8.04925 6.6411H9.46001C9.64561 6.6411 9.73111 6.87195 9.59029 6.99285L6.41786 9.71645C6.28816 9.8278 6.08758 9.73565 6.08758 9.5647V0.567715Z" fill="url(#paint0_linear_124_16806)"/>
<defs>
<linearGradient id="paint0_linear_124_16806" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 979 B

View File

@@ -0,0 +1,9 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.51961 6.8937V10H4.48V6.8937H1.76732C1.65823 6.8937 1.56223 6.85372 1.48369 6.77237C1.40522 6.69504 1.3621 6.5915 1.36369 6.48421C1.36369 5.95891 1.52769 5.48566 1.85895 5.06411C2.18986 4.64428 2.56258 4.43334 2.97893 4.43334V1.64277C2.75966 1.64277 2.57167 1.56142 2.41022 1.39873C2.25355 1.24349 2.16738 1.0362 2.17022 0.821384C2.17022 0.598718 2.25022 0.407762 2.41022 0.244037C2.56912 0.0827244 2.7593 0 2.97893 0H7.01959C7.23885 0 7.42685 0.0813456 7.5883 0.244037C7.74721 0.406728 7.82866 0.598718 7.82866 0.821384C7.82866 1.04405 7.74866 1.23501 7.58867 1.39873C7.4283 1.5628 7.23885 1.64277 7.01959 1.64277V4.43196C7.43594 4.43196 7.81012 4.64291 8.13956 5.06273C8.46631 5.47151 8.64098 5.97137 8.63628 6.48421C8.63628 6.59486 8.59701 6.6924 8.51665 6.77237C8.43665 6.85234 8.34211 6.8937 8.23302 6.8937H5.51998H5.51961Z" fill="url(#paint0_linear_124_16803)"/>
<defs>
<linearGradient id="paint0_linear_124_16803" x1="5.00001" y1="0" x2="5.00001" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg t="1662694543392" class="move-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2689" aria-hidden="true" fill="currentColor"><path d="M469.333333 256a85.333333 85.333333 0 1 1-85.333333-85.333333 85.333333 85.333333 0 0 1 85.333333 85.333333z m-85.333333 170.666667a85.333333 85.333333 0 1 0 85.333333 85.333333 85.333333 85.333333 0 0 0-85.333333-85.333333z m0 256a85.333333 85.333333 0 1 0 85.333333 85.333333 85.333333 85.333333 0 0 0-85.333333-85.333333z m256-341.333334a85.333333 85.333333 0 1 0-85.333333-85.333333 85.333333 85.333333 0 0 0 85.333333 85.333333z m0 85.333334a85.333333 85.333333 0 1 0 85.333333 85.333333 85.333333 85.333333 0 0 0-85.333333-85.333333z m0 256a85.333333 85.333333 0 1 0 85.333333 85.333333 85.333333 85.333333 0 0 0-85.333333-85.333333z" p-id="2690" fill="#8a8a8a"></path></svg>

After

Width:  |  Height:  |  Size: 855 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1663922664875" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9161" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 192c102.4 0 153.6 51.2 153.6 153.6C665.6 480 576 576 576 576L556.8 595.2l0 25.6c0 57.6 44.8 121.6 121.6 121.6 102.4 0 102.4 19.2 102.4 44.8l0 0C768 800 710.4 832 505.6 832c-185.6 0-243.2-32-262.4-38.4l0-6.4c0-25.6 0-44.8 102.4-44.8 76.8 0 121.6-64 121.6-121.6L467.2 595.2 448 576c0 0-89.6-96-89.6-230.4C358.4 243.2 409.6 192 512 192M512 128C403.2 128 294.4 185.6 294.4 345.6s108.8 275.2 108.8 275.2 0 57.6-57.6 57.6-166.4 0-166.4 108.8c0 0-44.8 108.8 326.4 108.8s332.8-108.8 332.8-108.8c0-108.8-108.8-108.8-166.4-108.8S620.8 620.8 620.8 620.8s108.8-108.8 108.8-275.2S620.8 128 512 128L512 128z" p-id="9162"></path></svg>

After

Width:  |  Height:  |  Size: 958 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1663922655131" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8238" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M458 476.6L170.8 332.9c-19.6-9.8-38.6-10.2-53.4-1-14.8 9.2-23 26.2-23 48.1v332.2c0 31.4 21.7 67.7 49.3 82.8l289.1 156.6c10.5 5.7 20.8 8.6 30.6 8.6 8.1 0 15.6-2 22.2-5.9 14.7-8.8 22.9-25.7 22.9-47.6V558.2c0-15.4-5.2-32.2-14.5-47.4-9.4-15.1-22.2-27.3-36-34.2z m-7.3 81.6v337.4L171.2 744.2c-8.9-4.8-19.1-21.9-19.1-32V388.3l280 140c8.7 4.3 18.6 20.2 18.6 29.9zM874.5 300.8c19.3-9.5 29.9-23.1 29.8-38.3 0-15.2-10.6-28.8-29.9-38.3l-302-148c-16.3-8-37.8-12.3-60.5-12.3-22.7 0-44.2 4.4-60.4 12.3l-302 147.9c-19.3 9.5-29.8 23.1-29.8 38.3 0 15.2 10.6 28.8 29.9 38.3l302 148c16.3 8 37.8 12.3 60.5 12.3 22.7 0 44.2-4.4 60.4-12.3l302-147.9z m-671.8-38.4L477 128.1c18-8.8 52-8.8 70.1 0l274.2 134.3L547 396.8c-18 8.8-52 8.8-70.1 0L202.7 262.4zM906.7 332.4c-14.8-8.8-33.6-7.9-52.9 2.6L581 483.3c-27.6 15.1-49.3 51.6-49.3 82.9v340.3c0 22 8.1 38.8 22.8 47.4 6.4 3.7 13.6 5.6 21.4 5.6 10 0 20.4-3.1 31.1-9.1l273.8-154.7c13.3-7.6 25.7-20.3 34.8-35.8 9.1-15.5 14.1-32.5 14.1-47.7V380c-0.1-21.9-8.2-38.8-23-47.6z m-34.9 58.7v321c0 10.4-10.3 28.1-19.4 33.2L589.5 893.9V566.2c0-10.1 10.2-27.2 19.1-32.1l263.2-143z" p-id="8239"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch -->
<title>Vue</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="69.644116%" y1="0%" x2="69.644116%" y2="100%" id="linearGradient-1">
<stop stop-color="#29CDFF" offset="0%"></stop>
<stop stop-color="#148EFF" offset="37.8600687%"></stop>
<stop stop-color="#0A60FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-19.8191553%" y1="-36.7931464%" x2="138.57919%" y2="157.637507%" id="linearGradient-2">
<stop stop-color="#29CDFF" offset="0%"></stop>
<stop stop-color="#0F78FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="68.1279872%" y1="-35.6905737%" x2="30.4400914%" y2="114.942679%" id="linearGradient-3">
<stop stop-color="#FA8E7D" offset="0%"></stop>
<stop stop-color="#F74A5C" offset="51.2635191%"></stop>
<stop stop-color="#F51D2C" offset="100%"></stop>
</linearGradient>
</defs>
<g id="AntVue" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" transform="translate(19.000000, 9.000000)">
<path d="M89.96,90.48 C78.58,93.48 68.33,83.36 67.62,82.48 L46.6604487,62.2292258 C45.5023849,61.1103236 44.8426845,59.5728835 44.8296987,57.9626396 L44.5035564,17.5209948 C44.4948861,16.4458744 44.0537714,15.4195095 43.2796864,14.6733517 L29.6459999,1.53153737 C28.055475,-0.00160504005 25.5232423,0.0449126588 23.9900999,1.63543756 C23.2715121,2.38092066 22.87,3.37600834 22.87,4.41143746 L22.87,64.3864751 C22.87,67.0807891 23.9572233,69.6611067 25.885409,71.5429748 L63.6004615,108.352061 C65.9466323,110.641873 69.6963584,110.624605 72.0213403,108.313281" id="Path-Copy" fill="url(#linearGradient-1)" fill-rule="nonzero" transform="translate(56.415000, 54.831157) scale(-1, 1) translate(-56.415000, -54.831157) "></path>
<path d="M68,90.1163122 C56.62,93.1163122 45.46,83.36 44.75,82.48 L23.7904487,62.2292258 C22.6323849,61.1103236 21.9726845,59.5728835 21.9596987,57.9626396 L21.6335564,17.5209948 C21.6248861,16.4458744 21.1837714,15.4195095 20.4096864,14.6733517 L6.7759999,1.53153737 C5.185475,-0.00160504005 2.65324232,0.0449126588 1.12009991,1.63543756 C0.401512125,2.38092066 3.90211878e-13,3.37600834 3.90798505e-13,4.41143746 L3.94351218e-13,64.3864751 C3.94681177e-13,67.0807891 1.08722326,69.6611067 3.01540903,71.5429748 L40.7807092,108.401101 C43.1069304,110.671444 46.8180151,110.676525 49.1504445,108.412561" id="Path" fill="url(#linearGradient-2)" fill-rule="nonzero"></path>
<path d="M43.2983488,19.0991931 L27.5566079,3.88246244 C26.7624281,3.11476967 26.7409561,1.84862177 27.5086488,1.05444194 C27.8854826,0.664606611 28.4044438,0.444472651 28.9466386,0.444472651 L60.3925021,0.444472651 C61.4970716,0.444472651 62.3925021,1.33990315 62.3925021,2.44447265 C62.3925021,2.9858375 62.1730396,3.50407742 61.7842512,3.88079942 L46.0801285,19.0975301 C45.3051579,19.8484488 44.0742167,19.8491847 43.2983488,19.0991931 Z" id="Path" fill="url(#linearGradient-3)"></path>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

2
cmdb-ui/src/bus/index.js Normal file
View File

@@ -0,0 +1,2 @@
import Vue from 'vue'
export default new Vue()

View File

@@ -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>

View File

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

View File

@@ -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>

View File

@@ -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'
},
/**
* 头像大小 类型: largesmall 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>

View File

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

View File

@@ -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;
}
}
}
}
}

View File

@@ -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 | - |

View File

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

View 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>

View 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: '<=' },
]

View 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>

View 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>

View 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>

View 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>

View File

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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,13 +0,0 @@
.antv-chart-mini {
position: relative;
width: 100%;
.chart-wrapper {
position: absolute;
bottom: -28px;
width: 100%;
/* margin: 0 -5px;
overflow: hidden;*/
}
}

View File

@@ -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%;
}
}

View 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>

View File

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

View File

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

View File

@@ -1,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 | -|

View 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>

View 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>

View 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>

View 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>

View 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
// 获取规则数组[012345星期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调整为0week值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>

View 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>

View 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>

View 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>

View 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>

View File

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

View 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;
}
}

View File

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

View 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>

View 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>

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View 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>

View 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>

View File

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

View 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>

View File

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

View File

@@ -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>

View File

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

View File

@@ -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>

View File

@@ -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>

Some files were not shown because too many files have changed in this diff Show More