mirror of
https://github.com/veops/cmdb.git
synced 2025-11-07 23:46:11 +08:00
前后端全面升级
This commit is contained in:
72
cmdb-ui/src/modules/acl/api/app.js
Normal file
72
cmdb-ui/src/modules/acl/api/app.js
Normal file
@@ -0,0 +1,72 @@
|
||||
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 searchApp(params = {}) {
|
||||
return axios({
|
||||
url: urlPrefix + '/apps',
|
||||
method: 'GET',
|
||||
params: { ...params, page_size: 9999 }
|
||||
})
|
||||
}
|
||||
|
||||
export function addApp(data) {
|
||||
return axios({
|
||||
url: urlPrefix + '/apps',
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateApp(aid, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/apps/${aid}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function getApp(aid) {
|
||||
return axios({
|
||||
url: urlPrefix + `/apps/${aid}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteApp(aid) {
|
||||
return axios({
|
||||
url: urlPrefix + `/apps/${aid}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
35
cmdb-ui/src/modules/acl/api/history.js
Normal file
35
cmdb-ui/src/modules/acl/api/history.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function searchPermissonHistory(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/audit_log/permission`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function searchRoleHistory(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/audit_log/role`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function searchResourceHistory(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/audit_log/resource`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function searchTriggerHistory(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/audit_log/trigger`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
136
cmdb-ui/src/modules/acl/api/permission.js
Normal file
136
cmdb-ui/src/modules/acl/api/permission.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function getResourcePerms(resourceID, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resources/${resourceID}/permissions`,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
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}/grant2`,
|
||||
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}/revoke2`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRoleResourceGroupPerm(rid, resourceGroupID, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resource_groups/${resourceGroupID}/revoke`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源组 清空按钮使用
|
||||
export function deleteRoleResourceGroupPerm2(rid, resourceGroupID, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resource_groups/${resourceGroupID}/revoke2`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function searchPermResourceByRoleId(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function roleHasPermissionToGrant(params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/roles/has_perm',
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源批量授权
|
||||
export function setBatchRoleResourcePerm(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources/batch/grant`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源组批量授权
|
||||
export function setBatchRoleResourceGroupPerm(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resource_groups/batch/grant`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源批量权限回收
|
||||
export function setBatchRoleResourceRevoke(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources/batch/revoke`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源组批量授权
|
||||
export function setBatchRoleResourceGroupRevoke(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resource_groups/batch/revoke`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 按资源名批量授权
|
||||
export function setBatchRoleResourceByResourceName(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources/batch/grant2`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 资源名批量回收
|
||||
export function setBatchRoleResourceRevokeByResourceName(rid, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/resources/batch/revoke2`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
104
cmdb-ui/src/modules/acl/api/resource.js
Normal file
104
cmdb-ui/src/modules/acl/api/resource.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function searchResource (params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resources`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addResource (params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/resources',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function updateResourceById (id, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resources/${id}`,
|
||||
method: 'PUT',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteResourceById (id) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resources/${id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
export function searchResourceType (params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resource_types`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addResourceType (params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/resource_types',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function updateResourceTypeById (id, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resource_types/${id}`,
|
||||
method: 'PUT',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteResourceTypeById (id) {
|
||||
return axios({
|
||||
url: urlPrefix + `/resource_types/${id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
// add resource group
|
||||
export function getResourceGroups (params) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/resource_groups`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addResourceGroup (data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/resource_groups`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateResourceGroup (_id, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/resource_groups/${_id}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteResourceGroup (_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/resource_groups/${_id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
export function getResourceGroupItems (_id) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/resource_groups/${_id}/items`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
83
cmdb-ui/src/modules/acl/api/role.js
Normal file
83
cmdb-ui/src/modules/acl/api/role.js
Normal file
@@ -0,0 +1,83 @@
|
||||
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, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${id}`,
|
||||
method: 'DELETE',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function addParentRole(id, otherID, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${id}/parents`,
|
||||
method: 'POST',
|
||||
data: { ...data, parent_id: otherID }
|
||||
})
|
||||
}
|
||||
|
||||
// export function addChildRole (id, otherID, data) {
|
||||
// return axios({
|
||||
// url: urlPrefix + `/roles/${otherID}/parents`,
|
||||
// method: 'POST',
|
||||
// data: {...data, parent_id: id }
|
||||
// })
|
||||
// }
|
||||
|
||||
export function delParentRole(cid, pid, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${cid}/parents`,
|
||||
method: 'DELETE',
|
||||
data: { ...data, parent_id: pid }
|
||||
})
|
||||
}
|
||||
|
||||
// export function delChildRole (pid, cid, data) {
|
||||
// return axios({
|
||||
// url: urlPrefix + `/roles/${cid}/parents`,
|
||||
// method: 'DELETE',
|
||||
// data: { data, parent_id: pid }
|
||||
// })
|
||||
// }
|
||||
|
||||
export function getUsersUnderRole(rid, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${rid}/users`,
|
||||
method: 'GET',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
export function addBatchParentRole(parent_id, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/roles/${parent_id}/children`,
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
16
cmdb-ui/src/modules/acl/api/secretKey.js
Normal file
16
cmdb-ui/src/modules/acl/api/secretKey.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getSecret() {
|
||||
return axios({
|
||||
url: '/v1/acl/users/secret',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function updateSecret(data) {
|
||||
return axios({
|
||||
url: '/v1/acl/users/reset_key_secret',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
56
cmdb-ui/src/modules/acl/api/trigger.js
Normal file
56
cmdb-ui/src/modules/acl/api/trigger.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function getTriggers(params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/triggers',
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addTrigger(data) {
|
||||
return axios({
|
||||
url: urlPrefix + '/triggers',
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTrigger(tid, data) {
|
||||
return axios({
|
||||
url: urlPrefix + `/triggers/${tid}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTrigger(tid) {
|
||||
return axios({
|
||||
url: urlPrefix + `/triggers/${tid}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
export function applyTrigger(tid) {
|
||||
return axios({
|
||||
url: urlPrefix + `/triggers/${tid}/apply`,
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
|
||||
export function cancelTrigger(tid) {
|
||||
return axios({
|
||||
url: urlPrefix + `/triggers/${tid}/cancel`,
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
|
||||
export function patternResults(params) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/triggers/resources`,
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
49
cmdb-ui/src/modules/acl/api/user.js
Normal file
49
cmdb-ui/src/modules/acl/api/user.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/* eslint-disable */
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v1/acl'
|
||||
|
||||
export function currentUser() {
|
||||
return axios({
|
||||
url: urlPrefix + `/users/info`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function getOnDutyUser() {
|
||||
return axios({
|
||||
url: urlPrefix + '/users/employee',
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function searchUser(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/users`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function addUser(params) {
|
||||
return axios({
|
||||
url: urlPrefix + '/users',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function updateUserById(id, params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/users/${id}`,
|
||||
method: 'PUT',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteUserById(id) {
|
||||
return axios({
|
||||
url: urlPrefix + `/users/${id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
9
cmdb-ui/src/modules/acl/constants/constants.js
Normal file
9
cmdb-ui/src/modules/acl/constants/constants.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const valueTypeMap = {
|
||||
'0': '整数',
|
||||
'1': '浮点数',
|
||||
'2': '文本',
|
||||
'3': 'datetime',
|
||||
'4': 'date',
|
||||
'5': 'time',
|
||||
'6': 'json'
|
||||
}
|
||||
6
cmdb-ui/src/modules/acl/index.js
Normal file
6
cmdb-ui/src/modules/acl/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import genAclRoutes from './router'
|
||||
|
||||
export default {
|
||||
name: 'acl',
|
||||
route: genAclRoutes
|
||||
}
|
||||
100
cmdb-ui/src/modules/acl/router/index.js
Normal file
100
cmdb-ui/src/modules/acl/router/index.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { BasicLayout, RouteView } from '@/layouts/index'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
|
||||
const genAppRoute = ({ name }) => {
|
||||
return {
|
||||
path: `/acl/${name}`,
|
||||
component: RouteView,
|
||||
meta: { title: name, icon: 'solution', permission: [`${name}_admin`, 'acl_admin'] },
|
||||
children: [
|
||||
{
|
||||
path: `/acl/${name}/roles`,
|
||||
name: `${name}_roles_acl`,
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/roles'),
|
||||
meta: { title: '角色管理', icon: 'team', keepAlive: true }
|
||||
},
|
||||
{
|
||||
path: `/acl/${name}/resources`,
|
||||
name: `${name}_resources_acl`,
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/resources'),
|
||||
meta: { title: '资源管理', icon: 'credit-card', keepAlive: false }
|
||||
},
|
||||
{
|
||||
path: `/acl/${name}/resource_types`,
|
||||
name: `${name}_resource_types_acl`,
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/resource_types'),
|
||||
meta: { title: '资源类型', icon: 'file-text', keepAlive: true }
|
||||
},
|
||||
{
|
||||
path: `/acl/${name}/trigger`,
|
||||
name: `${name}_trigger_acl`,
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/trigger'),
|
||||
meta: { title: '触发器', icon: 'clock-circle', keepAlive: true }
|
||||
},
|
||||
{
|
||||
path: `/acl/${name}/history`,
|
||||
name: `${name}_history_acl`,
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/history'),
|
||||
meta: { title: '操作审计', icon: 'search', keepAlive: false }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const genAclRoutes = async () => {
|
||||
const aclApps = await searchApp()
|
||||
const routes = {
|
||||
path: '/acl',
|
||||
name: 'acl',
|
||||
component: BasicLayout,
|
||||
meta: { title: 'ACL', keepAlive: true },
|
||||
redirect: '/acl/secret_key',
|
||||
children: [
|
||||
{
|
||||
path: `/acl/secret_key`,
|
||||
name: 'acl_secret_key',
|
||||
component: () => import('../views/secretKey'),
|
||||
meta: { title: '用户密钥', icon: 'key' }
|
||||
},
|
||||
{
|
||||
path: `/acl/operate_history`,
|
||||
name: 'acl_operate_history',
|
||||
component: () => import('../views/operation_history/index.vue'),
|
||||
// meta: { title: '操作审计', icon: 'search', permission: ['acl_admin'] },
|
||||
meta: { title: '操作审计', icon: 'search' }
|
||||
},
|
||||
{
|
||||
path: `/acl/user`,
|
||||
name: 'acl_user',
|
||||
component: () => import('../views/users'),
|
||||
meta: { title: '用户管理', icon: 'user', permission: ['acl_admin'] }
|
||||
},
|
||||
{
|
||||
path: `/acl/roles`,
|
||||
name: `acl_roles`,
|
||||
component: () => import('../views/roles'),
|
||||
meta: { title: '角色管理', icon: 'team', keepAlive: true, permission: ['acl_admin'] }
|
||||
},
|
||||
{
|
||||
path: `/acl/apps`,
|
||||
name: 'acl_apps',
|
||||
component: () => import('../views/apps'),
|
||||
meta: { title: '应用管理', icon: 'appstore', permission: ['acl_admin'] }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
aclApps.apps.forEach(app => {
|
||||
if (app.name !== 'acl') {
|
||||
routes.children.push(genAppRoute(app))
|
||||
}
|
||||
})
|
||||
return routes
|
||||
}
|
||||
|
||||
export default genAclRoutes
|
||||
0
cmdb-ui/src/modules/acl/store/index.js
Normal file
0
cmdb-ui/src/modules/acl/store/index.js
Normal file
0
cmdb-ui/src/modules/acl/style/index.css
Normal file
0
cmdb-ui/src/modules/acl/style/index.css
Normal file
0
cmdb-ui/src/modules/acl/style/index.less
Normal file
0
cmdb-ui/src/modules/acl/style/index.less
Normal file
118
cmdb-ui/src/modules/acl/views/apps.vue
Normal file
118
cmdb-ui/src/modules/acl/views/apps.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-card title="应用管理" class="acl-app-wrapper" :style="{ '--ant-body-height': `${windowHeight - 150}px` }">
|
||||
<a-row :guttr="24">
|
||||
<a-col v-for="app in apps" :key="app.id" :span="8">
|
||||
<div style="margin: 15px">
|
||||
<a-card class="acl-app-single-card">
|
||||
<a-card-meta :title="app.name">
|
||||
<div slot="description" :title="app.description || ''">{{ app.description || '无' }}</div>
|
||||
<a-avatar style="background-color: #5dc2f1" slot="avatar">{{ app.name[0].toUpperCase() }}</a-avatar>
|
||||
</a-card-meta>
|
||||
<template class="ant-card-actions" slot="actions">
|
||||
<a-icon type="edit" @click="handleEditApp(app)" />
|
||||
<a-icon type="delete" @click="handleDeleteApp(app)" />
|
||||
</template>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<div class="acl-app-add" @click="handleCreateApp">
|
||||
<span class="acl-app-add-icon" style="">+</span>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<app-form ref="appForm" @refresh="loadApps"></app-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
import { mapState } from 'vuex'
|
||||
import { searchApp, addApp, updateApp, deleteApp } from '@/modules/acl/api/app'
|
||||
import AppForm from './module/appForm'
|
||||
|
||||
export default {
|
||||
name: 'AclApps',
|
||||
data() {
|
||||
return {
|
||||
apps: [],
|
||||
selected: null,
|
||||
modalVisible: false,
|
||||
allUsers: [],
|
||||
permissionRoles: [],
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
components: { AppForm },
|
||||
mounted() {
|
||||
this.loadApps()
|
||||
},
|
||||
methods: {
|
||||
loadApps() {
|
||||
searchApp().then((res) => {
|
||||
this.apps = res.apps
|
||||
})
|
||||
},
|
||||
handleCreateApp() {
|
||||
this.selected = null
|
||||
this.$refs.appForm.handleEdit()
|
||||
},
|
||||
handleDeleteApp(app) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '危险操作',
|
||||
content: '确定要删除该App吗?',
|
||||
onOk() {
|
||||
deleteApp(app.id).then((res) => {
|
||||
that.$message.success(`删除成功:${app.name}`)
|
||||
that.loadApps()
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
handleEditApp(app) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.appForm.handleEdit(app)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.acl-app-wrapper {
|
||||
> .ant-card-body {
|
||||
overflow-y: auto;
|
||||
height: var(--ant-body-height);
|
||||
cursor: default;
|
||||
}
|
||||
.acl-app-single-card {
|
||||
.ant-card-meta-description > div {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0 2px 8px #0000004d;
|
||||
}
|
||||
}
|
||||
.acl-app-add {
|
||||
height: 141px;
|
||||
margin: 15px;
|
||||
border: 1px #e8e8e8 solid;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
box-shadow: 0 2px 8px #0000004d;
|
||||
}
|
||||
.acl-app-add-icon {
|
||||
font-size: 70px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: #5dc2f1;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
237
cmdb-ui/src/modules/acl/views/history.vue
Normal file
237
cmdb-ui/src/modules/acl/views/history.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-card :bordered="false">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="权限变更">
|
||||
<permisson-history-table
|
||||
v-if="isloaded"
|
||||
:allResourceTypes="allResourceTypes"
|
||||
:allResources="allResources"
|
||||
:allUsers="allUsers"
|
||||
:allRoles="allRoles"
|
||||
:allRolesMap="allRolesMap"
|
||||
:allUsersMap="allUsersMap"
|
||||
:allResourceTypesMap="allResourceTypesMap"
|
||||
@loadMoreResources="loadMoreResources"
|
||||
@reloadResources="reloadResources"
|
||||
@fetchResources="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></permisson-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="角色变更">
|
||||
<role-history-table
|
||||
v-if="isloaded"
|
||||
:allUsers="allUsers"
|
||||
:allRoles="allRoles"
|
||||
:allRolesMap="allRolesMap"
|
||||
:allUsersMap="allUsersMap"
|
||||
></role-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="资源变更">
|
||||
<resource-history-table
|
||||
v-if="isloaded"
|
||||
:allResources="allResources"
|
||||
:allUsers="allUsers"
|
||||
:allRoles="allRoles"
|
||||
:allRolesMap="allRolesMap"
|
||||
:allUsersMap="allUsersMap"
|
||||
:allResourcesMap="allResourcesMap"
|
||||
@loadMoreResources="loadMoreResources"
|
||||
@reloadResources="reloadResources"
|
||||
@fetchResources="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></resource-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="4" tab="资源类型变更">
|
||||
<resource-type-history-table
|
||||
v-if="isloaded"
|
||||
:allResourceTypes="allResourceTypes"
|
||||
:allUsers="allUsers"
|
||||
:allRoles="allRoles"
|
||||
:allRolesMap="allRolesMap"
|
||||
:allUsersMap="allUsersMap"
|
||||
:allResourceTypesMap="allResourceTypesMap"
|
||||
></resource-type-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" tab="触发器变更">
|
||||
<trigger-history-table
|
||||
v-if="isloaded"
|
||||
:allTriggers="allTriggers"
|
||||
:allUsers="allUsers"
|
||||
:allRoles="allRoles"
|
||||
:allRolesMap="allRolesMap"
|
||||
:allUsersMap="allUsersMap"
|
||||
:allTriggersMap="allTriggersMap"
|
||||
:allResourceTypesMap="allResourceTypesMap"
|
||||
></trigger-history-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import permissonHistoryTable from './module/permissionHistoryTable.vue'
|
||||
import roleHistoryTable from './module/roleHistoryTable.vue'
|
||||
import resourceHistoryTable from './module/resourceHistoryTable.vue'
|
||||
import triggerHistoryTable from './module/triggerHistoryTable.vue'
|
||||
import resourceTypeHistoryTable from './module/resourceTypeHistoryTable.vue'
|
||||
import { getTriggers } from '@/modules/acl/api/trigger'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
import { searchResource, searchResourceType } from '@/modules/acl/api/resource'
|
||||
export default {
|
||||
components: {
|
||||
permissonHistoryTable,
|
||||
roleHistoryTable,
|
||||
resourceHistoryTable,
|
||||
triggerHistoryTable,
|
||||
resourceTypeHistoryTable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
isloaded: false,
|
||||
resourcesNum: 0,
|
||||
resourcesPage: 1,
|
||||
allResourceTypes: [],
|
||||
allResources: [],
|
||||
allUsers: [],
|
||||
allRoles: [],
|
||||
allTriggers: [],
|
||||
allRolesMap: new Map(),
|
||||
allUsersMap: new Map(),
|
||||
allResourceTypesMap: new Map(),
|
||||
allResourcesMap: new Map(),
|
||||
allTriggersMap: new Map(),
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.initData()
|
||||
this.isloaded = true
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
try {
|
||||
this.isloaded = false
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
this.allResources = []
|
||||
this.resourcesPage = 1
|
||||
await this.initData()
|
||||
} finally {
|
||||
this.isloaded = true
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async initData() {
|
||||
await Promise.all([
|
||||
this.getAllRoles(),
|
||||
this.getAllUsers(),
|
||||
this.getAllResourceTypes(),
|
||||
this.getAllResources(1),
|
||||
this.getTriggers(),
|
||||
])
|
||||
},
|
||||
async getAllRoles() {
|
||||
const { roles } = await searchRole({ app_id: this.app_id, page_size: 9999 })
|
||||
const allRoles = []
|
||||
const allRolesMap = new Map()
|
||||
roles.forEach((item) => {
|
||||
allRoles.push({ [item.name]: item.id })
|
||||
allRolesMap.set(item.id, item.name)
|
||||
})
|
||||
this.allRoles = allRoles
|
||||
this.allRolesMap = allRolesMap
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000 })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach((item) => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
},
|
||||
async getAllResourceTypes() {
|
||||
const { groups } = await searchResourceType({ app_id: this.app_id, page_size: 9999, page: 1 })
|
||||
const allResourceTypes = []
|
||||
const allResourceTypesMap = new Map()
|
||||
groups.forEach((item) => {
|
||||
allResourceTypes.push({ [item.name]: item.id })
|
||||
allResourceTypesMap.set(item.id, item.name)
|
||||
})
|
||||
this.allResourceTypes = allResourceTypes
|
||||
this.allResourceTypesMap = allResourceTypesMap
|
||||
},
|
||||
async getAllResources(page = 1, value = undefined) {
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: page,
|
||||
page_size: 50,
|
||||
app_id: this.app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
const allResources = this.allResources
|
||||
const allResourcesMap = this.allResourcesMap
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
allResourcesMap.set(item.id, item.name)
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.allResourcesMap = allResourcesMap
|
||||
},
|
||||
reloadResources() {
|
||||
this.resourcesPage = 1
|
||||
this.allResources = []
|
||||
this.allResourcesMap = new Map()
|
||||
this.getAllResources()
|
||||
},
|
||||
loadMoreResources(value) {
|
||||
if (this.allResources.length < this.resourcesNum) {
|
||||
let currentPage = this.resourcesPage
|
||||
this.getAllResources(++currentPage, value)
|
||||
this.resourcesPage = currentPage
|
||||
}
|
||||
},
|
||||
resourceClear() {
|
||||
this.resourcesPage = 1
|
||||
this.allResources = []
|
||||
this.getAllResources()
|
||||
},
|
||||
async fetchResources(value) {
|
||||
const allResources = []
|
||||
const allResourcesMap = new Map()
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
this.resourcesPage = 1
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
allResourcesMap.set(item.id, item.name)
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.allResourcesMap = allResourcesMap
|
||||
},
|
||||
async getTriggers() {
|
||||
const res = await getTriggers({ app_id: this.app_id })
|
||||
const allTriggers = []
|
||||
const allTriggersMap = new Map()
|
||||
res.forEach((item) => {
|
||||
allTriggers.push({ [item.name]: item.id })
|
||||
allTriggersMap.set(item.id, item.name)
|
||||
})
|
||||
this.allTriggers = allTriggers
|
||||
this.allTriggersMap = allTriggersMap
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
85
cmdb-ui/src/modules/acl/views/module/appForm.vue
Normal file
85
cmdb-ui/src/modules/acl/views/module/appForm.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<CustomDrawer @close="handleClose" width="500" :title="title" :visible="visible" :closable="false">
|
||||
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="应用名称">
|
||||
<a-input v-decorator="['name', { rules: [{ required: true, message: '请输入应用名称' }] }]"> </a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述">
|
||||
<a-input v-decorator="['description', { rules: [{ required: true, message: '请输入描述' }] }]"> </a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="AppId">
|
||||
<a-input v-decorator="['app_id', { rules: [{ required: false }] }]" :disabled="mode === 'update'"> </a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="SecretKey">
|
||||
<a-input v-decorator="['secret_key', { rules: [{ required: false }] }]" :disabled="mode === 'update'">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-input v-decorator="['id']" type="hidden" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="handleClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">提交</a-button>
|
||||
</div>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { addApp, updateApp, getApp } from '@/modules/acl/api/app'
|
||||
|
||||
export default {
|
||||
name: 'AppForm',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
title: '创建应用',
|
||||
mode: 'create',
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
methods: {
|
||||
handleEdit(ele) {
|
||||
this.visible = true
|
||||
if (ele) {
|
||||
this.title = '修改应用'
|
||||
this.mode = 'update'
|
||||
console.log(ele)
|
||||
const { name, description } = ele
|
||||
getApp(ele.id).then((res) => {
|
||||
const { app_id, secret_key } = res
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({ name, description, app_id, secret_key, id: ele.id })
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.mode = 'create'
|
||||
this.title = '创建应用'
|
||||
}
|
||||
},
|
||||
handleClose() {
|
||||
this.visible = false
|
||||
this.form.resetFields()
|
||||
},
|
||||
async handleSubmit() {
|
||||
this.form.validateFields(async (err, values) => {
|
||||
if (err) {
|
||||
return
|
||||
}
|
||||
if (values.id) {
|
||||
await updateApp(values.id, values).then((res) => {
|
||||
this.$message.success('修改成功!')
|
||||
})
|
||||
} else {
|
||||
await addApp(values).then((res) => {
|
||||
this.$message.success('创建成功!')
|
||||
})
|
||||
}
|
||||
this.handleClose()
|
||||
this.$emit('refresh')
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
116
cmdb-ui/src/modules/acl/views/module/pager.vue
Normal file
116
cmdb-ui/src/modules/acl/views/module/pager.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-row class="row" type="flex" justify="end">
|
||||
<a-col>
|
||||
<a-space align="end">
|
||||
<a-button class="left-button" size="small" :disabled="prevIsDisabled" @click="prevPage"><a-icon type="left" /></a-button>
|
||||
<a-button class="page-button" size="small">{{ currentPage }}</a-button>
|
||||
<a-button class="right-button" size="small" :disabled="nextIsDisabled" @click="nextPage"><a-icon type="right" /></a-button>
|
||||
<a-dropdown class="dropdown" size="small" placement="topCenter" :trigger="['click']" :disabled="dropdownIsDisabled">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)">
|
||||
{{ size }}条/页
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button size="small"> {{ pageSize }}条/页 <a-icon type="down" /> </a-button>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
currentPage: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dropdownIsDisabled: false,
|
||||
prevIsDisabled: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nextIsDisabled() {
|
||||
return this.isLoading || this.total < this.pageSize
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isLoading: {
|
||||
immediate: true,
|
||||
handler: function (val) {
|
||||
if (val === true) {
|
||||
this.dropdownIsDisabled = true
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.dropdownIsDisabled = false
|
||||
if (this.currentPage === 1) {
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.prevIsDisabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
currentPage: {
|
||||
immediate: true,
|
||||
handler: function (val) {
|
||||
if (val === 1) {
|
||||
this.prevIsDisabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleItemClick(size) {
|
||||
this.$emit('showSizeChange', size)
|
||||
},
|
||||
nextPage() {
|
||||
const pageNum = this.currentPage + 1
|
||||
this.$emit('change', pageNum)
|
||||
},
|
||||
prevPage() {
|
||||
const pageNum = this.currentPage - 1
|
||||
this.$emit('change', pageNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row{
|
||||
margin-top: 5px;
|
||||
.left-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.right-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.page-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
268
cmdb-ui/src/modules/acl/views/module/permCollectForm.vue
Normal file
268
cmdb-ui/src/modules/acl/views/module/permCollectForm.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:title="`权限汇总: ${user.nickname}`"
|
||||
:visible="visible"
|
||||
placement="left"
|
||||
width="100%"
|
||||
@close="visible = false"
|
||||
:hasFooter="false"
|
||||
>
|
||||
<a-tabs @change="handleSwitchApp">
|
||||
<a-tab-pane
|
||||
v-for="app in displayApps"
|
||||
:key="app.id"
|
||||
:tab="app.name.slice(0, 1).toUpperCase() + app.name.slice(1)"
|
||||
>
|
||||
<a-tabs
|
||||
v-if="resourceTypes && resourceTypes.length"
|
||||
:animated="false"
|
||||
v-model="currentResourceId"
|
||||
@change="loadResource()"
|
||||
>
|
||||
<a-tab-pane v-for="rType in resourceTypes" :key="rType.id" :tab="rType.name">
|
||||
<a-spin :spinning="spinning">
|
||||
<vxe-table :max-height="`${windowHeight - 230}px`" :data="resources" ref="rTable">
|
||||
<vxe-column
|
||||
field="name"
|
||||
title="资源名"
|
||||
width="30%"
|
||||
:filters="[{ data: '' }]"
|
||||
:filter-method="filterNameMethod"
|
||||
:filter-recover-method="filterNameRecoverMethod"
|
||||
>
|
||||
<template #filter="{ $panel, column }">
|
||||
<template v-for="(option, index) in column.filters">
|
||||
<input
|
||||
class="my-input"
|
||||
type="type"
|
||||
:key="index"
|
||||
v-model="option.data"
|
||||
@input="$panel.changeOption($event, !!option.data, option)"
|
||||
@keyup.enter="$panel.confirmFilter()"
|
||||
placeholder="按回车确认筛选"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="permissions" title="权限列表" width="70%">
|
||||
<template #default="{row}">
|
||||
<a-tag color="cyan" v-for="(r, index) in row.permissions" :key="index">{{ r }}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<!-- <a-table
|
||||
:columns="tableColumns"
|
||||
:dataSource="resources"
|
||||
:rowKey="record=>record.id"
|
||||
:pagination="{ showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条记录` }"
|
||||
showPagination="auto"
|
||||
ref="rTable"
|
||||
size="middle">
|
||||
<div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown">
|
||||
<a-input
|
||||
v-ant-ref="c => searchInput = c"
|
||||
:placeholder="` ${column.title}`"
|
||||
:value="selectedKeys[0]"
|
||||
@change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
|
||||
@pressEnter="() => handleSearch(selectedKeys, confirm, column)"
|
||||
style="width: 188px; margin-bottom: 8px; display: block;"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="() => handleSearch(selectedKeys, confirm, column)"
|
||||
icon="search"
|
||||
size="small"
|
||||
style="width: 90px; margin-right: 8px"
|
||||
>搜索</a-button>
|
||||
<a-button
|
||||
@click="() => handleReset(clearFilters, column)"
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
>重置</a-button>
|
||||
</div>
|
||||
<a-icon slot="filterIcon" slot-scope="filtered" type="search" :style="{ color: filtered ? '#108ee9' : undefined }" />
|
||||
|
||||
<template slot="nameSearchRender" slot-scope="text">
|
||||
<span v-if="columnSearchText.name">
|
||||
<template v-for="(fragment, i) in text.toString().split(new RegExp(`(?<=${columnSearchText.name})|(?=${columnSearchText.name})`, 'i'))">
|
||||
<mark v-if="fragment.toLowerCase() === columnSearchText.name.toLowerCase()" :key="i" class="highlight">{{ fragment }}</mark>
|
||||
<template v-else>{{ fragment }}</template>
|
||||
</template>
|
||||
</span>
|
||||
<template v-else>{{ text }}</template>
|
||||
</template>
|
||||
<template slot="permissions" slot-scope="record">
|
||||
<a-tag color="cyan" v-for="(r, index) in record" :key="index">{{ r }}</a-tag>
|
||||
</template>
|
||||
</a-table> -->
|
||||
</a-spin>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { searchApp, searchRole } from '@/modules/acl/api/app'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
import { searchPermResourceByRoleId } from '@/modules/acl/api/permission'
|
||||
|
||||
export default {
|
||||
name: 'PermCollectForm',
|
||||
data() {
|
||||
return {
|
||||
spinning: false,
|
||||
visible: false,
|
||||
user: {},
|
||||
roles: [],
|
||||
apps: [],
|
||||
currentAppId: 0,
|
||||
currentResourceId: 0,
|
||||
resourceTypes: [],
|
||||
resourceTypePerms: [],
|
||||
resources: [],
|
||||
// tableColumns: [
|
||||
// {
|
||||
// title: '资源名',
|
||||
// dataIndex: 'name',
|
||||
// sorter: false,
|
||||
// width: 150,
|
||||
// // scopedSlots: {
|
||||
// // customRender: 'nameSearchRender',
|
||||
// // filterDropdown: 'filterDropdown',
|
||||
// // filterIcon: 'filterIcon'
|
||||
// // },
|
||||
// // onFilter: (value, record) => record.name && record.name.toLowerCase().includes(value.toLowerCase()),
|
||||
// // onFilterDropdownVisibleChange: (visible) => {
|
||||
// // if (visible) {
|
||||
// // setTimeout(() => {
|
||||
// // this.searchInput.focus()
|
||||
// // }, 0)
|
||||
// // }
|
||||
// // }
|
||||
// },
|
||||
// {
|
||||
// title: '权限列表',
|
||||
// dataIndex: 'permissions',
|
||||
// width: 300,
|
||||
// scopedSlots: { customRender: 'permissions' },
|
||||
// },
|
||||
// ],
|
||||
columnSearchText: {
|
||||
name: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: state => state.windowHeight,
|
||||
}),
|
||||
displayApps() {
|
||||
const roles = this.$store.state.user.roles.permissions
|
||||
if (roles.includes('acl_admin')) {
|
||||
return this.apps
|
||||
}
|
||||
return this.apps.filter(item => {
|
||||
if (roles.includes(`${item.name}_admin`)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadApps()
|
||||
},
|
||||
methods: {
|
||||
collect(user) {
|
||||
this.user = user
|
||||
this.visible = true
|
||||
setTimeout(() => {
|
||||
this.loadResource()
|
||||
}, 500)
|
||||
},
|
||||
handleSearch(selectedKeys, confirm, column) {
|
||||
confirm()
|
||||
this.columnSearchText[column.dataIndex] = selectedKeys[0]
|
||||
},
|
||||
handleReset(clearFilters, column) {
|
||||
clearFilters()
|
||||
this.columnSearchText[column.dataIndex] = ''
|
||||
},
|
||||
async loadRoles(_appId) {
|
||||
const res = await searchRole({ app_id: _appId, page_size: 9999, is_all: true })
|
||||
this.roles = res.roles.filter(item => item.uid)
|
||||
},
|
||||
async handleSwitchApp(appId) {
|
||||
this.currentAppId = appId
|
||||
await this.loadResourceTypes(appId)
|
||||
},
|
||||
async loadApps() {
|
||||
const res = await searchApp()
|
||||
this.apps = res.apps
|
||||
if (this.displayApps[0]) {
|
||||
this.currentAppId = this.displayApps[0].id
|
||||
await this.loadRoles(this.apps[0].id)
|
||||
await this.loadResourceTypes(this.displayApps[0].id)
|
||||
} else {
|
||||
this.$message.info('No apps!')
|
||||
}
|
||||
},
|
||||
async loadResourceTypes(appId) {
|
||||
const res = await searchResourceType({ app_id: appId, page_size: 9999 })
|
||||
this.resourceTypes = res['groups']
|
||||
this.resourceTypePerms = res['id2perms'][`${appId}`]
|
||||
this.currentResourceId = res['groups'][0] && res['groups'][0]['id']
|
||||
await this.loadResource()
|
||||
},
|
||||
async loadResource() {
|
||||
this.spinning = true
|
||||
const fil = this.roles.filter(role => role.uid === this.user.uid)
|
||||
if (!fil[0]) {
|
||||
return
|
||||
}
|
||||
const res = await searchPermResourceByRoleId(fil[0].id, {
|
||||
resource_type_id: this.currentResourceId,
|
||||
app_id: this.currentAppId,
|
||||
})
|
||||
this.resources = res['resources']
|
||||
this.spinning = false
|
||||
},
|
||||
filterNameMethod({ option, row }) {
|
||||
return row.name.toLowerCase().includes(option.data.toLowerCase())
|
||||
},
|
||||
filterNameRecoverMethod({ option }) {
|
||||
option.data = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.custom-filter-dropdown {
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: rgb(255, 192, 105);
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
199
cmdb-ui/src/modules/acl/views/module/permissionForm.vue
Normal file
199
cmdb-ui/src/modules/acl/views/module/permissionForm.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:closable="false"
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
@close="onClose"
|
||||
placement="right"
|
||||
width="30%"
|
||||
>
|
||||
<a-form :form="form" :layout="formLayout" @submit="handleSubmit">
|
||||
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" label="类型名">
|
||||
<a-input
|
||||
name="name"
|
||||
placeholder=""
|
||||
v-decorator="['name', { rules: [{ required: true, message: '请输入资源名' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" label="描述">
|
||||
<a-textarea placeholder="请输入描述信息..." name="description" :rows="4" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" label="权限">
|
||||
<div :style="{ borderBottom: '1px solid #E9E9E9' }">
|
||||
<a-checkbox :indeterminate="indeterminate" @change="onCheckAllChange" :checked="checkAll">
|
||||
全选
|
||||
</a-checkbox>
|
||||
</div>
|
||||
<br />
|
||||
<a-checkbox-group :options="plainOptions" v-model="perms" @change="onPermChange" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addResourceType, updateResourceTypeById } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceForm',
|
||||
data() {
|
||||
return {
|
||||
drawerTitle: '新增资源类型',
|
||||
drawerVisible: false,
|
||||
formLayout: 'vertical',
|
||||
perms: ['1'],
|
||||
indeterminate: true,
|
||||
checkAll: false,
|
||||
plainOptions: ['1', '2'],
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
computed: {
|
||||
formItemLayout() {
|
||||
const { formLayout } = this
|
||||
return formLayout === 'horizontal'
|
||||
? {
|
||||
labelCol: { span: 4 },
|
||||
wrapperCol: { span: 14 },
|
||||
}
|
||||
: {}
|
||||
},
|
||||
|
||||
horizontalFormItemLayout() {
|
||||
return {
|
||||
labelCol: { span: 5 },
|
||||
wrapperCol: { span: 12 },
|
||||
}
|
||||
},
|
||||
buttonItemLayout() {
|
||||
const { formLayout } = this
|
||||
return formLayout === 'horizontal'
|
||||
? {
|
||||
wrapperCol: { span: 14, offset: 4 },
|
||||
}
|
||||
: {}
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
onPermChange(perms) {
|
||||
this.indeterminate = !!perms.length && perms.length < this.plainOptions.length
|
||||
this.checkAll = perms.length === this.plainOptions.length
|
||||
},
|
||||
onCheckAllChange(e) {
|
||||
Object.assign(this, {
|
||||
perms: e.target.checked ? this.plainOptions : [],
|
||||
indeterminate: false,
|
||||
checkAll: e.target.checked,
|
||||
})
|
||||
},
|
||||
handleCreate() {
|
||||
this.drawerVisible = true
|
||||
},
|
||||
onClose() {
|
||||
this.form.resetFields()
|
||||
this.drawerVisible = false
|
||||
},
|
||||
onChange(e) {
|
||||
console.log(`checked = ${e}`)
|
||||
},
|
||||
|
||||
handleEdit(record) {
|
||||
this.drawerVisible = true
|
||||
console.log(record)
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
description: record.description,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
|
||||
values.app_id = this.$route.name.split('_')[0]
|
||||
values.perms = this.perms
|
||||
if (values.id) {
|
||||
this.updateResourceType(values.id, values)
|
||||
} else {
|
||||
this.createResourceType(values)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
updateResourceType(id, data) {
|
||||
updateResourceTypeById(id, data).then(res => {
|
||||
this.$message.success(`更新成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
createResourceType(data) {
|
||||
addResourceType(data).then(res => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
// requestFailed (err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// }
|
||||
},
|
||||
watch: {},
|
||||
props: {
|
||||
handleOk: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
308
cmdb-ui/src/modules/acl/views/module/permissionHistoryTable.vue
Normal file
308
cmdb-ui/src/modules/acl/views/module/permissionHistoryTable.vue
Normal file
@@ -0,0 +1,308 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="permissionTableAttrList"
|
||||
@search="handleSearch"
|
||||
@expandChange="handleExpandChange"
|
||||
@searchFormReset="searchFormReset"
|
||||
@loadMoreData="loadMoreResources"
|
||||
@fetchData="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
:scroll-y="{ enabled: false }"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.deleted_at || row.updated_at || row.created_at }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="row.operate_type === 'grant' ? 'green' : 'red'">{{
|
||||
operateTypeMap.get(row.operate_type)
|
||||
}}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="rid" title="用户"></vxe-column>
|
||||
<vxe-column field="resource_type_id" title="资源类型"></vxe-column>
|
||||
<vxe-column field="resources" title="资源">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.resource_ids.length > 0">
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
<span>{{ row.resource_ids[0] }}</span>
|
||||
</template>
|
||||
<a-tag color="blue" v-for="(resource, index) in row.resource_ids" :key="'resources_' + resource + index">
|
||||
{{ resource }}
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="row.group_ids.length > 0">
|
||||
<a-tag color="blue" v-for="(group, index) in row.group_ids" :key="'groups_' + group + index">
|
||||
{{ group }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="权限">
|
||||
<template #default="{ row }">
|
||||
<a-tag v-for="(perm, index) in row.permission_ids" :key="'perms_' + perm + index">
|
||||
{{ perm }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50, 100, 200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { searchPermissonHistory } from '@/modules/acl/api/history'
|
||||
import debounce from 'lodash/debounce'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
props: {
|
||||
allResourceTypes: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allResources: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allUsers: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allRolesMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
allUsersMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
allResourceTypesMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
this.fetchResources = debounce(this.fetchResources, 800)
|
||||
return {
|
||||
isExpand: false,
|
||||
loading: true,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
tableData: [],
|
||||
operateTypeMap: new Map([
|
||||
['grant', '授权'],
|
||||
['revoke', '撤销'],
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
start: '',
|
||||
end: '',
|
||||
},
|
||||
permissionTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3',
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: this.allUsers,
|
||||
},
|
||||
{
|
||||
alias: '用户',
|
||||
is_choice: true,
|
||||
name: 'rid',
|
||||
value_type: '2',
|
||||
choice_value: this.allRoles,
|
||||
},
|
||||
{
|
||||
alias: '资源类型',
|
||||
is_choice: true,
|
||||
name: 'resource_type_id',
|
||||
value_type: '2',
|
||||
choice_value: this.allResourceTypes,
|
||||
},
|
||||
{
|
||||
alias: '资源',
|
||||
is_choice: true,
|
||||
name: 'resource_id',
|
||||
value_type: '2',
|
||||
choice_value: this.allResources,
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [{ 授权: 'grant' }, { 撤销: 'revoke' }],
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
allResources: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.permissionTableAttrList[4].choice_value = val
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取数据
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data, id2groups, id2perms, id2resources, id2roles } = await searchPermissonHistory(
|
||||
this.handleQueryParams(queryParams)
|
||||
)
|
||||
data.forEach((item) => {
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
item.rid = id2roles[item.rid].name
|
||||
item.resource_type_id = this.allResourceTypesMap.get(item.resource_type_id)
|
||||
item.resource_ids.forEach((subItem, index) => {
|
||||
item.resource_ids[index] = id2resources[subItem].name
|
||||
})
|
||||
item.group_ids.forEach((subItem, index) => {
|
||||
item.group_ids[index] = id2groups[subItem].name
|
||||
})
|
||||
item.permission_ids.forEach((subItem, index) => {
|
||||
item.permission_ids[index] = id2perms[subItem].name
|
||||
})
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
loadMoreResources(name, value) {
|
||||
if (name === 'resource_id') {
|
||||
this.$emit('loadMoreResources', value)
|
||||
}
|
||||
},
|
||||
resourceClear() {
|
||||
this.$emit('resourceClear')
|
||||
},
|
||||
fetchResources(value) {
|
||||
if (value === '') {
|
||||
this.$emit('reloadResources')
|
||||
return
|
||||
}
|
||||
this.$emit('fetchResources', value)
|
||||
},
|
||||
// 处理查询参数
|
||||
handleQueryParams(queryParams) {
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
) {
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
148
cmdb-ui/src/modules/acl/views/module/resourceBatchPerm.vue
Normal file
148
cmdb-ui/src/modules/acl/views/module/resourceBatchPerm.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
title="便捷授权"
|
||||
width="500px"
|
||||
:maskClosable="false"
|
||||
:closable="true"
|
||||
:visible="visible"
|
||||
@close="handleClose"
|
||||
>
|
||||
<a-form :form="form">
|
||||
<a-form-item>
|
||||
<div slot="label" style="display: inline-block">
|
||||
<span>角色列表</span>
|
||||
<a-divider type="vertical" />
|
||||
<a-switch
|
||||
style="display: inline-block"
|
||||
checked-children="用户"
|
||||
un-checked-children="虚拟"
|
||||
@change="handleRoleTypeChange"
|
||||
v-model="roleType"
|
||||
/>
|
||||
</div>
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
size="small"
|
||||
v-decorator="['roleIdList', { rules: [{ required: true, message: '请选择角色名称' }] }]"
|
||||
multiple
|
||||
filterable
|
||||
placeholder="请选择角色名称,可多选!"
|
||||
>
|
||||
<el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="权限列表">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
size="small"
|
||||
name="permName"
|
||||
v-decorator="['permName', { rules: [{ required: true, message: '请选择权限' }] }]"
|
||||
multiple
|
||||
placeholder="请选择权限,可多选!"
|
||||
>
|
||||
<el-option v-for="perm in allPerms" :key="perm.name" :value="perm.name" :label="perm.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="资源名">
|
||||
<a-textarea
|
||||
v-decorator="['resource_names', { rules: [{ required: true, message: '请输入资源名,换行分隔' }] }]"
|
||||
:autoSize="{ minRows: 4 }"
|
||||
placeholder="请输入资源名,换行分隔"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="handleRevoke" type="danger" ghost>权限回收</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">授权</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Select, Option } from 'element-ui'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
import {
|
||||
getResourceTypePerms,
|
||||
setBatchRoleResourceByResourceName,
|
||||
setBatchRoleResourceRevokeByResourceName,
|
||||
} from '@/modules/acl/api/permission'
|
||||
export default {
|
||||
name: 'ResourceBatchPerm',
|
||||
components: { ElSelect: Select, ElOption: Option },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
resource_type_id: null,
|
||||
roleType: true,
|
||||
allRoles: [],
|
||||
allPerms: [],
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
methods: {
|
||||
open(currentTypeId) {
|
||||
this.visible = true
|
||||
this.resource_type_id = currentTypeId
|
||||
this.loadRoles(1)
|
||||
this.loadPerm(currentTypeId)
|
||||
},
|
||||
handleClose() {
|
||||
this.form.resetFields()
|
||||
this.roleType = true
|
||||
this.visible = false
|
||||
},
|
||||
handleRoleTypeChange(target) {
|
||||
this.loadRoles(Number(target))
|
||||
},
|
||||
loadRoles(isUserRole) {
|
||||
searchRole({ page_size: 9999, app_id: this.$route.name.split('_')[0], user_role: isUserRole }).then(res => {
|
||||
this.allRoles = res.roles
|
||||
})
|
||||
},
|
||||
loadPerm(resourceTypeId) {
|
||||
getResourceTypePerms(resourceTypeId).then(res => {
|
||||
this.allPerms = res
|
||||
})
|
||||
},
|
||||
handleSubmit() {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log(values)
|
||||
values.roleIdList.forEach(roleId => {
|
||||
setBatchRoleResourceByResourceName(roleId, {
|
||||
resource_names: values.resource_names.split('\n'),
|
||||
perms: values.permName,
|
||||
resource_type_id: this.resource_type_id,
|
||||
}).then(res => {
|
||||
this.$message.success('授权成功')
|
||||
this.form.resetFields()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleRevoke() {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log(values)
|
||||
values.roleIdList.forEach(roleId => {
|
||||
setBatchRoleResourceRevokeByResourceName(roleId, {
|
||||
resource_names: values.resource_names.split('\n'),
|
||||
perms: values.permName,
|
||||
resource_type_id: this.resource_type_id,
|
||||
}).then(res => {
|
||||
this.$message.success('权限回收成功')
|
||||
this.form.resetFields()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
151
cmdb-ui/src/modules/acl/views/module/resourceForm.vue
Normal file
151
cmdb-ui/src/modules/acl/views/module/resourceForm.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:closable="false"
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
@close="onClose"
|
||||
placement="right"
|
||||
width="30%"
|
||||
>
|
||||
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="资源名">
|
||||
<a-input
|
||||
name="name"
|
||||
placeholder=""
|
||||
v-decorator="['name', { rules: [{ required: true, message: '请输入资源名' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="资源类型">
|
||||
<a-select v-model="selectedTypeId">
|
||||
<a-select-option v-for="type in allTypes" :key="type.id">{{ type.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="是否组">
|
||||
<a-radio-group v-model="isGroup">
|
||||
<a-radio :value="true"> 是 </a-radio>
|
||||
<a-radio :value="false"> 否 </a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addResource, searchResourceType, addResourceGroup } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceForm',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
drawerTitle: '新增资源',
|
||||
drawerVisible: false,
|
||||
allTypes: [],
|
||||
isGroup: false,
|
||||
selectedTypeId: 0,
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
created() {
|
||||
this.getAllResourceTypes()
|
||||
},
|
||||
|
||||
computed: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
getAllResourceTypes() {
|
||||
searchResourceType({ page_size: 9999, app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.allTypes = res.groups
|
||||
})
|
||||
},
|
||||
handleCreate(defaultType) {
|
||||
this.drawerVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.selectedTypeId = defaultType.id
|
||||
})
|
||||
},
|
||||
onClose() {
|
||||
this.form.resetFields()
|
||||
this.drawerVisible = false
|
||||
this.$emit('fresh')
|
||||
},
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
values.type_id = this.selectedTypeId
|
||||
values.app_id = this.$route.name.split('_')[0]
|
||||
if (values.id) {
|
||||
this.$message.error('错误提示')
|
||||
} else {
|
||||
this.createResource(values)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
createResource(data) {
|
||||
if (!this.isGroup) {
|
||||
addResource(data).then((res) => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
} else {
|
||||
addResourceGroup(data).then((res) => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
}
|
||||
},
|
||||
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function (newValue, oldValue) {
|
||||
this.getAllResourceTypes()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
35
cmdb-ui/src/modules/acl/views/module/resourceGroupMember.vue
Normal file
35
cmdb-ui/src/modules/acl/views/module/resourceGroupMember.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<a-modal v-model="visible" :title="`组成员:${editRecord.name}`" :width="800" :footer="null">
|
||||
<div :style="{ maxHeight: '500px', overflow: 'auto' }">
|
||||
<a-tag :style="{ marginBottom: '5px' }" v-for="mem in members" :key="mem.name">
|
||||
{{ mem.name }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
import { getResourceGroupItems } from '@/modules/acl/api/resource'
|
||||
export default {
|
||||
name: 'ResourceGroupMember',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
editRecord: {},
|
||||
members: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleEdit(record) {
|
||||
this.visible = true
|
||||
this.editRecord = record
|
||||
this.loadMembers(record.id)
|
||||
},
|
||||
loadMembers(_id) {
|
||||
getResourceGroupItems(_id).then((res) => {
|
||||
this.members = res
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
241
cmdb-ui/src/modules/acl/views/module/resourceGroupModal.vue
Normal file
241
cmdb-ui/src/modules/acl/views/module/resourceGroupModal.vue
Normal file
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<a-modal v-model="visible" :title="`成员管理:${editRecord.name}`" @ok="handleSubmit" :width="690">
|
||||
<!-- <CustomTransfer
|
||||
ref="customTransfer"
|
||||
:show-search="true"
|
||||
:data-source="resources"
|
||||
:filter-option="filterOption"
|
||||
:target-keys="targetKeys"
|
||||
:render="(item) => item.title"
|
||||
@change="handleChange"
|
||||
@search="handleSearch"
|
||||
@selectChange="selectChange"
|
||||
>
|
||||
</CustomTransfer> -->
|
||||
<a-transfer
|
||||
:dataSource="resources"
|
||||
:showSearch="true"
|
||||
:listStyle="{
|
||||
width: '300px',
|
||||
height: '450px',
|
||||
}"
|
||||
:titles="[]"
|
||||
:render="(item) => item.title"
|
||||
:targetKeys="targetKeys"
|
||||
@change="handleChange"
|
||||
@selectChange="selectChange"
|
||||
:selectedKeys="selectedKeys"
|
||||
>
|
||||
<span slot="notFoundContent">暂无数据</span>
|
||||
<template slot="children" slot-scope="{ props: { direction, filteredItems } }">
|
||||
<div class="ant-transfer-list-content" v-if="direction === 'right'">
|
||||
<div
|
||||
@dblclick="(e) => changeSingleItem(e, item)"
|
||||
v-for="item in filteredItems"
|
||||
:key="item.key"
|
||||
:style="{ height: '32px' }"
|
||||
>
|
||||
<li
|
||||
:class="
|
||||
`ant-transfer-list-content-item ${
|
||||
selectedKeys.includes(item.key) ? 'ant-transfer-list-content-item-selected' : ''
|
||||
}`
|
||||
"
|
||||
style="padding:0 12px 0 12px;position:relative"
|
||||
@click="setSelectedKeys(item)"
|
||||
>
|
||||
<div :class="`ant-transfer-list-content-item-text`" style="display:inline">
|
||||
{{ item.title }}
|
||||
<div class="ant-transfer-list-icon" @click="(e) => changeSingleItem(e, item)">
|
||||
<a-icon type="left" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="direction === 'left'" class="ant-transfer-list-content">
|
||||
<div
|
||||
@dblclick="(e) => changeSingleItem(e, item)"
|
||||
v-for="item in filteredItems"
|
||||
:key="item.key"
|
||||
:style="{ height: '32px' }"
|
||||
>
|
||||
<li
|
||||
:class="
|
||||
`ant-transfer-list-content-item ${
|
||||
selectedKeys.includes(item.key) ? 'ant-transfer-list-content-item-selected' : ''
|
||||
}`
|
||||
"
|
||||
style="padding:0 12px 0 12px;position:relative"
|
||||
@click="setSelectedKeys(item)"
|
||||
>
|
||||
<div class="ant-transfer-list-content-item-text" style="display:inline">
|
||||
{{ item.title }}
|
||||
<div @click="(e) => changeSingleItem(e, item)" class="ant-transfer-list-icon">
|
||||
<a-icon type="right" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-transfer>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { updateResourceGroup, searchResource, getResourceGroupItems } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceGroupModal',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
editRecord: {},
|
||||
resources: [],
|
||||
targetKeys: [],
|
||||
selectedKeys: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSubmit() {
|
||||
const items = this.targetKeys.map((key) => {
|
||||
return this.resources.filter((item) => item.name === key)[0]['id']
|
||||
})
|
||||
updateResourceGroup(this.editRecord['id'], { items: items.join(',') }).then(() => {
|
||||
this.visible = false
|
||||
this.$message.success('更新成功!')
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
// filterOption(inputValue, option) {
|
||||
// return option.description.indexOf(inputValue) > -1
|
||||
// },
|
||||
// handleChange(targetKeys, direction, moveKeys) {
|
||||
// console.log(JSON.parse(JSON.stringify(targetKeys)))
|
||||
// this.targetKeys = targetKeys
|
||||
// },
|
||||
// handleSearch(dir, value) {
|
||||
// console.log('search:', dir, value)
|
||||
// },
|
||||
async handleEdit(record) {
|
||||
this.editRecord = record
|
||||
this.visible = true
|
||||
this.selectedKeys = []
|
||||
await this.loadChildren(record.id)
|
||||
await this.loadResource()
|
||||
},
|
||||
loadChildren(_id) {
|
||||
getResourceGroupItems(_id).then((res) => {
|
||||
this.targetKeys = res.map((item) => item.name)
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
loadResource() {
|
||||
const params = {
|
||||
app_id: this.editRecord['app_id'],
|
||||
resource_type_id: this.editRecord['resource_type_id'],
|
||||
page_size: 9999,
|
||||
}
|
||||
searchResource(params).then((res) => {
|
||||
this.resources = res['resources'].map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
key: item.name,
|
||||
description: item.name,
|
||||
title: item.name,
|
||||
}
|
||||
})
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
setSelectedKeys(item) {
|
||||
const idx = this.selectedKeys.findIndex((key) => key === item.key)
|
||||
if (idx > -1) {
|
||||
this.selectedKeys.splice(idx, 1)
|
||||
} else {
|
||||
this.selectedKeys.push(item.key)
|
||||
}
|
||||
},
|
||||
changeSingleItem(e, item) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
const idx = this.targetKeys.findIndex((key) => key === item.key)
|
||||
if (idx > -1) {
|
||||
this.targetKeys.splice(idx, 1)
|
||||
} else {
|
||||
this.targetKeys.push(item.key)
|
||||
}
|
||||
},
|
||||
handleChange(targetKeys, direction, moveKeys) {
|
||||
const _selectedKeys = _.cloneDeep(this.selectedKeys)
|
||||
moveKeys.forEach((key) => {
|
||||
const idx = _selectedKeys.findIndex((selected) => selected === key)
|
||||
if (idx > -1) {
|
||||
_selectedKeys.splice(idx, 1)
|
||||
}
|
||||
})
|
||||
this.selectedKeys = _.cloneDeep(_selectedKeys)
|
||||
this.targetKeys = targetKeys
|
||||
},
|
||||
selectChange(sourceSelectedKeys, targetSelectedKeys) {
|
||||
const _selectedKeys = _.cloneDeep(this.selectedKeys)
|
||||
const list = [
|
||||
{ data: sourceSelectedKeys, name: 'source' },
|
||||
{ data: targetSelectedKeys, name: 'target' },
|
||||
]
|
||||
list.forEach((item) => {
|
||||
if (!item.data.__ob__) {
|
||||
if (item.data.length) {
|
||||
item.data.forEach((key) => {
|
||||
const idx = _selectedKeys.findIndex((selected) => selected === key)
|
||||
if (idx > -1) {
|
||||
} else {
|
||||
_selectedKeys.push(key)
|
||||
}
|
||||
})
|
||||
this.selectedKeys = _.cloneDeep(_selectedKeys)
|
||||
} else {
|
||||
let _list = []
|
||||
if (item.name === 'source') {
|
||||
_list = _selectedKeys.filter((key) => this.targetKeys.includes(key))
|
||||
} else {
|
||||
_list = _selectedKeys.filter((key) => !this.targetKeys.includes(key))
|
||||
}
|
||||
this.selectedKeys = _list
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ant-transfer-list-body-customize-wrapper {
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
max-height: calc(100% - 44px);
|
||||
}
|
||||
.ant-transfer-list-content-item {
|
||||
transition: all 0.3s;
|
||||
.ant-transfer-list-icon {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
display: none;
|
||||
&:hover {
|
||||
color: #1f78d1;
|
||||
}
|
||||
}
|
||||
&:hover .ant-transfer-list-icon {
|
||||
display: inline;
|
||||
background-color: #c0eaff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
.ant-transfer-list-content-item-selected {
|
||||
background-color: #f0faff;
|
||||
}
|
||||
</style>
|
||||
326
cmdb-ui/src/modules/acl/views/module/resourceHistoryTable.vue
Normal file
326
cmdb-ui/src/modules/acl/views/module/resourceHistoryTable.vue
Normal file
@@ -0,0 +1,326 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="resourceTableAttrList"
|
||||
:hasSwitch="true"
|
||||
switchValue="组"
|
||||
@onSwitchChange="onSwitchChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
@loadMoreData="loadMoreResources"
|
||||
@fetchData="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
size="small"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
resizable
|
||||
:max-height="`${windowHeight - 331}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="link_id" title="资源名">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.description }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50, 100, 200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { searchResourceHistory } from '@/modules/acl/api/history'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
props: {
|
||||
allResources: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allUsers: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allRolesMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
allUsersMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
allResourcesMap: {
|
||||
type: Map,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
tableData: [],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
resourceTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3',
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: this.allUsers,
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [{ 新建: 'create' }, { 修改: 'update' }, { 删除: 'delete' }],
|
||||
},
|
||||
{
|
||||
alias: '资源名',
|
||||
is_choice: true,
|
||||
name: 'link_id',
|
||||
value_type: '2',
|
||||
choice_value: this.allResources,
|
||||
},
|
||||
],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['update', 'orange'],
|
||||
['delete', 'red'],
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: 'resource',
|
||||
start: '',
|
||||
end: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
allResources: {
|
||||
deep: true,
|
||||
handler(val) {
|
||||
this.resourceTableAttrList[3].choice_value = val
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data } = await searchResourceHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach((item) => {
|
||||
item.originResource_ids = item?.extra?.resource_ids?.origin.map((subItem) =>
|
||||
this.allResourcesMap.get(Number(subItem))
|
||||
)
|
||||
item.currentResource_ids = item?.extra?.resource_ids?.current.map((subItem) =>
|
||||
this.allResourcesMap.get(Number(subItem))
|
||||
)
|
||||
this.handleChangeDescription(item, item.operate_type)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
loadMoreResources(name, value) {
|
||||
if (name === 'link_id') {
|
||||
this.$emit('loadMoreResources', value)
|
||||
}
|
||||
},
|
||||
resourceClear() {
|
||||
this.$emit('resourceClear')
|
||||
},
|
||||
fetchResources(value) {
|
||||
if (value === '') {
|
||||
this.$emit('reloadResources')
|
||||
return
|
||||
}
|
||||
this.$emit('fetchResources', value)
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id, scope: this.checked ? 'resource_group' : 'resource' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: this.checked ? 'resource_group' : 'resource',
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onSwitchChange(checked) {
|
||||
this.checked = checked
|
||||
this.queryParams.scope = checked ? 'resource_group' : 'resource'
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'q' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
item.description = `新建资源:${item.current.name}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.description = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
item.description += ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
} else {
|
||||
item.description += ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
}
|
||||
}
|
||||
}
|
||||
const originResource_ids = item.originResource_ids
|
||||
const currentResource_ids = item.currentResource_ids
|
||||
if (!_.isEqual(originResource_ids, currentResource_ids)) {
|
||||
if (originResource_ids.length === 0) {
|
||||
const str = ` 【 resource_ids : 新增 ${currentResource_ids} 】 `
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 resource_ids : 由 ${originResource_ids} 改为 ${currentResource_ids} 】 `
|
||||
item.description += str
|
||||
}
|
||||
}
|
||||
if (!item.description) item.description = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
item.description = `删除资源:${item.origin.name}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
272
cmdb-ui/src/modules/acl/views/module/resourcePermForm.vue
Normal file
272
cmdb-ui/src/modules/acl/views/module/resourcePermForm.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
width="80%"
|
||||
placement="left"
|
||||
@close="() => (drawerVisible = false)"
|
||||
:hasFooter="false"
|
||||
>
|
||||
<vxe-table :max-height="`${windowHeight - 150}px`" :data="resPerms" ref="rTable">
|
||||
<vxe-column
|
||||
field="name"
|
||||
title="角色名"
|
||||
width="20%"
|
||||
:filters="[{ data: '' }]"
|
||||
:filter-method="filterNameMethod"
|
||||
:filter-recover-method="filterNameRecoverMethod"
|
||||
>
|
||||
<template #filter="{ $panel, column }">
|
||||
<template v-for="(option, index) in column.filters">
|
||||
<input
|
||||
class="my-input"
|
||||
type="type"
|
||||
:key="index"
|
||||
v-model="option.data"
|
||||
@input="$panel.changeOption($event, !!option.data, option)"
|
||||
@keyup.enter="$panel.confirmFilter()"
|
||||
placeholder="按回车确认筛选"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="users" title="下属用户" width="35%">
|
||||
<template #default="{row}">
|
||||
<a-tag color="green" v-for="user in row.users" :key="user.nickname">
|
||||
{{ user.nickname }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="perms" title="权限列表" width="35%">
|
||||
<template #default="{row}">
|
||||
<a-tag
|
||||
closable
|
||||
color="cyan"
|
||||
v-for="perm in row.perms"
|
||||
:key="perm.name"
|
||||
@close="deletePerm(perm.rid, perm.name)"
|
||||
>
|
||||
{{ perm.name }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operate" title="批量操作">
|
||||
<template #default="{row}">
|
||||
<a-button size="small" type="danger" @click="handleClearAll(row)">
|
||||
清空
|
||||
</a-button>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<!-- <a-table
|
||||
:columns="columns"
|
||||
:dataSource="resPerms"
|
||||
:rowKey="record => record.name"
|
||||
:pagination="{ showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条记录` }"
|
||||
showPagination="auto"
|
||||
ref="rTable"
|
||||
size="middle"
|
||||
>
|
||||
<div slot="perms" slot-scope="text">
|
||||
<a-tag closable color="cyan" v-for="perm in text" :key="perm.name" @close="deletePerm(perm.rid, perm.name)">
|
||||
{{ perm.name }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div slot="users" slot-scope="text">
|
||||
<a-tag color="green" v-for="user in text" :key="user.nickname">
|
||||
{{ user.nickname }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div slot="operate" slot-scope="text">
|
||||
<a-button size="small" type="danger" @click="handleClearAll(text)">
|
||||
清空
|
||||
</a-button>
|
||||
</div>
|
||||
</a-table> -->
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import {
|
||||
getResourceTypePerms,
|
||||
getResourcePerms,
|
||||
deleteRoleResourcePerm,
|
||||
getResourceGroupPerms,
|
||||
deleteRoleResourceGroupPerm,
|
||||
deleteRoleResourceGroupPerm2,
|
||||
} from '@/modules/acl/api/permission'
|
||||
|
||||
export default {
|
||||
name: 'ResourceForm',
|
||||
data() {
|
||||
return {
|
||||
isGroup: false,
|
||||
drawerTitle: '权限列表',
|
||||
drawerVisible: false,
|
||||
record: null,
|
||||
allPerms: [],
|
||||
resPerms: [],
|
||||
roleID: null,
|
||||
// columns: [
|
||||
// {
|
||||
// title: '角色名',
|
||||
// dataIndex: 'name',
|
||||
// sorter: false,
|
||||
// width: 50,
|
||||
// },
|
||||
// {
|
||||
// title: '下属用户',
|
||||
// dataIndex: 'users',
|
||||
// sorter: false,
|
||||
// width: 150,
|
||||
// scopedSlots: { customRender: 'users' },
|
||||
// },
|
||||
// {
|
||||
// title: '权限列表',
|
||||
// dataIndex: 'perms',
|
||||
// sorter: false,
|
||||
// width: 150,
|
||||
// scopedSlots: { customRender: 'perms' },
|
||||
// },
|
||||
// {
|
||||
// title: '批量操作',
|
||||
// sorter: false,
|
||||
// width: 50,
|
||||
// scopedSlots: { customRender: 'operate' },
|
||||
// },
|
||||
// ],
|
||||
childrenDrawer: false,
|
||||
allRoles: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
methods: {
|
||||
handlePerm(record, isGroup) {
|
||||
this.isGroup = isGroup
|
||||
this.drawerVisible = true
|
||||
this.record = record
|
||||
this.getResPerms(record.id)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.getAllPerms(record.resource_type_id)
|
||||
})
|
||||
},
|
||||
handleClearAll(text) {
|
||||
if (this.isGroup) {
|
||||
deleteRoleResourceGroupPerm2(text.perms[0].rid, this.record.id, {
|
||||
perms: [],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}).then((res) => {
|
||||
this.$message.success('删除成功')
|
||||
this.$nextTick(() => {
|
||||
this.getResPerms(this.record.id)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
deleteRoleResourcePerm(text.perms[0].rid, this.record.id, {
|
||||
perms: [],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}).then((res) => {
|
||||
this.$message.success('删除成功')
|
||||
// for (let i = 0; i < this.resPerms.length; i++) {
|
||||
// if (this.resPerms[i].name === text.name) {
|
||||
// this.resPerms[i].perms = []
|
||||
// }
|
||||
// }
|
||||
this.$nextTick(() => {
|
||||
this.getResPerms(this.record.id)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
getResPerms(resId) {
|
||||
if (!this.isGroup) {
|
||||
getResourcePerms(resId).then((res) => {
|
||||
const perms = []
|
||||
for (const key in res) {
|
||||
perms.push({ name: key, perms: res[key].perms, users: res[key].users })
|
||||
}
|
||||
this.resPerms = perms
|
||||
})
|
||||
} else {
|
||||
getResourceGroupPerms(resId).then((res) => {
|
||||
const perms = []
|
||||
for (const key in res) {
|
||||
perms.push({ name: key, perms: res[key].perms, users: res[key].users })
|
||||
}
|
||||
this.resPerms = perms
|
||||
})
|
||||
}
|
||||
},
|
||||
getAllPerms(resTypeId) {
|
||||
getResourceTypePerms(resTypeId).then((res) => {
|
||||
this.allPerms = res
|
||||
})
|
||||
},
|
||||
deletePerm(roleID, permName) {
|
||||
if (!this.isGroup) {
|
||||
deleteRoleResourcePerm(roleID, this.record.id, {
|
||||
perms: [permName],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
} else {
|
||||
deleteRoleResourceGroupPerm(roleID, this.record.id, {
|
||||
perms: [permName],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
}
|
||||
},
|
||||
handleCancel(e) {
|
||||
this.drawerVisible = false
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
filterNameMethod({ option, row }) {
|
||||
return row.name.toLowerCase().includes(option.data.toLowerCase())
|
||||
},
|
||||
filterNameRecoverMethod({ option }) {
|
||||
option.data = ''
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
216
cmdb-ui/src/modules/acl/views/module/resourcePermManageForm.vue
Normal file
216
cmdb-ui/src/modules/acl/views/module/resourcePermManageForm.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<CustomDrawer :title="title" width="500px" :closable="false" :visible="visible" @close="closeForm">
|
||||
<a-form :form="form">
|
||||
<a-form-item>
|
||||
<div slot="label" style="display: inline-block">
|
||||
<span>角色列表</span>
|
||||
<a-divider type="vertical" />
|
||||
<a-switch
|
||||
style="display: inline-block"
|
||||
checked-children="用户"
|
||||
un-checked-children="虚拟"
|
||||
@change="handleRoleTypeChange"
|
||||
/>
|
||||
</div>
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
size="small"
|
||||
v-decorator="['roleIdList', { rules: [{ required: true, message: '请选择角色名称' }] }]"
|
||||
multiple
|
||||
filterable
|
||||
placeholder="请选择角色名称,可多选!"
|
||||
>
|
||||
<el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="权限列表">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
size="small"
|
||||
name="permName"
|
||||
v-decorator="['permName', { rules: [{ required: true, message: '请选择权限' }] }]"
|
||||
multiple
|
||||
placeholder="请选择权限,可多选!"
|
||||
>
|
||||
<el-option v-for="perm in allPerms" :key="perm.name" :value="perm.name" :label="perm.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="closeForm">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="loading">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { Select, Option } from 'element-ui'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
import {
|
||||
getResourceTypePerms,
|
||||
setRoleResourcePerm,
|
||||
setRoleResourceGroupPerm,
|
||||
setBatchRoleResourcePerm,
|
||||
setBatchRoleResourceGroupPerm,
|
||||
setBatchRoleResourceRevoke,
|
||||
setBatchRoleResourceGroupRevoke,
|
||||
} from '@/modules/acl/api/permission'
|
||||
|
||||
export default {
|
||||
name: 'ResourcePermManageForm',
|
||||
components: { ElSelect: Select, ElOption: Option },
|
||||
data() {
|
||||
return {
|
||||
isGroup: false,
|
||||
allRoles: [],
|
||||
allPerms: [],
|
||||
visible: false,
|
||||
instance: {} || [], // 当前对象or批量授权的数组
|
||||
type: 'grant', // grant or revoke
|
||||
title: '',
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
groupTypeMessage: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
computed: {},
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadRoles(1)
|
||||
},
|
||||
methods: {
|
||||
handleRoleTypeChange(target) {
|
||||
if (!target) {
|
||||
this.loadRoles(1)
|
||||
} else {
|
||||
this.loadRoles(0)
|
||||
}
|
||||
},
|
||||
loadRoles(isUserRole) {
|
||||
searchRole({ page_size: 9999, app_id: this.$route.name.split('_')[0], user_role: isUserRole }).then((res) => {
|
||||
this.allRoles = res.roles
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
loadPerm(resourceTypeId) {
|
||||
getResourceTypePerms(resourceTypeId).then((res) => {
|
||||
this.allPerms = res
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
closeForm() {
|
||||
this.visible = false
|
||||
this.form.resetFields()
|
||||
this.$emit('close')
|
||||
},
|
||||
editPerm(record, isGroup, type = 'grant') {
|
||||
this.isGroup = isGroup
|
||||
this.visible = true
|
||||
this.instance = record
|
||||
this.type = type
|
||||
if (Array.isArray(record)) {
|
||||
this.loadPerm(record[0]['resource_type_id'])
|
||||
this.title = `${type === 'grant' ? '批量授权' : '批量权限回收'}`
|
||||
} else {
|
||||
this.title = `添加授权:${record.name}`
|
||||
this.loadPerm(record['resource_type_id'])
|
||||
}
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
values.roleIdList.forEach((roleId) => {
|
||||
const params = { perms: values.permName, app_id: this.$route.name.split('_')[0] }
|
||||
this.loading = true
|
||||
if (!this.isGroup) {
|
||||
if (Array.isArray(this.instance)) {
|
||||
// const promises = this.instance.map(item => {
|
||||
// return setRoleResourcePerm(roleId, item.id, params)
|
||||
// })
|
||||
// Promise.all(promises).then(() => {
|
||||
// this.$message.success('添加授权成功')
|
||||
// })
|
||||
if (this.type === 'grant') {
|
||||
setBatchRoleResourcePerm(roleId, { ...params, resource_ids: this.instance.map((a) => a.id) })
|
||||
.then((res) => {
|
||||
this.$message.success('添加授权成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
setBatchRoleResourceRevoke(roleId, { ...params, resource_ids: this.instance.map((a) => a.id) })
|
||||
.then((res) => {
|
||||
this.$message.success('批量权限回收成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
} else {
|
||||
setRoleResourcePerm(roleId, this.instance.id, params)
|
||||
.then((res) => {
|
||||
this.$message.success('添加授权成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(this.instance)) {
|
||||
// const promises = this.instance.map(item => {
|
||||
// return setRoleResourceGroupPerm(roleId, item.id, params)
|
||||
// })
|
||||
// Promise.all(promises).then(() => {
|
||||
// this.$message.success('添加授权成功')
|
||||
// })
|
||||
if (this.type === 'grant') {
|
||||
setBatchRoleResourceGroupPerm(roleId, { ...params, group_ids: this.instance.map((a) => a.id) })
|
||||
.then((res) => {
|
||||
this.$message.success('添加授权成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
setBatchRoleResourceGroupRevoke(roleId, {
|
||||
...params,
|
||||
group_ids: this.instance.map((a) => a.id),
|
||||
})
|
||||
.then((res) => {
|
||||
this.$message.success('批量权限回收成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
} else {
|
||||
setRoleResourceGroupPerm(roleId, this.instance.id, params)
|
||||
.then((res) => {
|
||||
this.$message.success('添加授权成功')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
160
cmdb-ui/src/modules/acl/views/module/resourceTypeForm.vue
Normal file
160
cmdb-ui/src/modules/acl/views/module/resourceTypeForm.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:closable="false"
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
@close="onClose"
|
||||
placement="right"
|
||||
width="500px"
|
||||
>
|
||||
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="类型名">
|
||||
<a-input
|
||||
name="name"
|
||||
placeholder="类型名称"
|
||||
v-decorator="['name', { rules: [{ required: true, message: '请输入类型名' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="描述">
|
||||
<a-textarea
|
||||
placeholder="请输入描述信息..."
|
||||
name="description"
|
||||
:rows="4"
|
||||
v-decorator="['description', { rules: [] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="权限">
|
||||
<a-select mode="tags" v-model="perms" style="width: 100%" placeholder="请输入权限名..."> </a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addResourceType, updateResourceTypeById } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceForm',
|
||||
data() {
|
||||
return {
|
||||
drawerTitle: '新增资源类型',
|
||||
drawerVisible: false,
|
||||
perms: [],
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
computed: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
handleCreate() {
|
||||
this.drawerVisible = true
|
||||
},
|
||||
onClose() {
|
||||
this.form.resetFields()
|
||||
this.perms = []
|
||||
this.drawerVisible = false
|
||||
},
|
||||
onChange(e) {
|
||||
console.log(`checked = ${e}`)
|
||||
},
|
||||
|
||||
handleEdit(record) {
|
||||
this.drawerVisible = true
|
||||
console.log(record)
|
||||
this.perms = record.perms
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
description: record.description,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
|
||||
values.app_id = this.$route.name.split('_')[0]
|
||||
values.perms = this.perms
|
||||
if (values.id) {
|
||||
this.updateResourceType(values.id, values)
|
||||
} else {
|
||||
this.createResourceType(values)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
updateResourceType(id, data) {
|
||||
updateResourceTypeById(id, data).then(res => {
|
||||
this.$message.success(`更新成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
createResourceType(data) {
|
||||
addResourceType(data).then(res => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
// requestFailed (err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// }
|
||||
},
|
||||
watch: {},
|
||||
props: {
|
||||
handleOk: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="resourceTableAttrList"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
size="small"
|
||||
:data="tableData"
|
||||
resizable
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - 331}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="link_id" title="资源类型名">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { searchResourceHistory } from '@/modules/acl/api/history'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
props: {
|
||||
allResourceTypes: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allUsers: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRolesMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allUsersMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allResourceTypesMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
tableData: [],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
resourceTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: this.allUsers
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
]
|
||||
},
|
||||
{
|
||||
alias: '资源类型',
|
||||
is_choice: true,
|
||||
name: 'link_id',
|
||||
value_type: '2',
|
||||
choice_value: this.allResourceTypes
|
||||
}
|
||||
],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除']
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['update', 'orange'],
|
||||
['delete', 'red']
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: 'resource_type',
|
||||
start: '',
|
||||
end: ''
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
await this.getTable(this.queryParams)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data } = await searchResourceHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach(item => {
|
||||
this.handleChangeDescription(item, item.operate_type)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id, scope: 'resource_type' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: 'resource_type',
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'q' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
item.changeDescription = `新增资源类型:${item.current.name}\n描述:${item.current.description}\n权限:${item.extra.permission_ids.current}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.changeDescription = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null || oldVal === '') {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 \n`
|
||||
item.changeDescription += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 \n`
|
||||
item.changeDescription += str
|
||||
}
|
||||
}
|
||||
}
|
||||
const currentPerms = item.extra.permission_ids.current
|
||||
const originPerms = item.extra.permission_ids.origin
|
||||
if (!_.isEqual(currentPerms, originPerms)) item.changeDescription += ` 【 permission_ids : 由 ${originPerms} 改为 ${currentPerms} 】 `
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
item.changeDescription = `删除资源类型:${item.origin.name}\n描述:${item.origin.description}\n权限:${item.extra.permission_ids.origin}`
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
292
cmdb-ui/src/modules/acl/views/module/resourceUserForm.vue
Normal file
292
cmdb-ui/src/modules/acl/views/module/resourceUserForm.vue
Normal file
@@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
width="800px"
|
||||
placement="left"
|
||||
title="资源列表"
|
||||
@close="handleCancel"
|
||||
:visible="visible"
|
||||
:hasFooter="false"
|
||||
>
|
||||
<a-form-item label="资源类型" :label-col="{ span: 2 }" :wrapper-col="{ span: 14 }">
|
||||
<a-select v-model="typeSelected" style="width:100%" @change="refresh">
|
||||
<a-select-option v-for="type in resourceTypes" :value="type.id" :key="type.id">{{ type.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<vxe-table :max-height="`${windowHeight - 180}px`" :data="records" ref="rTable">
|
||||
<vxe-column
|
||||
field="name"
|
||||
title="资源名"
|
||||
width="30%"
|
||||
:filters="[{ data: '' }]"
|
||||
:filter-method="filterNameMethod"
|
||||
:filter-recover-method="filterNameRecoverMethod"
|
||||
>
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-tooltip title="复制资源名">
|
||||
<a-icon @click="copyResourceName" class="resource-user-form-copy" theme="filled" type="copy" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #filter="{ $panel, column }">
|
||||
<template v-for="(option, index) in column.filters">
|
||||
<input
|
||||
class="my-input"
|
||||
type="type"
|
||||
:key="index"
|
||||
v-model="option.data"
|
||||
@input="$panel.changeOption($event, !!option.data, option)"
|
||||
@keyup.enter="$panel.confirmFilter()"
|
||||
placeholder="按回车确认筛选"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="permissions" title="权限列表" width="70%">
|
||||
<template #default="{row}">
|
||||
<a-tag color="cyan" v-for="(r, index) in row.permissions" :key="index">{{ r }}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<!-- <a-table
|
||||
:columns="columns"
|
||||
:dataSource="records"
|
||||
:rowKey="record => record.id"
|
||||
:pagination="false"
|
||||
ref="rTable"
|
||||
size="middle"
|
||||
:scroll="{ y: 300 }"
|
||||
> -->
|
||||
<!-- <div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown">
|
||||
<a-input
|
||||
v-ant-ref="c => searchInput = c"
|
||||
:placeholder="` ${column.title}`"
|
||||
:value="selectedKeys[0]"
|
||||
@change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
|
||||
@pressEnter="() => handleSearch(selectedKeys, confirm, column)"
|
||||
style="width: 188px; margin-bottom: 8px; display: block;"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="() => handleSearch(selectedKeys, confirm, column)"
|
||||
icon="search"
|
||||
size="small"
|
||||
style="width: 90px; margin-right: 8px"
|
||||
>搜索</a-button>
|
||||
<a-button
|
||||
@click="() => handleReset(clearFilters, column)"
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
>重置</a-button>
|
||||
</div>
|
||||
<a-icon slot="filterIcon" slot-scope="filtered" type="search" :style="{ color: filtered ? '#108ee9' : undefined }" />
|
||||
|
||||
<template slot="nameSearchRender" slot-scope="text">
|
||||
<span v-if="columnSearchText.name">
|
||||
<template v-for="(fragment, i) in text.toString().split(new RegExp(`(?<=${columnSearchText.name})|(?=${columnSearchText.name})`, 'i'))">
|
||||
<mark v-if="fragment.toLowerCase() === columnSearchText.name.toLowerCase()" :key="i" class="highlight">{{ fragment }}</mark>
|
||||
<template v-else>{{ fragment }}</template>
|
||||
</template>
|
||||
</span>
|
||||
<template v-else>{{ text }}</template>
|
||||
</template> -->
|
||||
<!-- <template slot="permissions" slot-scope="record">
|
||||
<a-tag color="cyan" v-for="(r, index) in record" :key="index">{{ r }}</a-tag>
|
||||
</template>
|
||||
</a-table> -->
|
||||
<!-- <div
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
borderTop: '1px solid #e9e9e9',
|
||||
padding: '10px 16px',
|
||||
background: '#fff',
|
||||
textAlign: 'right',
|
||||
}"
|
||||
>
|
||||
<a-button :style="{marginRight: '8px'}" @click="handleCancel">
|
||||
取消
|
||||
</a-button>
|
||||
<a-button @click="handleOk" type="primary">确定</a-button>
|
||||
</div> -->
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { searchPermResourceByRoleId } from '@/modules/acl/api/permission'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceUserForm',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
rid: 0,
|
||||
records: [],
|
||||
resourceTypes: [],
|
||||
typeSelected: null,
|
||||
columnSearchText: {
|
||||
name: '',
|
||||
},
|
||||
filterName: '',
|
||||
// columns: [
|
||||
// {
|
||||
// title: '资源名',
|
||||
// field: 'name',
|
||||
// sorter: false,
|
||||
// width: '30%',
|
||||
// // scopedSlots: {
|
||||
// // customRender: 'nameSearchRender',
|
||||
// // filterDropdown: 'filterDropdown',
|
||||
// // filterIcon: 'filterIcon'
|
||||
// // },
|
||||
// // onFilter: (value, record) => record.name && record.name.toLowerCase().includes(value.toLowerCase()),
|
||||
// // onFilterDropdownVisibleChange: (visible) => {
|
||||
// // if (visible) {
|
||||
// // setTimeout(() => {
|
||||
// // this.searchInput.focus()
|
||||
// // }, 0)
|
||||
// // }
|
||||
// // }
|
||||
// },
|
||||
// {
|
||||
// title: '权限列表',
|
||||
// field: 'permissions',
|
||||
// width: '70%',
|
||||
// slots: { default: 'permissions_default' },
|
||||
// },
|
||||
// ],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: state => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.loadResourceTypes()
|
||||
},
|
||||
methods: {
|
||||
loadUserResource(record) {
|
||||
this.visible = true
|
||||
this.rid = record.id
|
||||
this.refresh()
|
||||
},
|
||||
// handleSearch(selectedKeys, confirm, column) {
|
||||
// confirm()
|
||||
// this.columnSearchText[column.dataIndex] = selectedKeys[0]
|
||||
// },
|
||||
// handleReset(clearFilters, column) {
|
||||
// clearFilters()
|
||||
// this.columnSearchText[column.dataIndex] = ''
|
||||
// },
|
||||
loadResourceTypes() {
|
||||
this.resourceTypes = []
|
||||
const appId = this.$route.name.split('_')[0]
|
||||
searchResourceType({ app_id: appId }).then(res => {
|
||||
this.resourceTypes = res.groups
|
||||
if (res.groups && res.groups.length > 0) {
|
||||
this.typeSelected = res.groups[0].id
|
||||
console.log(this.typeSelected)
|
||||
} else {
|
||||
this.typeSelected = null
|
||||
}
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
handleOk() {
|
||||
this.visible = false
|
||||
},
|
||||
refresh() {
|
||||
if (this.typeSelected) {
|
||||
searchPermResourceByRoleId(this.rid, {
|
||||
resource_type_id: this.typeSelected,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
}).then(res => {
|
||||
this.records = res.resources
|
||||
})
|
||||
// .catch(err=>this.$httpError(err))
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
filterNameMethod({ option, row }) {
|
||||
this.filterName = option.data
|
||||
return row.name.toLowerCase().includes(option.data.toLowerCase())
|
||||
},
|
||||
filterNameRecoverMethod({ option }) {
|
||||
this.filterName = ''
|
||||
option.data = ''
|
||||
},
|
||||
copyResourceName() {
|
||||
const val = this.records
|
||||
.filter(item => item.name.toLowerCase().includes(this.filterName.toLowerCase()))
|
||||
.map(item => item.name)
|
||||
.join('\n')
|
||||
|
||||
this.copy(val, () => {
|
||||
this.$message.success('复制成功')
|
||||
})
|
||||
},
|
||||
copy(value, cb) {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.readOnly = 'readonly'
|
||||
textarea.value = value
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
textarea.setSelectionRange(0, textarea.value.length)
|
||||
document.execCommand('Copy')
|
||||
document.body.removeChild(textarea)
|
||||
if (cb && Object.prototype.toString.call(cb) === '[object Function]') {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function(newName, oldName) {
|
||||
this.resourceTypes = []
|
||||
this.loadResourceTypes()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.custom-filter-dropdown {
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: rgb(255, 192, 105);
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.resource-user-form-copy {
|
||||
color: #c0c4cc;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
225
cmdb-ui/src/modules/acl/views/module/roleForm.vue
Normal file
225
cmdb-ui/src/modules/acl/views/module/roleForm.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:closable="false"
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
@close="onClose"
|
||||
placement="right"
|
||||
width="500px"
|
||||
>
|
||||
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }" @submit="handleSubmit">
|
||||
<a-form-item label="角色名">
|
||||
<a-input
|
||||
name="name"
|
||||
placeholder="角色名"
|
||||
v-decorator="['name', { rules: [{ required: true, message: '请输入角色名' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="$route.name.split('_')[0] !== 'acl'" label="密码">
|
||||
<a-input name="password" placeholder="密码" v-decorator="['password', { rules: [{ required: false }] }]" />
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="继承自">
|
||||
<a-select
|
||||
showSearch
|
||||
v-model="selectedParents"
|
||||
:filterOption="false"
|
||||
placeholder="可选择继承角色"
|
||||
mode="multiple"
|
||||
>
|
||||
<a-select-option v-for="role in scrollData" :key="role.id">{{ role.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item> -->
|
||||
<a-form-item label="继承自">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
size="small"
|
||||
v-model="selectedParents"
|
||||
multiple
|
||||
filterable
|
||||
placeholder="可选择继承角色"
|
||||
>
|
||||
<el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否应用管理员">
|
||||
<a-switch
|
||||
@change="onChange"
|
||||
name="is_app_admin"
|
||||
v-decorator="['is_app_admin', { rules: [], valuePropName: 'checked' }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Select, Option } from 'element-ui'
|
||||
import { addRole, updateRoleById, delParentRole, addBatchParentRole } from '@/modules/acl/api/role'
|
||||
|
||||
export default {
|
||||
name: 'RoleForm',
|
||||
components: {
|
||||
ElSelect: Select,
|
||||
ElOption: Option,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
drawerTitle: '',
|
||||
current_id: 0,
|
||||
drawerVisible: false,
|
||||
selectedParents: [],
|
||||
oldParents: [],
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
computed: {},
|
||||
mounted() {
|
||||
console.log(this.$route)
|
||||
},
|
||||
methods: {
|
||||
// filterOption(input, option) {
|
||||
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
// },
|
||||
handleCreate() {
|
||||
this.drawerTitle = '新增角色'
|
||||
this.drawerVisible = true
|
||||
},
|
||||
onClose() {
|
||||
this.form.resetFields()
|
||||
this.selectedParents = []
|
||||
this.oldParents = []
|
||||
this.drawerVisible = false
|
||||
},
|
||||
onChange(e) {
|
||||
console.log(`checked = ${e}`)
|
||||
},
|
||||
|
||||
handleEdit(record) {
|
||||
this.drawerTitle = `编辑:${record.name}`
|
||||
this.drawerVisible = true
|
||||
this.current_id = record.id
|
||||
const _parents = this.id2parents[record.id]
|
||||
if (_parents) {
|
||||
_parents.forEach((item) => {
|
||||
this.selectedParents.push(item.id)
|
||||
this.oldParents.push(item.id)
|
||||
})
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
is_app_admin: record.is_app_admin,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
values.app_id = this.$route.name.split('_')[0]
|
||||
if (values.id) {
|
||||
this.updateRole(values.id, values)
|
||||
} else {
|
||||
this.createRole(values)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
updateRole(id, data) {
|
||||
this.updateParents(id)
|
||||
updateRoleById(id, { ...data, app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.$message.success(`更新成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
createRole(data) {
|
||||
addRole({ ...data, app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.updateParents(res.id)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
updateParents(id) {
|
||||
this.oldParents.forEach((item) => {
|
||||
if (!this.selectedParents.includes(item)) {
|
||||
delParentRole(id, item, { app_id: this.$route.name.split('_')[0] })
|
||||
// .catch(err => this.requestFailed(err))
|
||||
}
|
||||
})
|
||||
this.selectedParents.forEach((item) => {
|
||||
if (!this.oldParents.includes(item)) {
|
||||
addBatchParentRole(item, {
|
||||
child_ids: [id],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
})
|
||||
// addParentRole(id, item, { app_id: this.$route.name.split('_')[0] })
|
||||
// .catch(err => this.requestFailed(err))
|
||||
}
|
||||
})
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// console.log(err)
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
},
|
||||
watch: {},
|
||||
props: {
|
||||
handleOk: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
id2parents: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
314
cmdb-ui/src/modules/acl/views/module/roleHistoryTable.vue
Normal file
314
cmdb-ui/src/modules/acl/views/module/roleHistoryTable.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="roleTableAttrList"
|
||||
:hasSwitch="true"
|
||||
switchValue="角色关系"
|
||||
@onSwitchChange="onSwitchChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
:data="tableData"
|
||||
size="small"
|
||||
:loading="loading"
|
||||
resizable
|
||||
:max-height="`${windowHeight - 331}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" title="操作员" width="130px"></vxe-column>
|
||||
<vxe-column field="operate_type" title="操作" width="112px">
|
||||
<template #default="{ row }">
|
||||
<template>
|
||||
<a-tag :color="handleTagColor(row.operate_type)">{{ operateTypeMap.get(row.operate_type) }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :title="checked ? '角色' : '角色'">
|
||||
<template #default="{ row }">
|
||||
<template v-if="!checked">
|
||||
<a-tag color="blue">{{ row.current.name || row.origin.name }}</a-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag v-for="(id,index) in row.extra.child_ids" :key="'child_ids_' + id + index" color="blue">{{ id }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :title="checked ? '继承自' : '管理员'" :width="checked ? '350px' : '80px'">
|
||||
<template #default="{ row }">
|
||||
<template v-if="!checked">
|
||||
<a-icon type="check" v-if="row.current.is_app_admin"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag v-for="(id,index) in row.extra.parent_ids" :key="'parent_ids_' + id + index" color="cyan">{{ id }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述" v-if="!checked">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.description }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" title="来源" width="100px"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { searchRoleHistory } from '@/modules/acl/api/history'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
props: {
|
||||
allUsers: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRolesMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allUsersMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
tableData: [],
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['delete', '删除'],
|
||||
['update', '修改'],
|
||||
['role_relation_add', '添加角色关系'],
|
||||
['role_relation_delete', '删除角色关系'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['delete', 'red'],
|
||||
['update', 'orange'],
|
||||
['role_relation_add', 'green'],
|
||||
['role_relation_delete', 'red']
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: 'role',
|
||||
start: '',
|
||||
end: ''
|
||||
},
|
||||
roleTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: this.allUsers
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
{ '添加角色关系': 'role_relation_add' },
|
||||
{ '删除角色关系': 'role_relation_delete' },
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
await this.getTable(this.queryParams)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data, id2roles, id2perms, id2resources } = await searchRoleHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach(item => {
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
if (item.operate_type === 'role_relation_add' || item.operate_type === 'role_relation_delete') {
|
||||
item.extra.child_ids.forEach((subItem, index) => { item.extra.child_ids[index] = id2roles[subItem].name })
|
||||
item.extra.parent_ids.forEach((subItem, index) => { item.extra.parent_ids[index] = id2roles[subItem].name })
|
||||
} else {
|
||||
this.handleChangeDescription(item, item.operate_type, id2roles, id2perms, id2resources)
|
||||
}
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
onSwitchChange(checked) {
|
||||
this.checked = checked
|
||||
this.queryParams.scope = checked ? 'role_relation' : 'role'
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, app_id: this.app_id, scope: this.checked ? 'role_relation' : 'role' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
scope: this.checked ? 'role_relation' : 'role'
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// 处理查询参数
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'q' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
// 处理tag颜色
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type, id2roles, id2perms, id2resources) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
item.description = `新建角色:${item.current.name}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.description = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.description += ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item.description) item.description = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const { extra: { child_ids, parent_ids, role_permissions } } = item
|
||||
child_ids.forEach((subItem, index) => { child_ids[index] = id2roles[subItem].name })
|
||||
parent_ids.forEach((subItem, index) => { parent_ids[index] = id2roles[subItem].name })
|
||||
|
||||
const resourceMap = new Map()
|
||||
const permsArr = []
|
||||
role_permissions.forEach(subItem => {
|
||||
const resource_id = subItem.resource_id
|
||||
const perm_id = subItem.perm_id
|
||||
if (resourceMap.has(resource_id)) {
|
||||
let resource_perms = resourceMap.get(resource_id)
|
||||
resource_perms += `,${id2perms[perm_id].name}`
|
||||
resourceMap.set(resource_id, resource_perms)
|
||||
} else {
|
||||
resourceMap.set(resource_id, String(id2perms[perm_id].name))
|
||||
}
|
||||
})
|
||||
resourceMap.forEach((value, key) => {
|
||||
permsArr.push(`${id2resources[key].name}:${value}`)
|
||||
})
|
||||
item.description = `继承者:${child_ids}\n继承自:${parent_ids}\n涉及资源及权限:\n${permsArr.join(`\n`)}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
236
cmdb-ui/src/modules/acl/views/module/searchForm.vue
Normal file
236
cmdb-ui/src/modules/acl/views/module/searchForm.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-form :colon="false">
|
||||
<a-row :gutter="24">
|
||||
<a-col
|
||||
:sm="24"
|
||||
:md="12"
|
||||
:lg="12"
|
||||
:xl="6"
|
||||
v-for="attr in attrList.slice(0, 4)"
|
||||
:key="attr.name">
|
||||
<a-form-item
|
||||
:label="attr.alias || attr.name"
|
||||
:labelCol="{ span: 4 }"
|
||||
:wrapperCol="{ span: 20 }"
|
||||
labelAlign="right"
|
||||
>
|
||||
<a-select
|
||||
@popupScroll="loadMoreData(attr.name, $event)"
|
||||
@search="(value) => fetchData(value, attr.name)"
|
||||
v-model="queryParams[attr.name]"
|
||||
placeholder="请选择"
|
||||
v-if="attr.is_choice"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
v-for="(choice, index) in attr.choice_value"
|
||||
:key="'Search_' + attr.name + index"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
v-model="date"
|
||||
@change="onChange"
|
||||
:style="{ width: '100%' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
:show-time="{
|
||||
hideDisabledOptions: true,
|
||||
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
|
||||
}"
|
||||
v-else-if="valueTypeMap[attr.value_type] == 'date' || valueTypeMap[attr.value_type] == 'datetime'"
|
||||
/>
|
||||
<a-input v-model="queryParams[attr.name]" style="width: 100%" allowClear v-else />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<template v-if="expand && attrList.length >= 4">
|
||||
<a-col
|
||||
:sm="24"
|
||||
:md="12"
|
||||
:lg="8"
|
||||
:xl="6"
|
||||
:key="'expand_' + item.name"
|
||||
v-for="item in attrList.slice(4)">
|
||||
<a-form-item
|
||||
:label="item.alias || item.name"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
labelAlign="right"
|
||||
>
|
||||
<a-select
|
||||
@popupScroll="loadMoreData(item.name, $event)"
|
||||
@search="(value) => fetchData(value, item.name)"
|
||||
v-model="queryParams[item.name]"
|
||||
placeholder="请选择"
|
||||
v-if="item.is_choice"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
:key="'Search_' + item.name + index"
|
||||
v-for="(choice, index) in item.choice_value"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
:style="{ width: '100%' }"
|
||||
@change="onChange"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'"
|
||||
:show-time="{
|
||||
hideDisabledOptions: true,
|
||||
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
|
||||
}"
|
||||
/>
|
||||
<a-input v-model="queryParams[item.name]" style="width: 100%" allowClear v-else />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :span="24" :style="{ textAlign: 'right', marginBottom: '10px' }">
|
||||
<a-switch
|
||||
ref="switch"
|
||||
v-if="hasSwitch"
|
||||
:un-checked-children="switchValue"
|
||||
@change="onSwitchChange"
|
||||
v-model="checked"
|
||||
/>
|
||||
<a-button :style="{ marginLeft: '8px' }" type="primary" html-type="submit" @click="handleSearch">
|
||||
查询
|
||||
</a-button>
|
||||
<a-button :style="{ marginLeft: '8px' }" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
<a :style="{ marginLeft: '8px', fontSize: '12px' }" @click="toggle" v-if="attrList.length >= 5">
|
||||
{{ expand ? '隐藏' : '展开' }} <a-icon :type="expand ? 'up' : 'down'" />
|
||||
</a>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import { valueTypeMap } from '../../constants/constants'
|
||||
export default {
|
||||
name: 'SearchForm',
|
||||
props: {
|
||||
attrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
hasSwitch: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
switchValue: {
|
||||
default: '',
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueTypeMap,
|
||||
expand: false,
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
},
|
||||
date: undefined,
|
||||
checked: false,
|
||||
scrollPage: 1,
|
||||
searchValue: undefined,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
queryParams: {
|
||||
deep: true,
|
||||
handler: function(val) {
|
||||
this.preProcessData()
|
||||
this.$emit('searchFormChange', val)
|
||||
},
|
||||
},
|
||||
'queryParams.resource_id': {
|
||||
handler(val) {
|
||||
if (val === undefined) {
|
||||
this.$emit('resourceClear')
|
||||
}
|
||||
},
|
||||
},
|
||||
'queryParams.link_id': {
|
||||
handler(val) {
|
||||
if (val === undefined) {
|
||||
this.$emit('resourceClear')
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleSearch() {
|
||||
this.queryParams.page = 1
|
||||
this.$emit('search', this.queryParams)
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
}
|
||||
this.date = undefined
|
||||
this.$emit('searchFormReset')
|
||||
},
|
||||
|
||||
toggle() {
|
||||
this.expand = !this.expand
|
||||
this.$emit('expandChange', this.expand)
|
||||
},
|
||||
|
||||
onChange(date, dateString) {
|
||||
this.queryParams.start = dateString[0]
|
||||
this.queryParams.end = dateString[1]
|
||||
},
|
||||
onSwitchChange(checked) {
|
||||
this.$emit('onSwitchChange', checked)
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return option.componentOptions.children[0].text.indexOf(input) >= 0
|
||||
},
|
||||
preProcessData() {
|
||||
Object.keys(this.queryParams).forEach((item) => {
|
||||
if (this.queryParams[item] === '' || this.queryParams[item] === undefined) {
|
||||
delete this.queryParams[item]
|
||||
}
|
||||
})
|
||||
return this.queryParams
|
||||
},
|
||||
loadMoreData(name, e, value) {
|
||||
const { target } = e
|
||||
if (target.scrollTop + target.clientHeight === target.scrollHeight) {
|
||||
this.$emit('loadMoreData', name, this.searchValue)
|
||||
}
|
||||
},
|
||||
fetchData(value, name) {
|
||||
this.searchValue = value
|
||||
if (name === 'link_id' || name === 'resource_id') {
|
||||
this.$emit('fetchData', value)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
197
cmdb-ui/src/modules/acl/views/module/triggerForm.vue
Normal file
197
cmdb-ui/src/modules/acl/views/module/triggerForm.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
@close="handleClose"
|
||||
width="500"
|
||||
:title="`${triggerId ? '修改' : '新建'}触发器`"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
>
|
||||
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }">
|
||||
<a-form-item label="触发器名">
|
||||
<a-input size="large" v-decorator="['name', { rules: [{ required: true, message: '请输入触发器名' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="资源名">
|
||||
<a-input size="large" v-decorator="['wildcard']" placeholder="优先正则模式(次通配符)"> </a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="创建人">
|
||||
<el-select :style="{ width: '100%' }" filterable multiple v-decorator="['uid']">
|
||||
<template v-for="role in roles">
|
||||
<el-option v-if="role.uid" :key="role.id" :value="role.uid" :label="role.name">{{ role.name }}</el-option>
|
||||
</template>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="资源类型">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
@change="handleRTChange"
|
||||
v-decorator="['resource_type_id', { rules: [{ required: true, message: '请选择资源类型' }] }]"
|
||||
>
|
||||
<el-option
|
||||
v-for="resourceType in resourceTypeList"
|
||||
:key="resourceType.id"
|
||||
:value="resourceType.id"
|
||||
:label="resourceType.name"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<a-tooltip title="查看正则匹配结果">
|
||||
<a class="trigger-form-pattern" @click="handlePattern"><a-icon type="eye"/></a>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
<a-form-item label="角色">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
filterable
|
||||
multiple
|
||||
v-decorator="['roles', { rules: [{ required: true, message: '请选择角色' }] }]"
|
||||
>
|
||||
<el-option v-for="role in roles" :key="role.id" :value="role.id" :label="role.name"></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="权限">
|
||||
<el-select
|
||||
:style="{ width: '100%' }"
|
||||
multiple
|
||||
v-decorator="['permissions', { rules: [{ required: true, message: '请选择权限' }] }]"
|
||||
>
|
||||
<el-option
|
||||
v-for="perm in selectResourceTypePerms"
|
||||
:key="perm.id"
|
||||
:value="perm.name"
|
||||
:label="perm.name"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="启用/禁用">
|
||||
<a-switch v-decorator="['enabled', { rules: [], valuePropName: 'checked', initialValue: true }]" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="handleClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">提交</a-button>
|
||||
</div>
|
||||
<TriggerPattern ref="triggerPattern" :roles="roles" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
<script>
|
||||
import { Select, Option, Input } from 'element-ui'
|
||||
import { addTrigger, updateTrigger } from '@/modules/acl/api/trigger'
|
||||
import TagSelectOption from '@/components/TagSelect/TagSelectOption'
|
||||
import TriggerPattern from '../module/triggerPattern'
|
||||
|
||||
export default {
|
||||
name: 'TriggerForm',
|
||||
components: {
|
||||
TagSelectOption,
|
||||
TriggerPattern,
|
||||
ElSelect: Select,
|
||||
ElOption: Option,
|
||||
ElInput: Input,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
triggerId: null,
|
||||
selectResourceTypePerms: [],
|
||||
form: this.$form.createForm(this),
|
||||
}
|
||||
},
|
||||
props: {
|
||||
roles: {
|
||||
required: true,
|
||||
type: Array,
|
||||
},
|
||||
resourceTypeList: {
|
||||
required: true,
|
||||
type: Array,
|
||||
},
|
||||
id2perms: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
// eslint-disable-next-line vue/prop-name-casing
|
||||
app_id: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
beforeCreate() {},
|
||||
methods: {
|
||||
handleEdit(ele) {
|
||||
this.form.resetFields()
|
||||
this.visible = true
|
||||
if (ele) {
|
||||
this.triggerId = ele.id
|
||||
this.$nextTick(() => {
|
||||
this.selectResourceTypePerms = this.id2perms[ele.resource_type_id]
|
||||
const { name, wildcard, uid, resource_type_id, roles, permissions, enabled } = ele
|
||||
this.form.setFieldsValue({
|
||||
name,
|
||||
wildcard,
|
||||
uid,
|
||||
resource_type_id,
|
||||
permissions,
|
||||
enabled,
|
||||
roles: roles.map((x) => Number(x)),
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.triggerId = null
|
||||
}
|
||||
},
|
||||
handleClose() {
|
||||
this.$nextTick(() => {
|
||||
this.visible = false
|
||||
})
|
||||
},
|
||||
handleRTChange(value) {
|
||||
this.selectResourceTypePerms = this.id2perms[value]
|
||||
},
|
||||
// filterOption(input, option) {
|
||||
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
// },
|
||||
handleSubmit() {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (err) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.triggerId) {
|
||||
updateTrigger(this.triggerId, { ...values, app_id: this.app_id }).then((res) => {
|
||||
this.visible = false
|
||||
this.$message.success('修改成功!')
|
||||
this.$emit('refresh')
|
||||
})
|
||||
} else {
|
||||
addTrigger({ ...values, app_id: this.app_id }).then((res) => {
|
||||
this.visible = false
|
||||
this.$message.success('创建成功!')
|
||||
this.$emit('refresh')
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handlePattern() {
|
||||
this.form.validateFields(['wildcard', 'uid', 'resource_type_id'], (err, values) => {
|
||||
if (!err) {
|
||||
const { wildcard, uid, resource_type_id } = values
|
||||
console.log(values)
|
||||
this.$refs.triggerPattern.open({
|
||||
resource_type_id,
|
||||
app_id: this.app_id,
|
||||
owner: uid,
|
||||
pattern: wildcard,
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.trigger-form-pattern {
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
}
|
||||
</style>
|
||||
295
cmdb-ui/src/modules/acl/views/module/triggerHistoryTable.vue
Normal file
295
cmdb-ui/src/modules/acl/views/module/triggerHistoryTable.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="triggerTableAttrList"
|
||||
@searchFormReset="searchFormReset"
|
||||
@search="handleSearch"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
size="small"
|
||||
resizable
|
||||
:max-height="`${windowHeight - 331}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="trigger_id" width="250px" title="触发器">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { searchTriggerHistory } from '@/modules/acl/api/history'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
props: {
|
||||
allUsers: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRoles: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allTriggers: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
allRolesMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allTriggersMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allUsersMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
allResourceTypesMap: {
|
||||
type: Map,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
loading: true,
|
||||
tableData: [],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除'],
|
||||
['trigger_apply', '应用'],
|
||||
['trigger_cancel', '取消'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['delete', 'red'],
|
||||
['update', 'orange'],
|
||||
['trigger_apply', 'green'],
|
||||
['trigger_cancel', 'red'],
|
||||
]),
|
||||
triggerTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: this.allUsers
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
{ '应用': 'trigger_apply' },
|
||||
{ '取消': 'trigger_cancel' },
|
||||
]
|
||||
},
|
||||
{
|
||||
alias: '触发器',
|
||||
is_choice: true,
|
||||
name: 'trigger_id',
|
||||
value_type: '2',
|
||||
choice_value: this.allTriggers
|
||||
}
|
||||
],
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0]
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
watch: {
|
||||
'$route.name': async function(oldName, newName) {
|
||||
this.app_id = this.$route.name.split('_')[0]
|
||||
await this.getTable(this.queryParams)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const res = await searchTriggerHistory(this.handleQueryParams(queryParams))
|
||||
res.data.forEach(item => {
|
||||
this.handleChangeDescription(item, item.operate_type)
|
||||
item.trigger_id = this.allTriggersMap.get(item.trigger_id)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = res.data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.queryParams.app_id = this.app_id
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.$route.name.split('_')[0]
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => this.allRolesMap.get(Number(i))).join(',')
|
||||
item.changeDescription = `新增触发器:${item.current.name}\n资源类型:${this.allResourceTypesMap.get(item.current.resource_type_id)},资源名:${item.current.wildcard},角色:[${newStr}]\n权限:${item.current.permissions}\n状态:${item.current.enabled}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.changeDescription = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
item.changeDescription += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.changeDescription += ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const str = item.origin.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => this.allRolesMap.get(Number(i))).join(',')
|
||||
item.changeDescription = `删除触发器:${item.origin.name}\n资源类型:${this.allResourceTypesMap.get(item.origin.resource_type_id)},资源名:${item.origin.wildcard},角色:[${newStr}]\n权限:${item.origin.permissions}\n状态:${item.origin.enabled}`
|
||||
break
|
||||
}
|
||||
case 'trigger_apply': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => this.allRolesMap.get(Number(i))).join(',')
|
||||
item.changeDescription = `应用触发器:${item.current.name}\n资源类型:${this.allResourceTypesMap.get(item.current.resource_type_id)},资源名:${item.current.wildcard},角色:[${newStr}]\n权限:${item.current.permissions}\n状态:${item.current.enabled}`
|
||||
break
|
||||
}
|
||||
case 'trigger_cancel': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => this.allRolesMap.get(Number(i))).join(',')
|
||||
item.changeDescription = `取消触发器:${item.current.name}\n资源类型:${this.allResourceTypesMap.get(item.current.resource_type_id)},资源名:${item.current.wildcard},角色:[${newStr}]\n权限:${item.current.permissions}\n状态:${item.current.enabled}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
handleQueryParams(queryParams) {
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
if (q) {
|
||||
q += `,${key}:${queryParams[key]}`
|
||||
} else {
|
||||
q += `${key}:${queryParams[key]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
</style>
|
||||
68
cmdb-ui/src/modules/acl/views/module/triggerPattern.vue
Normal file
68
cmdb-ui/src/modules/acl/views/module/triggerPattern.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:hasFooter="false"
|
||||
title="正则匹配结果"
|
||||
:visible="patternVisible"
|
||||
width="500"
|
||||
@close="
|
||||
() => {
|
||||
patternVisible = false
|
||||
}
|
||||
"
|
||||
>
|
||||
<vxe-table :data="tableData" :max-height="`${windowHeight - 110}px`">
|
||||
<vxe-table-column field="name" title="资源名"></vxe-table-column>
|
||||
<vxe-table-column field="uid" title="创建人">
|
||||
<template #default="{row}">
|
||||
{{ getRoleName(row.uid) }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column field="created_at" title="创建时间"></vxe-table-column>
|
||||
</vxe-table>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { patternResults } from '@/modules/acl/api/trigger'
|
||||
export default {
|
||||
name: 'TriggerPattern',
|
||||
props: {
|
||||
roles: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
patternVisible: false,
|
||||
tableData: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: state => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
open(params) {
|
||||
patternResults(params).then(res => {
|
||||
this.patternVisible = true
|
||||
this.tableData = res
|
||||
})
|
||||
},
|
||||
getRoleName(uid) {
|
||||
if (uid) {
|
||||
const _find = this.roles.find(item => item.uid === uid)
|
||||
if (_find) {
|
||||
return _find.name
|
||||
}
|
||||
return ''
|
||||
}
|
||||
return ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
198
cmdb-ui/src/modules/acl/views/module/userForm.vue
Normal file
198
cmdb-ui/src/modules/acl/views/module/userForm.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<CustomDrawer
|
||||
:closable="false"
|
||||
:title="drawerTitle"
|
||||
:visible="drawerVisible"
|
||||
@close="onClose"
|
||||
placement="right"
|
||||
width="500px"
|
||||
>
|
||||
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-item label="用户名(英文)">
|
||||
<a-input
|
||||
name="username"
|
||||
placeholder="英文名"
|
||||
v-decorator="['username', { rules: [{ required: true, message: '请输入用户名' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="中文名">
|
||||
<a-input name="nickname" v-decorator="['nickname', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
<a-form-item label="密码">
|
||||
<a-input
|
||||
type="password"
|
||||
name="password"
|
||||
v-decorator="['password', { rules: [{ required: true, message: '请输入密码' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="部门">
|
||||
<a-input name="department" v-decorator="['department', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="小组">
|
||||
<a-input name="catalog" v-decorator="['catalog', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="邮箱">
|
||||
<a-input
|
||||
name="email"
|
||||
v-decorator="[
|
||||
'email',
|
||||
{
|
||||
rules: [
|
||||
{
|
||||
type: 'email',
|
||||
message: '请输入正确的邮箱!',
|
||||
},
|
||||
{
|
||||
required: true,
|
||||
message: '请输入邮箱',
|
||||
},
|
||||
],
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="手机号码">
|
||||
<a-input
|
||||
name="mobile"
|
||||
v-decorator="['mobile', { rules: [{ message: '请输入正确的手机号码', pattern: /^1\d{10}$/ }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="是否锁定">
|
||||
<a-switch @change="onChange" name="block" v-decorator="['block', { rules: [], valuePropName: 'checked' }]" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" />
|
||||
</a-form-item>
|
||||
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addUser, updateUserById } from '@/modules/acl/api/user'
|
||||
|
||||
export default {
|
||||
name: 'AttributeForm',
|
||||
data() {
|
||||
return {
|
||||
drawerTitle: '新增用户',
|
||||
drawerVisible: false,
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
|
||||
computed: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
handleCreate() {
|
||||
this.drawerVisible = true
|
||||
},
|
||||
onClose() {
|
||||
this.form.resetFields()
|
||||
this.drawerVisible = false
|
||||
},
|
||||
onChange(e) {
|
||||
console.log(`checked = ${e}`)
|
||||
},
|
||||
|
||||
handleEdit(record) {
|
||||
this.drawerVisible = true
|
||||
console.log(record)
|
||||
this.$nextTick(() => {
|
||||
this.form.setFieldsValue({
|
||||
id: record.uid,
|
||||
username: record.username,
|
||||
nickname: record.nickname,
|
||||
password: record.password,
|
||||
department: record.department,
|
||||
catalog: record.catalog,
|
||||
email: record.email,
|
||||
mobile: record.mobile,
|
||||
block: record.block,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
|
||||
if (values.id) {
|
||||
this.updateUser(values.id, values)
|
||||
} else {
|
||||
this.createUser(values)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
updateUser(attrId, data) {
|
||||
updateUserById(attrId, data).then((res) => {
|
||||
this.$message.success(`更新成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
createUser(data) {
|
||||
addUser(data).then((res) => {
|
||||
this.$message.success(`添加成功`)
|
||||
this.handleOk()
|
||||
this.onClose()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
|
||||
// requestFailed (err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// }
|
||||
},
|
||||
watch: {},
|
||||
props: {
|
||||
handleOk: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
width: calc(100% - 216px);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.operator {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.action-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.fold {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
102
cmdb-ui/src/modules/acl/views/module/usersUnderRoleForm.vue
Normal file
102
cmdb-ui/src/modules/acl/views/module/usersUnderRoleForm.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<CustomDrawer :closable="true" :visible="visible" width="500px" @close="handleClose" title="组用户">
|
||||
<a-form-item label="添加用户" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
|
||||
<a-row>
|
||||
<a-col span="15">
|
||||
<el-select v-model="selectedChildrenRole" multiple collapse-tags size="small" filterable>
|
||||
<el-option
|
||||
class="drop-down-render"
|
||||
v-for="role in allRoles"
|
||||
:key="role.id"
|
||||
:value="role.id"
|
||||
:label="role.name"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</a-col>
|
||||
<a-col span="5" offset="1">
|
||||
<a-button style="display: inline-block" @click="handleAddRole">确定</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-card>
|
||||
<a-row :gutter="24" v-for="(record, index) in records" :key="record.id" :style="{ marginBottom: '5px' }">
|
||||
<a-col :span="20">{{ index + 1 }}、{{ record.nickname }}</a-col>
|
||||
<a-col :span="4"><a-button type="danger" size="small" @click="handleRevokeUser(record)">移除</a-button></a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUsersUnderRole, delParentRole, addBatchParentRole } from '@/modules/acl/api/role'
|
||||
import { Select, Option } from 'element-ui'
|
||||
export default {
|
||||
name: 'UsersUnderRoleForm',
|
||||
components: { ElSelect: Select, ElOption: Option },
|
||||
props: {
|
||||
allRoles: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
records: [],
|
||||
roleId: 0,
|
||||
selectedChildrenRole: [],
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.visible = false
|
||||
},
|
||||
loadRecords(roleId) {
|
||||
getUsersUnderRole(roleId, { app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.records = res['users']
|
||||
})
|
||||
// .catch(err=> this.$httpError(err))
|
||||
},
|
||||
handleProcessRole(roleId) {
|
||||
this.roleId = roleId
|
||||
this.visible = true
|
||||
this.loadRecords(roleId)
|
||||
},
|
||||
// filterOption(input, option) {
|
||||
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
// },
|
||||
async handleAddRole() {
|
||||
// await this.selectedChildrenRole.forEach(item => {
|
||||
// addParentRole(item, this.roleId, { app_id: this.$route.name.split('_')[0] }).then(res => {
|
||||
// this.$message.success('添加成功')
|
||||
// })
|
||||
// // .catch(err=>{
|
||||
// // this.$httpError(err)
|
||||
// // })
|
||||
|
||||
// })
|
||||
await addBatchParentRole(this.roleId, {
|
||||
child_ids: this.selectedChildrenRole,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
})
|
||||
this.loadRecords(this.roleId)
|
||||
this.$message.success('添加完成')
|
||||
this.selectedChildrenRole = []
|
||||
},
|
||||
handleRevokeUser(record) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
content: '是否确定要移除该用户',
|
||||
onOk() {
|
||||
delParentRole(record.role.id, that.roleId, { app_id: that.$route.name.split('_')[0] }).then((res) => {
|
||||
that.$message.success('删除成功!')
|
||||
that.loadRecords(that.roleId)
|
||||
})
|
||||
// .catch(err=>that.$httpError(err))
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
34
cmdb-ui/src/modules/acl/views/operation_history/index.vue
Normal file
34
cmdb-ui/src/modules/acl/views/operation_history/index.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div :style="{ backgroundColor: '#fff', padding: '24px' }">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="权限变更">
|
||||
<permisson-table></permisson-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="角色变更">
|
||||
<role-history-table></role-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="资源变更">
|
||||
<resource-history-table></resource-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="4" tab="资源类型变更">
|
||||
<resource-type-history-table></resource-type-history-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" tab="触发器变更">
|
||||
<trigger-history-table></trigger-history-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import permissonTable from './modules/permissionTable.vue'
|
||||
import resourceHistoryTable from './modules/resourceHistoryTable.vue'
|
||||
import resourceTypeHistoryTable from './modules/resourceTypeHistoryTable.vue'
|
||||
import roleHistoryTable from './modules/roleHistoryTable.vue'
|
||||
import triggerHistoryTable from './modules/triggerHistoryTable.vue'
|
||||
export default {
|
||||
components: { permissonTable, resourceHistoryTable, resourceTypeHistoryTable, roleHistoryTable, triggerHistoryTable },
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -0,0 +1,416 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="permissionTableAttrList"
|
||||
@search="handleSearch"
|
||||
@expandChange="handleExpandChange"
|
||||
@searchFormReset="searchFormReset"
|
||||
@searchFormChange="searchFormChange"
|
||||
@loadMoreData="loadMoreResources"
|
||||
@fetchData="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
:scroll-y="{ enabled: false }"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.deleted_at || row.updated_at || row.created_at }}</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="row.operate_type === 'grant' ? 'green' : 'red'">{{
|
||||
operateTypeMap.get(row.operate_type)
|
||||
}}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="rid" title="用户"></vxe-column>
|
||||
<vxe-column field="resource_type_id" title="资源类型"></vxe-column>
|
||||
<vxe-column field="resources" title="资源">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.resource_ids.length > 0">
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
<span>{{ row.resource_ids[0] }}</span>
|
||||
</template>
|
||||
<a-tag color="blue" v-for="(resource, index) in row.resource_ids" :key="'resources_' + resource + index">
|
||||
{{ resource }}
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="row.group_ids.length > 0">
|
||||
<a-tag color="blue" v-for="(group, index) in row.group_ids" :key="'groups_' + group + index">
|
||||
{{ group }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="权限">
|
||||
<template #default="{ row }">
|
||||
<a-tag v-for="(perm, index) in row.permission_ids" :key="'perms_' + perm + index">
|
||||
{{ perm }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50, 100, 200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import debounce from 'lodash/debounce'
|
||||
import Pager from '../../module/pager.vue'
|
||||
import SearchForm from '../../module/searchForm.vue'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
import { searchPermissonHistory } from '@/modules/acl/api/history'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
import { searchResource, searchResourceType } from '@/modules/acl/api/resource'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
this.fetchResources = debounce(this.fetchResources, 800)
|
||||
return {
|
||||
app_id: undefined,
|
||||
resource_id: undefined,
|
||||
isExpand: false,
|
||||
loading: true,
|
||||
resourcesPage: 1,
|
||||
resourcesNum: 0,
|
||||
tableData: [],
|
||||
allRoles: [],
|
||||
allUsers: [],
|
||||
allResourceTypes: [],
|
||||
allResources: [],
|
||||
allApps: [],
|
||||
allRolesMap: new Map(),
|
||||
allUsersMap: new Map(),
|
||||
allResourceTypesMap: new Map(),
|
||||
operateTypeMap: new Map([
|
||||
['grant', '授权'],
|
||||
['revoke', '撤销'],
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
start: '',
|
||||
end: '',
|
||||
},
|
||||
permissionTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3',
|
||||
},
|
||||
{
|
||||
alias: '应用',
|
||||
is_choice: true,
|
||||
name: 'app_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '用户',
|
||||
is_choice: true,
|
||||
name: 'rid',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '资源类型',
|
||||
is_choice: true,
|
||||
name: 'resource_type_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '资源',
|
||||
is_choice: true,
|
||||
name: 'resource_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [{ 授权: 'grant' }, { 撤销: 'revoke' }],
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.$watch(
|
||||
function() {
|
||||
return this.permissionTableAttrList[3].choice_value
|
||||
},
|
||||
function() {
|
||||
delete this.$refs.child.queryParams.rid
|
||||
delete this.$refs.child.queryParams.resource_type_id
|
||||
delete this.$refs.child.queryParams.resource_id
|
||||
}
|
||||
)
|
||||
await Promise.all([this.getAllApps(), this.getAllUsers()])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data, id2groups, id2perms, id2resources, id2roles, id2resource_types } = await searchPermissonHistory(
|
||||
this.handleQueryParams(queryParams)
|
||||
)
|
||||
data.forEach((item) => {
|
||||
item.rid = id2roles[item.rid].name
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
// 脏数据清除后可删
|
||||
if (id2resource_types[item.resource_type_id]) {
|
||||
item.resource_type_id = id2resource_types[item.resource_type_id].name
|
||||
}
|
||||
item.resource_ids.forEach((subItem, index) => {
|
||||
item.resource_ids[index] = id2resources[subItem].name
|
||||
})
|
||||
item.group_ids.forEach((subItem, index) => {
|
||||
item.group_ids[index] = id2groups[subItem].name
|
||||
})
|
||||
item.permission_ids.forEach((subItem, index) => {
|
||||
item.permission_ids[index] = id2perms[subItem].name
|
||||
})
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getAllApps() {
|
||||
const res = await searchApp()
|
||||
const allApps = []
|
||||
res.apps.forEach((item) => {
|
||||
allApps.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allApps = allApps
|
||||
this.permissionTableAttrList[1].choice_value = this.allApps
|
||||
},
|
||||
async getAllRoles(app_id) {
|
||||
if (!app_id) {
|
||||
this.permissionTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
const { roles } = await searchRole({
|
||||
page_size: 9999,
|
||||
app_id: app_id,
|
||||
})
|
||||
const allRoles = []
|
||||
roles.forEach((item) => {
|
||||
allRoles.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allRoles = allRoles
|
||||
this.permissionTableAttrList[3].choice_value = this.allRoles
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000, app_id: 'acl' })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach((item) => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
this.permissionTableAttrList[2].choice_value = this.allUsers
|
||||
},
|
||||
async getAllResourceTypes(app_id) {
|
||||
if (!app_id) {
|
||||
this.permissionTableAttrList[4].choice_value = []
|
||||
return
|
||||
}
|
||||
const { groups } = await searchResourceType({
|
||||
page_size: 9999,
|
||||
page: 1,
|
||||
app_id: app_id,
|
||||
})
|
||||
const allResourceTypes = []
|
||||
groups.forEach((item) => {
|
||||
allResourceTypes.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allResourceTypes = allResourceTypes
|
||||
this.permissionTableAttrList[4].choice_value = this.allResourceTypes
|
||||
},
|
||||
async getAllResources(app_id, page, value = undefined) {
|
||||
if (!app_id) {
|
||||
this.permissionTableAttrList[5].choice_value = []
|
||||
return
|
||||
}
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: page,
|
||||
page_size: 50,
|
||||
app_id: app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
const allResources = this.allResources
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.permissionTableAttrList[5].choice_value = this.allResources
|
||||
},
|
||||
loadMoreResources(name, value) {
|
||||
if (name === 'resource_id' && this.allResources.length < this.resourcesNum) {
|
||||
let currentPage = this.resourcesPage
|
||||
this.getAllResources(this.app_id, ++currentPage, value)
|
||||
this.resourcesPage = currentPage
|
||||
}
|
||||
},
|
||||
resourceClear() {
|
||||
this.resourcesPage = 1
|
||||
this.allResources = []
|
||||
this.getAllResources(this.app_id, 1)
|
||||
},
|
||||
async fetchResources(value) {
|
||||
this.allResources = []
|
||||
const allResources = []
|
||||
if (!this.app_id) {
|
||||
this.permissionTableAttrList[5].choice_value = []
|
||||
return
|
||||
}
|
||||
this.resourcesPage = 1
|
||||
if (value === '') {
|
||||
this.getAllResources(this.app_id, 1)
|
||||
return
|
||||
}
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.permissionTableAttrList[5].choice_value = this.allResources
|
||||
},
|
||||
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
}
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
async searchFormChange(queryParams) {
|
||||
if (this.app_id !== queryParams.app_id) {
|
||||
this.app_id = queryParams.app_id
|
||||
this.allResources = []
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
await Promise.all([
|
||||
this.getAllRoles(this.app_id),
|
||||
this.getAllResourceTypes(this.app_id),
|
||||
this.getAllResources(this.app_id, this.resourcesPage),
|
||||
])
|
||||
}
|
||||
if (queryParams.app_id === undefined) {
|
||||
this.app_id = undefined
|
||||
this.$refs.child.queryParams.rid = undefined
|
||||
this.$refs.child.queryParams.resource_type_id = undefined
|
||||
this.$refs.child.queryParams.resource_id = undefined
|
||||
this.allResources = []
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
}
|
||||
},
|
||||
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
) {
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,400 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="resourceTableAttrList"
|
||||
:hasSwitch="true"
|
||||
switchValue="组"
|
||||
@onSwitchChange="onSwitchChange"
|
||||
@expandChange="handleExpandChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
@searchFormChange="searchFormChange"
|
||||
@loadMoreData="loadMoreResources"
|
||||
@fetchData="fetchResources"
|
||||
@resourceClear="resourceClear"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:loading="loading"
|
||||
:data="tableData"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="link_id" title="资源名">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.description }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50, 100, 200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from '../../module/pager.vue'
|
||||
import SearchForm from '../../module/searchForm.vue'
|
||||
import { searchResourceHistory } from '@/modules/acl/api/history'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
import { searchResource } from '@/modules/acl/api/resource'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
this.fetchResources = _.debounce(this.fetchResources, 800)
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
isExpand: false,
|
||||
app_id: undefined,
|
||||
resourcesPage: 1,
|
||||
resourcesNum: 0,
|
||||
tableData: [],
|
||||
allResources: [],
|
||||
allUsers: [],
|
||||
allApps: [],
|
||||
allUsersMap: new Map(),
|
||||
allResourcesMap: new Map(),
|
||||
resourceTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3',
|
||||
},
|
||||
{
|
||||
alias: '应用',
|
||||
is_choice: true,
|
||||
name: 'app_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '资源名',
|
||||
is_choice: true,
|
||||
name: 'link_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [{ 新建: 'create' }, { 修改: 'update' }, { 删除: 'delete' }],
|
||||
},
|
||||
],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['update', 'orange'],
|
||||
['delete', 'red'],
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: 'resource',
|
||||
start: '',
|
||||
end: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$watch(
|
||||
function() {
|
||||
return this.resourceTableAttrList[3].choice_value
|
||||
},
|
||||
function() {
|
||||
delete this.$refs.child.queryParams.link_id
|
||||
}
|
||||
)
|
||||
await Promise.all([this.getAllApps(), this.getAllUsers()])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data } = await searchResourceHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach((item) => {
|
||||
item.originResource_ids = item?.extra?.resource_ids?.origin
|
||||
item.currentResource_ids = item?.extra?.resource_ids?.current
|
||||
this.handleChangeDescription(item, item.operate_type)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getAllApps() {
|
||||
const { apps } = await searchApp()
|
||||
const allApps = []
|
||||
apps.forEach((item) => {
|
||||
allApps.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allApps = allApps
|
||||
this.resourceTableAttrList[1].choice_value = this.allApps
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000, app_id: 'acl' })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach((item) => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
this.resourceTableAttrList[2].choice_value = this.allUsers
|
||||
},
|
||||
async getAllResources(app_id, page, value = undefined) {
|
||||
if (!app_id) {
|
||||
this.resourceTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: page,
|
||||
page_size: 50,
|
||||
app_id: app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
const allResources = this.allResources
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.resourceTableAttrList[3].choice_value = this.allResources
|
||||
},
|
||||
loadMoreResources(name, value) {
|
||||
if (name === 'link_id' && this.allResources.length < this.resourcesNum) {
|
||||
let currentPage = this.resourcesPage
|
||||
this.getAllResources(this.app_id, ++currentPage, value)
|
||||
this.resourcesPage = currentPage
|
||||
}
|
||||
},
|
||||
resourceClear() {
|
||||
this.resourcesPage = 1
|
||||
this.allResources = []
|
||||
this.getAllResources(this.app_id, 1)
|
||||
},
|
||||
async fetchResources(value) {
|
||||
this.allResources = []
|
||||
const allResources = []
|
||||
if (!this.app_id) {
|
||||
this.resourceTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
this.resourcesPage = 1
|
||||
if (value === '') {
|
||||
this.getAllResources(this.app_id, 1)
|
||||
return
|
||||
}
|
||||
const { resources, numfound } = await searchResource({
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
app_id: this.app_id,
|
||||
q: value,
|
||||
})
|
||||
this.resourcesNum = numfound
|
||||
resources.forEach((item) => {
|
||||
allResources.push({ [item.name]: item.id })
|
||||
})
|
||||
this.allResources = allResources
|
||||
this.resourceTableAttrList[3].choice_value = this.allResources
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, scope: this.checked ? 'resource_group' : 'resource' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: this.checked ? 'resource_group' : 'resource',
|
||||
}
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onSwitchChange(checked) {
|
||||
this.checked = checked
|
||||
this.queryParams.scope = checked ? 'resource_group' : 'resource'
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
async searchFormChange(queryParams) {
|
||||
if (this.app_id !== queryParams.app_id) {
|
||||
this.app_id = queryParams.app_id
|
||||
this.allResources = []
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
await this.getAllResources(this.app_id, this.resourcesPage)
|
||||
}
|
||||
if (queryParams.app_id === undefined) {
|
||||
this.app_id = undefined
|
||||
this.$refs.child.queryParams.link_id = undefined
|
||||
this.allResources = []
|
||||
this.resourcesPage = 1
|
||||
this.resourcesNum = 0
|
||||
}
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (
|
||||
key !== 'page' &&
|
||||
key !== 'page_size' &&
|
||||
key !== 'app_id' &&
|
||||
key !== 'q' &&
|
||||
key !== 'start' &&
|
||||
key !== 'end' &&
|
||||
queryParams[key] !== undefined
|
||||
) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
item.description = `新建资源:${item.current.name}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.description = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.description += str
|
||||
}
|
||||
}
|
||||
}
|
||||
const originResource_ids = item.originResource_ids
|
||||
const currentResource_ids = item.currentResource_ids
|
||||
if (!_.isEqual(originResource_ids, currentResource_ids)) {
|
||||
if (originResource_ids.length === 0) {
|
||||
const str = ` 【 resource_ids : 新增 ${currentResource_ids} 】 `
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 resource_ids : 由 ${originResource_ids} 改为 ${currentResource_ids} 】 `
|
||||
item.description += str
|
||||
}
|
||||
}
|
||||
if (!item.description) item.description = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
item.description = `删除资源:${item.origin.name}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,324 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="resourceTableAttrList"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
@searchFormChange="searchFormChange"
|
||||
@expandChange="handleExpandChange"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="link_id" width="159px" title="资源类型名">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from '../../module/pager.vue'
|
||||
import SearchForm from '../../module/searchForm.vue'
|
||||
import { searchResourceHistory } from '@/modules/acl/api/history'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
isExpand: false,
|
||||
app_id: undefined,
|
||||
tableData: [],
|
||||
allResourceTypes: [],
|
||||
allUsers: [],
|
||||
allApps: [],
|
||||
allUsersMap: new Map(),
|
||||
allResourcesMap: new Map(),
|
||||
resourceTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '应用',
|
||||
is_choice: true,
|
||||
name: 'app_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '资源类型',
|
||||
is_choice: true,
|
||||
name: 'link_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
]
|
||||
}
|
||||
],
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除']
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['update', 'orange'],
|
||||
['delete', 'red']
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: 'resource_type',
|
||||
start: '',
|
||||
end: ''
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$watch(
|
||||
function () {
|
||||
return this.resourceTableAttrList[3].choice_value
|
||||
},
|
||||
function () {
|
||||
delete this.$refs.child.queryParams.link_id
|
||||
}
|
||||
)
|
||||
await Promise.all([ this.getAllApps(), this.getAllUsers() ])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data } = await searchResourceHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach(item => {
|
||||
this.handleChangeDescription(item, item.operate_type)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getAllApps() {
|
||||
const { apps } = await searchApp()
|
||||
const allApps = []
|
||||
apps.forEach(item => { allApps.push({ [item.name]: item.id }) })
|
||||
this.allApps = allApps
|
||||
this.resourceTableAttrList[1].choice_value = this.allApps
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000, app_id: 'acl' })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach(item => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
this.resourceTableAttrList[2].choice_value = this.allUsers
|
||||
},
|
||||
async getAllResourceTypes(app_id) {
|
||||
if (!app_id) {
|
||||
this.resourceTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
const { groups } = await searchResourceType({
|
||||
page: 1,
|
||||
page_size: 9999,
|
||||
app_id: app_id
|
||||
})
|
||||
const allResourceTypes = []
|
||||
groups.forEach(item => { allResourceTypes.push({ [item.name]: item.id }) })
|
||||
this.allResourceTypes = allResourceTypes
|
||||
this.resourceTableAttrList[3].choice_value = this.allResourceTypes
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, scope: 'resource_type' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: 'resource_type',
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
async searchFormChange(queryParams) {
|
||||
if (this.app_id !== queryParams.app_id) {
|
||||
this.app_id = queryParams.app_id
|
||||
await this.getAllResourceTypes(this.app_id)
|
||||
}
|
||||
if (queryParams.app_id === undefined) {
|
||||
this.app_id = undefined
|
||||
this.$refs.child.queryParams.link_id = undefined
|
||||
}
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'q' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
const description = item.current?.description === undefined ? '无' : item.current?.description
|
||||
const permission = item.extra.permission_ids?.current === undefined ? '无' : item.extra.permission_ids?.current
|
||||
item.changeDescription = `新增资源类型:${item.current.name}\n描述:${description}\n权限:${permission}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.changeDescription = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null || oldVal === '') {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 \n`
|
||||
item.changeDescription += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 \n`
|
||||
item.changeDescription += str
|
||||
}
|
||||
}
|
||||
}
|
||||
const currentPerms = item.extra.permission_ids?.current === undefined ? '无' : item.extra.permission_ids?.current
|
||||
const originPerms = item.extra.permission_ids?.origin === undefined ? '无' : item.extra.permission_ids?.origin
|
||||
if (!_.isEqual(currentPerms, originPerms)) item.changeDescription += ` 【 permission_ids : 由 ${originPerms} 改为 ${currentPerms} 】 `
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const description = item.origin?.description === undefined ? '无' : item.origin?.description
|
||||
const permission = item.extra.permission_ids?.origin === undefined ? '无' : item.extra.permission_ids?.origin
|
||||
item.changeDescription = `删除资源类型:${item.origin.name}\n描述:${description}\n权限:${permission}`
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,322 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="roleTableAttrList"
|
||||
:hasSwitch="true"
|
||||
switchValue="角色关系"
|
||||
@onSwitchChange="onSwitchChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - 331}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="112px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<template>
|
||||
<a-tag :color="handleTagColor(row.operate_type)">{{ operateTypeMap.get(row.operate_type) }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :title="checked ? '角色' : '角色'">
|
||||
<template #default="{ row }">
|
||||
<template v-if="!checked">
|
||||
<a-tag color="blue">{{ row.current.name || row.origin.name }}</a-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag v-for="(id,index) in row.extra.child_ids" :key="'child_ids_' + id + index" color="blue">{{ id }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column :title="checked ? '继承自' : '管理员'" :width="checked ? '350px' : '80px'">
|
||||
<template #default="{ row }">
|
||||
<template v-if="!checked">
|
||||
<a-icon type="check" v-if="row.current.is_app_admin"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag v-for="(id,index) in row.extra.parent_ids" :key="'parent_ids_' + id + index" color="cyan">{{ id }}</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述" v-if="!checked">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.description }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="source" width="100px" title="来源"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from '../../module/pager.vue'
|
||||
import SearchForm from '../../module/searchForm.vue'
|
||||
import { searchRoleHistory } from '@/modules/acl/api/history'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
checked: false,
|
||||
app_id: undefined,
|
||||
tableData: [],
|
||||
allApps: [],
|
||||
allUsers: [],
|
||||
allRoles: [],
|
||||
allRolesMap: new Map(),
|
||||
allUsersMap: new Map(),
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['delete', '删除'],
|
||||
['update', '修改'],
|
||||
['role_relation_add', '添加角色关系'],
|
||||
['role_relation_delete', '删除角色关系'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['delete', 'red'],
|
||||
['update', 'orange'],
|
||||
['role_relation_add', 'green'],
|
||||
['role_relation_delete', 'red']
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: 'role',
|
||||
start: '',
|
||||
end: ''
|
||||
},
|
||||
roleTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '应用',
|
||||
is_choice: true,
|
||||
name: 'app_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
{ '添加角色关系': 'role_relation_add' },
|
||||
{ '删除角色关系': 'role_relation_delete' },
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await Promise.all([ this.getAllApps(), this.getAllUsers() ])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data, id2roles, id2perms, id2resources } = await searchRoleHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach(item => {
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
if (item.operate_type === 'role_relation_add' || item.operate_type === 'role_relation_delete') {
|
||||
item.extra.child_ids.forEach((subItem, index) => { item.extra.child_ids[index] = id2roles[subItem].name })
|
||||
item.extra.parent_ids.forEach((subItem, index) => { item.extra.parent_ids[index] = id2roles[subItem].name })
|
||||
} else {
|
||||
this.handleChangeDescription(item, item.operate_type, id2roles, id2perms, id2resources)
|
||||
}
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getAllApps() {
|
||||
const { apps } = await searchApp()
|
||||
const allApps = []
|
||||
apps.forEach(item => { allApps.push({ [item.name]: item.id }) })
|
||||
this.allApps = allApps
|
||||
this.roleTableAttrList[1].choice_value = this.allApps
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000, app_id: 'acl' })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach(item => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
this.roleTableAttrList[2].choice_value = this.allUsers
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
onSwitchChange(checked) {
|
||||
this.checked = checked
|
||||
this.queryParams.scope = checked ? 'role_relation' : 'role'
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = { ...queryParams, scope: this.checked ? 'role_relation' : 'role' }
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.$refs.child.checked = false
|
||||
this.checked = false
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
scope: this.checked ? 'role_relation' : 'role'
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// 处理查询参数
|
||||
handleQueryParams(queryParams) {
|
||||
let flag = false
|
||||
let q = queryParams.q ? queryParams.q : ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'q' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
flag = true
|
||||
if (q) q += `,${key}:${queryParams[key]}`
|
||||
else q += `${key}:${queryParams[key]}`
|
||||
delete queryParams[key]
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return flag ? newQueryParams : queryParams
|
||||
},
|
||||
// 处理tag颜色
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
},
|
||||
handleChangeDescription(item, operate_type, id2roles, id2perms, id2resources) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
item.description = `新建角色:${item.current.name}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.description = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
item.description += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.description += str
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item.description) item.description = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const { extra: { child_ids, parent_ids, role_permissions } } = item
|
||||
child_ids.forEach((subItem, index) => { child_ids[index] = id2roles[subItem].name })
|
||||
parent_ids.forEach((subItem, index) => { parent_ids[index] = id2roles[subItem].name })
|
||||
|
||||
const resourceMap = new Map()
|
||||
const permsArr = []
|
||||
role_permissions.forEach(subItem => {
|
||||
const resource_id = subItem.resource_id
|
||||
const perm_id = subItem.perm_id
|
||||
if (resourceMap.has(resource_id)) {
|
||||
let resource_perms = resourceMap.get(resource_id)
|
||||
resource_perms += `,${id2perms[perm_id].name}`
|
||||
resourceMap.set(resource_id, resource_perms)
|
||||
} else {
|
||||
resourceMap.set(resource_id, String(id2perms[perm_id].name))
|
||||
}
|
||||
})
|
||||
resourceMap.forEach((value, key) => {
|
||||
permsArr.push(`${id2resources[key].name}:${value}`)
|
||||
})
|
||||
item.description = `继承者:${child_ids}\n继承自:${parent_ids}\n涉及资源及权限:\n${permsArr.join(`\n`)}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="triggerTableAttrList"
|
||||
@searchFormReset="searchFormReset"
|
||||
@search="handleSearch"
|
||||
@expandChange="handleExpandChange"
|
||||
@searchFormChange="searchFormChange"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
border
|
||||
resizable
|
||||
size="small"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
>
|
||||
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column>
|
||||
<vxe-column field="operate_type" width="80px" title="操作">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="handleTagColor(row.operate_type)">
|
||||
{{ operateTypeMap.get(row.operate_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="trigger_id" width="250px" title="触发器">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.current.name || row.origin.name }}
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="描述">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="tableDataLength"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import Pager from '../../module/pager.vue'
|
||||
import SearchForm from '../../module/searchForm.vue'
|
||||
import { searchTriggerHistory } from '@/modules/acl/api/history'
|
||||
import { getTriggers } from '@/modules/acl/api/trigger'
|
||||
import { searchUser } from '@/modules/acl/api/user'
|
||||
import { searchApp } from '@/modules/acl/api/app'
|
||||
export default {
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
return {
|
||||
app_id: undefined,
|
||||
loading: true,
|
||||
isExpand: false,
|
||||
tableData: [],
|
||||
allResourceTypes: [],
|
||||
allResources: [],
|
||||
allUsers: [],
|
||||
allRoles: [],
|
||||
allTriggers: [],
|
||||
allApps: [],
|
||||
allRolesMap: new Map(),
|
||||
allUsersMap: new Map(),
|
||||
allResourceTypesMap: new Map(),
|
||||
allResourcesMap: new Map(),
|
||||
allTriggersMap: new Map(),
|
||||
operateTypeMap: new Map([
|
||||
['create', '新建'],
|
||||
['update', '修改'],
|
||||
['delete', '删除'],
|
||||
['trigger_apply', '应用'],
|
||||
['trigger_cancel', '取消'],
|
||||
]),
|
||||
colorMap: new Map([
|
||||
['create', 'green'],
|
||||
['delete', 'red'],
|
||||
['update', 'orange'],
|
||||
['trigger_apply', 'green'],
|
||||
['trigger_cancel', 'red'],
|
||||
]),
|
||||
triggerTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '应用',
|
||||
is_choice: true,
|
||||
name: 'app_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作员',
|
||||
is_choice: true,
|
||||
name: 'operate_uid',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '触发器',
|
||||
is_choice: true,
|
||||
name: 'trigger_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新建': 'create' },
|
||||
{ '修改': 'update' },
|
||||
{ '删除': 'delete' },
|
||||
{ '应用': 'trigger_apply' },
|
||||
{ '取消': 'trigger_cancel' },
|
||||
]
|
||||
},
|
||||
],
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
start: '',
|
||||
end: ''
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$watch(
|
||||
function () {
|
||||
return this.triggerTableAttrList[3].choice_value
|
||||
},
|
||||
function () {
|
||||
delete this.$refs.child.queryParams.trigger_id
|
||||
}
|
||||
)
|
||||
await Promise.all([ this.getAllApps(), this.getAllUsers() ])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
tableDataLength() {
|
||||
return this.tableData.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const { data, id2resource_types, id2roles } = await searchTriggerHistory(this.handleQueryParams(queryParams))
|
||||
data.forEach(item => {
|
||||
this.handleChangeDescription(item, item.operate_type, id2resource_types, id2roles)
|
||||
item.trigger_id = this.allTriggersMap.get(item.trigger_id)
|
||||
item.operate_uid = this.allUsersMap.get(item.operate_uid)
|
||||
})
|
||||
this.tableData = data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getAllApps() {
|
||||
const { apps } = await searchApp()
|
||||
const allApps = []
|
||||
apps.forEach(item => { allApps.push({ [item.name]: item.id }) })
|
||||
this.allApps = allApps
|
||||
this.triggerTableAttrList[1].choice_value = this.allApps
|
||||
},
|
||||
async getAllUsers() {
|
||||
const { users } = await searchUser({ page_size: 10000, app_id: 'acl' })
|
||||
const allUsers = []
|
||||
const allUsersMap = new Map()
|
||||
users.forEach(item => {
|
||||
allUsers.push({ [item.nickname]: item.uid })
|
||||
allUsersMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.allUsers = allUsers
|
||||
this.allUsersMap = allUsersMap
|
||||
this.triggerTableAttrList[2].choice_value = this.allUsers
|
||||
},
|
||||
async getTriggers(app_id) {
|
||||
if (!app_id) {
|
||||
this.triggerTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
const res = await getTriggers({ app_id: app_id })
|
||||
const allTriggers = []
|
||||
const allTriggersMap = new Map()
|
||||
res.forEach(item => {
|
||||
allTriggers.push({ [item.name]: item.id })
|
||||
allTriggersMap.set(item.id, item.name)
|
||||
})
|
||||
this.allTriggers = allTriggers
|
||||
this.allTriggersMap = allTriggersMap
|
||||
this.triggerTableAttrList[3].choice_value = this.allTriggers
|
||||
},
|
||||
|
||||
// pager相关
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
|
||||
// searchForm相关
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
async searchFormChange(queryParams) {
|
||||
if (this.app_id !== queryParams.app_id) {
|
||||
this.app_id = queryParams.app_id
|
||||
await this.getTriggers(this.app_id)
|
||||
}
|
||||
if (queryParams.app_id === undefined) {
|
||||
this.app_id = undefined
|
||||
this.$refs.child.queryParams.trigger_id = undefined
|
||||
}
|
||||
},
|
||||
|
||||
handleChangeDescription(item, operate_type, id2resource_types, id2roles) {
|
||||
switch (operate_type) {
|
||||
// create
|
||||
case 'create': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => id2roles[i].name).join(',')
|
||||
const { name, resource_type_id, wildcard, permissions, enabled } = item.current
|
||||
item.changeDescription = `新增触发器:${name}\n资源类型:${id2resource_types[resource_type_id].name},资源名:${wildcard || ''},角色:[${newStr}]\n权限:${permissions}\n状态:${enabled}`
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
item.changeDescription = ''
|
||||
for (const key in item.origin) {
|
||||
const newVal = item.current[key]
|
||||
const oldVal = item.origin[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` 【 ${key} : 改为 ${newVal} 】 `
|
||||
item.changeDescription += str
|
||||
} else {
|
||||
const str = ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
item.changeDescription += ` 【 ${key} : 由 ${oldVal} 改为 ${newVal} 】 `
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const str = item.origin.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => id2roles[i].name).join(',')
|
||||
const { name, resource_type_id, wildcard, permissions, enabled } = item.origin
|
||||
item.changeDescription = `删除触发器:${name}\n资源类型:${id2resource_types[resource_type_id].name},资源名:${wildcard || ''},角色:[${newStr}]\n权限:${permissions}\n状态:${enabled}`
|
||||
break
|
||||
}
|
||||
case 'trigger_apply': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => id2roles[i].name).join(',')
|
||||
const { name, resource_type_id, wildcard, permissions, enabled } = item.current
|
||||
item.changeDescription = `应用触发器:${name}\n资源类型:${id2resource_types[resource_type_id].name},资源名:${wildcard || ''},角色:[${newStr}]\n权限:${permissions}\n状态:${enabled}`
|
||||
break
|
||||
}
|
||||
case 'trigger_cancel': {
|
||||
const str = item.current.roles
|
||||
const newArr = str.slice(1, str.length - 1).split(', ')
|
||||
const newStr = newArr.map(i => id2roles[i].name).join(',')
|
||||
const { name, resource_type_id, wildcard, permissions, enabled } = item.current
|
||||
item.changeDescription = `取消触发器:${name}\n资源类型:${id2resource_types[resource_type_id].name},资源名:${wildcard || ''},角色:[${newStr}]\n权限:${permissions}\n状态:${enabled}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
handleQueryParams(queryParams) {
|
||||
let q = ''
|
||||
for (const key in queryParams) {
|
||||
if (key !== 'page' && key !== 'page_size' && key !== 'app_id' && key !== 'start' && key !== 'end' && queryParams[key] !== undefined) {
|
||||
if (q) {
|
||||
q += `,${key}:${queryParams[key]}`
|
||||
} else {
|
||||
q += `${key}:${queryParams[key]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
const newQueryParams = { ...queryParams, q }
|
||||
return q ? newQueryParams : queryParams
|
||||
},
|
||||
handleTagColor(operateType) {
|
||||
return this.colorMap.get(operateType)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ant-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
</style>
|
||||
197
cmdb-ui/src/modules/acl/views/resource_types.vue
Normal file
197
cmdb-ui/src/modules/acl/views/resource_types.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div :style="{ backgroundColor: '#fff', padding: '24px' }">
|
||||
<div class="resource-types-action-btn">
|
||||
<a-button @click="handleCreate" type="primary" style="margin-right: 0.3rem;">{{ btnName }}</a-button>
|
||||
<a-input-search
|
||||
:style="{ display: 'inline', marginLeft: '10px', width: '200px' }"
|
||||
placeholder="搜索 | 资源类型名"
|
||||
v-model="searchName"
|
||||
allowClear
|
||||
@search="
|
||||
() => {
|
||||
this.tablePage.currentPage = 1
|
||||
this.searchData()
|
||||
}
|
||||
"
|
||||
></a-input-search>
|
||||
</div>
|
||||
<a-spin
|
||||
:spinning="loading"
|
||||
><vxe-grid :columns="tableColumns" :data="groups" :max-height="`${windowHeight - 185}px`" highlight-hover-row>
|
||||
<template #id_default="{row}">
|
||||
<a-tag color="cyan" v-for="perm in id2perms[row.id]" :key="perm.id">{{ perm.name }}</a-tag>
|
||||
</template>
|
||||
<template #action_default="{row}">
|
||||
<a @click="handleEdit(row)"><a-icon type="edit"/></a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm title="确认删除?" @confirm="handleDelete(row)" okText="是" cancelText="否">
|
||||
<a style="color: red"><a-icon type="delete"/></a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
<template #pager>
|
||||
<vxe-pager
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
</vxe-pager>
|
||||
</template> </vxe-grid
|
||||
></a-spin>
|
||||
|
||||
<resourceTypeForm ref="resourceTypeForm" :handleOk="handleOk"> </resourceTypeForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import resourceTypeForm from './module/resourceTypeForm'
|
||||
import { deleteResourceTypeById, searchResourceType } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ResourceType',
|
||||
components: {
|
||||
resourceTypeForm,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
groups: [],
|
||||
id2perms: {},
|
||||
btnName: '新增资源类型',
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
tablePage: {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
},
|
||||
tableColumns: [
|
||||
{
|
||||
title: '资源类型名',
|
||||
field: 'name',
|
||||
minWidth: '175px',
|
||||
fixed: 'left',
|
||||
showOverflow: 'tooltip',
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
field: 'description',
|
||||
minWidth: '175px',
|
||||
},
|
||||
{
|
||||
title: '权限',
|
||||
field: 'id',
|
||||
minWidth: '300px',
|
||||
slots: {
|
||||
default: 'id_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
field: 'action',
|
||||
minWidth: '175px',
|
||||
slots: { default: 'action_default' },
|
||||
fixed: 'right',
|
||||
},
|
||||
],
|
||||
searchName: '',
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {},
|
||||
mounted() {
|
||||
this.searchData()
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function(newName, oldName) {
|
||||
this.tablePage = {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
}
|
||||
this.searchData()
|
||||
},
|
||||
searchName: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
if (!newVal) {
|
||||
this.tablePage.currentPage = 1
|
||||
this.searchData()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inject: ['reload'],
|
||||
|
||||
methods: {
|
||||
searchData() {
|
||||
const { currentPage, pageSize } = this.tablePage
|
||||
this.loading = true
|
||||
const param = {
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
page_size: pageSize,
|
||||
page: currentPage,
|
||||
q: this.searchName,
|
||||
}
|
||||
searchResourceType(param).then((res) => {
|
||||
this.tablePage = { ...this.tablePage, total: res.numfound, currentPage: res.page }
|
||||
this.groups = res.groups
|
||||
this.id2perms = res.id2perms
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleEdit(record) {
|
||||
var perms = []
|
||||
var permList = this.id2perms[record.id]
|
||||
if (permList) {
|
||||
for (var i = 0; i < permList.length; i++) {
|
||||
perms.push(permList[i].name)
|
||||
}
|
||||
}
|
||||
record.perms = perms
|
||||
this.$refs.resourceTypeForm.handleEdit(record)
|
||||
},
|
||||
handleDelete(record) {
|
||||
this.deleteResourceType(record.id)
|
||||
},
|
||||
handleOk() {
|
||||
this.searchData()
|
||||
},
|
||||
handleCreate() {
|
||||
this.$refs.resourceTypeForm.handleCreate()
|
||||
},
|
||||
deleteResourceType(id) {
|
||||
deleteResourceTypeById(id).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
this.handleOk()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.searchData()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.resource-types-action-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
378
cmdb-ui/src/modules/acl/views/resources.vue
Normal file
378
cmdb-ui/src/modules/acl/views/resources.vue
Normal file
@@ -0,0 +1,378 @@
|
||||
<template>
|
||||
<div :style="{ backgroundColor: '#fff', padding: '24px' }">
|
||||
<div v-if="allResourceTypes.length > 0">
|
||||
<a-tabs default-active-key="1" @change="loadCurrentType">
|
||||
<a-tab-pane v-for="rtype in allResourceTypes" :key="rtype.id" :tab="rtype.name"> </a-tab-pane>
|
||||
</a-tabs>
|
||||
<div class="resources-action-btn">
|
||||
<a-space>
|
||||
<a-button @click="handleCreate" type="primary">{{ btnName }}</a-button>
|
||||
<a-input-search
|
||||
placeholder="搜索 | 资源名"
|
||||
v-model="searchName"
|
||||
@search="
|
||||
() => {
|
||||
this.tablePage.currentPage = 1
|
||||
this.searchData()
|
||||
}
|
||||
"
|
||||
></a-input-search>
|
||||
<a-dropdown v-if="selectedRows && selectedRows.length">
|
||||
<a-button type="primary" ghost>
|
||||
批量操作
|
||||
</a-button>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item @click="handleBatchPerm">
|
||||
<a href="javascript:;">授权</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleBatchRevoke">
|
||||
<a href="javascript:;">权限回收</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
|
||||
<span
|
||||
v-if="selectedRows && selectedRows.length"
|
||||
>已选择<strong>{{ selectedRows.length }}</strong
|
||||
>项</span
|
||||
>
|
||||
</a-space>
|
||||
|
||||
<a-space>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
@click="
|
||||
() => {
|
||||
$refs.resourceBatchPerm.open(currentType.id)
|
||||
}
|
||||
"
|
||||
>便捷授权</a-button
|
||||
>
|
||||
<a-switch
|
||||
v-model="isGroup"
|
||||
@change="
|
||||
() => {
|
||||
searchName = ''
|
||||
tablePage.currentPage = 1
|
||||
searchData()
|
||||
selectedRows = []
|
||||
$refs.xTable && $refs.xTable.clearCheckboxRow()
|
||||
$refs.xTable && $refs.xTable.clearCheckboxReserve()
|
||||
}
|
||||
"
|
||||
un-checked-children="组"
|
||||
></a-switch>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-spin
|
||||
:spinning="loading"
|
||||
><vxe-grid
|
||||
:columns="tableColumns"
|
||||
:data="tableData"
|
||||
highlight-hover-row
|
||||
:max-height="`${windowHeight - 245}px`"
|
||||
:checkbox-config="{ reserve: true }"
|
||||
@checkbox-change="changeCheckbox"
|
||||
@checkbox-all="changeCheckbox"
|
||||
ref="xTable"
|
||||
row-id="id"
|
||||
show-overflow
|
||||
>
|
||||
<template #name_header>
|
||||
{{ isGroup ? '资源组名' : '资源名' }}
|
||||
</template>
|
||||
<template #action_default="{row}">
|
||||
<span v-show="isGroup">
|
||||
<a @click="handleDisplayMember(row)">成员</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="handleGroupEdit(row)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
</span>
|
||||
<a-tooltip title="查看授权">
|
||||
<a @click="handlePerm(row)"><a-icon type="eye"/></a>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" />
|
||||
<a-tooltip title="授权">
|
||||
<a :style="{ color: '#4bbb13' }" @click="handlePermManage(row)">
|
||||
<a-icon type="usergroup-add" />
|
||||
</a>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm title="确认删除?" @confirm="handleDelete(row)" @cancel="cancel" okText="是" cancelText="否">
|
||||
<a style="color: red"><a-icon type="delete"/></a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
<template #pager>
|
||||
<vxe-pager
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
</vxe-pager>
|
||||
</template> </vxe-grid
|
||||
></a-spin>
|
||||
</div>
|
||||
<div v-else style="text-align: center">
|
||||
<a-icon style="font-size:50px; margin-bottom: 20px; color: orange" type="info-circle" />
|
||||
<h3>暂无类型信息,请先添加资源类型!</h3>
|
||||
</div>
|
||||
<resourceForm ref="resourceForm" @fresh="handleOk"> </resourceForm>
|
||||
<resourcePermForm ref="resourcePermForm"> </resourcePermForm>
|
||||
<ResourcePermManageForm
|
||||
ref="resourcePermManageForm"
|
||||
:groupTypeMessage="currentType"
|
||||
@close="closePerm"
|
||||
></ResourcePermManageForm>
|
||||
<resource-group-modal ref="resourceGroupModal"></resource-group-modal>
|
||||
<resource-group-member ref="resourceGroupMember"></resource-group-member>
|
||||
<ResourceBatchPerm ref="resourceBatchPerm" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import resourceForm from './module/resourceForm'
|
||||
import resourcePermForm from './module/resourcePermForm'
|
||||
import ResourceGroupModal from './module/resourceGroupModal'
|
||||
import ResourceGroupMember from './module/resourceGroupMember'
|
||||
import ResourcePermManageForm from './module/resourcePermManageForm'
|
||||
import ResourceBatchPerm from './module/resourceBatchPerm.vue'
|
||||
import {
|
||||
deleteResourceById,
|
||||
searchResource,
|
||||
searchResourceType,
|
||||
getResourceGroups,
|
||||
deleteResourceGroup,
|
||||
} from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'Resources',
|
||||
components: {
|
||||
resourceForm,
|
||||
resourcePermForm,
|
||||
ResourceGroupModal,
|
||||
ResourceGroupMember,
|
||||
ResourcePermManageForm,
|
||||
ResourceBatchPerm,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
tablePage: {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
},
|
||||
tableData: [],
|
||||
tableColumns: [
|
||||
{
|
||||
type: 'checkbox',
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: '资源名',
|
||||
field: 'name',
|
||||
minWidth: '150px',
|
||||
showOverflow: 'tooltip',
|
||||
fixed: 'left',
|
||||
slots: {
|
||||
header: 'name_header',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建者',
|
||||
minWidth: '100px',
|
||||
field: 'user',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
minWidth: '220px',
|
||||
field: 'created_at',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '最后修改时间',
|
||||
minWidth: '220px',
|
||||
field: 'updated_at',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
field: 'action',
|
||||
width: '200px',
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
slots: {
|
||||
default: 'action_default',
|
||||
},
|
||||
},
|
||||
],
|
||||
btnName: '新增资源',
|
||||
isGroup: false,
|
||||
allResourceTypes: [],
|
||||
currentType: { id: 0 },
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
searchName: '',
|
||||
selectedRows: [],
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
computed: mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
mounted() {
|
||||
// this.getAllResourceTypes()
|
||||
},
|
||||
inject: ['reload'],
|
||||
beforeRouteEnter(to, from, next) {
|
||||
next((vm) => {
|
||||
vm.getAllResourceTypes()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
async searchData() {
|
||||
const { currentPage, pageSize } = this.tablePage
|
||||
const param = {
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
resource_type_id: this.currentType.id,
|
||||
page_size: pageSize,
|
||||
page: currentPage,
|
||||
q: this.searchName,
|
||||
}
|
||||
this.loading = true
|
||||
if (!this.isGroup) {
|
||||
await searchResource(param).then((res) => {
|
||||
this.tablePage = { ...this.tablePage, total: res.numfound, currentPage: res.page }
|
||||
this.tableData = res.resources
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
await getResourceGroups(param).then((res) => {
|
||||
this.tablePage = { ...this.tablePage, total: res.numfound, currentPage: res.page }
|
||||
this.tableData = res.groups
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
handleDisplayMember(record) {
|
||||
this.$refs['resourceGroupMember'].handleEdit(record)
|
||||
},
|
||||
handleGroupEdit(record) {
|
||||
this.$refs['resourceGroupModal'].handleEdit(record)
|
||||
},
|
||||
async getAllResourceTypes() {
|
||||
await searchResourceType({ page_size: 9999, app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.allResourceTypes = res.groups
|
||||
if (res.groups && res.groups.length) {
|
||||
this.loadCurrentType(res.groups[0].id)
|
||||
}
|
||||
})
|
||||
},
|
||||
handlePermManage(record) {
|
||||
this.$refs['resourcePermManageForm'].editPerm(record, this.isGroup) // todo fix
|
||||
},
|
||||
loadCurrentType(rtypeId) {
|
||||
this.searchName = ''
|
||||
this.selectedRows = []
|
||||
this.tablePage.currentPage = 1
|
||||
this.$refs.xTable && this.$refs.xTable.clearCheckboxRow()
|
||||
this.$refs.xTable && this.$refs.xTable.clearCheckboxReserve()
|
||||
if (rtypeId) {
|
||||
this.currentType = this.allResourceTypes.find((item) => item.id === rtypeId)
|
||||
}
|
||||
this.searchData()
|
||||
},
|
||||
handlePerm(record) {
|
||||
this.$refs.resourcePermForm.handlePerm(record, this.isGroup)
|
||||
},
|
||||
handleDelete(record) {
|
||||
this.deleteResource(record.id)
|
||||
},
|
||||
handleOk() {
|
||||
this.tablePage.currentPage = 1
|
||||
this.searchData()
|
||||
},
|
||||
handleCreate() {
|
||||
this.$refs.resourceForm.handleCreate(this.currentType)
|
||||
},
|
||||
deleteResource(id) {
|
||||
if (!this.isGroup) {
|
||||
deleteResourceById(id).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
this.handleOk()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
} else {
|
||||
deleteResourceGroup(id).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
this.handleOk()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
}
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
cancel() {},
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.searchData()
|
||||
},
|
||||
changeCheckbox({ records }) {
|
||||
// console.log(records)
|
||||
this.selectedRows = this.$refs.xTable.getCheckboxRecords().concat(this.$refs.xTable.getCheckboxReserveRecords())
|
||||
},
|
||||
handleBatchPerm() {
|
||||
this.$refs['resourcePermManageForm'].editPerm(this.selectedRows, this.isGroup)
|
||||
},
|
||||
handleBatchRevoke() {
|
||||
this.$refs['resourcePermManageForm'].editPerm(this.selectedRows, this.isGroup, 'revoke')
|
||||
},
|
||||
closePerm() {
|
||||
this.$refs.xTable.clearCheckboxRow()
|
||||
this.selectedRows = []
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function(newName, oldName) {
|
||||
this.isGroup = false
|
||||
this.tablePage = {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
}
|
||||
},
|
||||
searchName: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (!newVal) {
|
||||
this.tablePage.currentPage = 1
|
||||
this.searchData()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.resources-action-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.ant-switch {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
311
cmdb-ui/src/modules/acl/views/roles.vue
Normal file
311
cmdb-ui/src/modules/acl/views/roles.vue
Normal file
@@ -0,0 +1,311 @@
|
||||
<template>
|
||||
<div class="acl-role">
|
||||
<div class="roles-action-btn">
|
||||
<a-button @click="handleCreate" type="primary">{{ btnName }}</a-button>
|
||||
<a-input-search
|
||||
allowClear
|
||||
:style="{ display: 'inline', marginLeft: '10px', width: '200px' }"
|
||||
placeholder="搜索 | 角色名"
|
||||
v-model="searchName"
|
||||
@search="
|
||||
() => {
|
||||
this.tablePage.currentPage = 1
|
||||
this.loadData()
|
||||
}
|
||||
"
|
||||
></a-input-search>
|
||||
<a-checkbox :checked="is_all" @click="handleClickBoxChange">所有角色</a-checkbox>
|
||||
</div>
|
||||
<a-spin :spinning="loading">
|
||||
<vxe-grid
|
||||
:columns="tableColumns"
|
||||
:data="tableData"
|
||||
:max-height="`${windowHeight - 185}px`"
|
||||
highlight-hover-row
|
||||
min-height="300px"
|
||||
:filter-config="{ remote: true }"
|
||||
@filter-change="filterTableMethod"
|
||||
>
|
||||
<template #is_app_admin_default="{row}">
|
||||
<a-icon type="check" v-if="row.is_app_admin" />
|
||||
</template>
|
||||
<template #inherit_default="{row}">
|
||||
<a-tag color="cyan" v-for="role in id2parents[row.id]" :key="role.id">{{ role.name }}</a-tag>
|
||||
</template>
|
||||
<template #isVisualRole_default="{row}">
|
||||
{{ row.uid ? '否' : '是' }}
|
||||
</template>
|
||||
<template #action_default="{row}">
|
||||
<div style="width:300px">
|
||||
<span>
|
||||
<a-tooltip title="资源列表">
|
||||
<a
|
||||
v-if="$route.name !== 'acl_roles'"
|
||||
@click="handleDisplayUserResource(row)"
|
||||
><a-icon
|
||||
type="file-search"
|
||||
/></a>
|
||||
</a-tooltip>
|
||||
<a-divider type="vertical" />
|
||||
<div v-if="!row.uid" style="display: inline-block">
|
||||
<a-tooltip
|
||||
title="用户列表"
|
||||
><a @click="handleDisplayUserUnderRole(row)"><a-icon type="team"/></a
|
||||
></a-tooltip>
|
||||
<a-divider type="vertical" />
|
||||
</div>
|
||||
<a @click="handleEdit(row)"><a-icon type="edit"/></a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm title="确认删除?" @confirm="handleDelete(row)" @cancel="cancel" okText="是" cancelText="否">
|
||||
<a style="color: red"><a-icon type="delete"/></a>
|
||||
</a-popconfirm>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #pager>
|
||||
<vxe-pager
|
||||
:layouts="['Total', 'PrevPage', 'JumpNumber', 'NextPage', 'Sizes']"
|
||||
:current-page.sync="tablePage.currentPage"
|
||||
:page-size.sync="tablePage.pageSize"
|
||||
:total="tablePage.total"
|
||||
:page-sizes="pageSizeOptions"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
</vxe-pager>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</a-spin>
|
||||
|
||||
<roleForm ref="roleForm" :allRoles="allRoles" :id2parents="id2parents" :handleOk="handleOk"></roleForm>
|
||||
<resource-user-form ref="ruf"></resource-user-form>
|
||||
<users-under-role-form ref="usersUnderRoleForm" :allRoles="allRoles"></users-under-role-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import roleForm from './module/roleForm'
|
||||
import resourceUserForm from './module/resourceUserForm'
|
||||
import usersUnderRoleForm from './module/usersUnderRoleForm'
|
||||
import { deleteRoleById, searchRole } from '@/modules/acl/api/role'
|
||||
|
||||
export default {
|
||||
name: 'Roles',
|
||||
components: {
|
||||
roleForm,
|
||||
resourceUserForm,
|
||||
usersUnderRoleForm,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
tablePage: {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
},
|
||||
tableColumns: [
|
||||
{
|
||||
title: '角色名',
|
||||
field: 'name',
|
||||
sortable: true,
|
||||
minWidth: '150px',
|
||||
fixed: 'left',
|
||||
showOverflow: 'tooltip',
|
||||
},
|
||||
{
|
||||
title: '管理员',
|
||||
field: 'is_app_admin',
|
||||
minWidth: '100px',
|
||||
align: 'center',
|
||||
slots: { default: 'is_app_admin_default' },
|
||||
},
|
||||
{
|
||||
title: '继承自',
|
||||
field: 'id',
|
||||
minWidth: '150px',
|
||||
slots: { default: 'inherit_default' },
|
||||
},
|
||||
{
|
||||
title: '虚拟角色',
|
||||
field: 'uid',
|
||||
minWidth: '100px',
|
||||
align: 'center',
|
||||
filters: [
|
||||
{ label: '是', value: 1 },
|
||||
{ label: '否', value: 0 },
|
||||
],
|
||||
filterMultiple: false,
|
||||
filterMethod: ({ value, row }) => {
|
||||
return value === !row.uid
|
||||
},
|
||||
slots: { default: 'isVisualRole_default' },
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
minWidth: '280px',
|
||||
field: 'action',
|
||||
fixed: 'right',
|
||||
slots: { default: 'action_default' },
|
||||
},
|
||||
],
|
||||
btnName: '新增虚拟角色',
|
||||
is_all: this.$route.name === 'acl_roles',
|
||||
tableData: [],
|
||||
allRoles: [],
|
||||
id2parents: {},
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
searchName: '',
|
||||
filterTableValue: { user_role: 1, user_only: 0 },
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function(newName, oldName) {
|
||||
this.tablePage = {
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
}
|
||||
this.tableData = []
|
||||
this.allRoles = []
|
||||
this.loadData()
|
||||
this.initData()
|
||||
},
|
||||
searchName: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (!newVal) {
|
||||
this.tablePage.currentPage = 1
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initData()
|
||||
},
|
||||
inject: ['reload'],
|
||||
methods: {
|
||||
handleClickBoxChange() {
|
||||
this.is_all = !this.is_all
|
||||
this.$nextTick(() => {
|
||||
this.tablePage.currentPage = 1
|
||||
this.loadData()
|
||||
})
|
||||
},
|
||||
initData() {
|
||||
console.log(111)
|
||||
searchRole({
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
page_size: 9999,
|
||||
}).then((res) => {
|
||||
this.allRoles = res.roles
|
||||
})
|
||||
},
|
||||
loadData() {
|
||||
console.log(222)
|
||||
this.loading = true
|
||||
const { currentPage, pageSize } = this.tablePage
|
||||
searchRole({
|
||||
...this.filterTableValue,
|
||||
app_id: this.$route.name.split('_')[0],
|
||||
page_size: pageSize,
|
||||
page: currentPage,
|
||||
is_all: this.is_all,
|
||||
q: this.searchName,
|
||||
})
|
||||
.then((res) => {
|
||||
this.tableData = res.roles
|
||||
this.id2parents = res.id2parents
|
||||
this.tablePage.total = res.numfound
|
||||
this.loading = false
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleDisplayUserResource(record) {
|
||||
this.$refs.ruf.loadUserResource(record)
|
||||
},
|
||||
handleDisplayUserUnderRole(record) {
|
||||
this.$refs.usersUnderRoleForm.handleProcessRole(record.id)
|
||||
},
|
||||
handleEdit(record) {
|
||||
this.$refs.roleForm.handleEdit(record)
|
||||
},
|
||||
handleDelete(record) {
|
||||
this.deleteRole(record.id)
|
||||
},
|
||||
handleOk() {
|
||||
this.loadData()
|
||||
this.initData()
|
||||
},
|
||||
|
||||
handleCreate() {
|
||||
this.$refs.roleForm.handleCreate()
|
||||
},
|
||||
|
||||
deleteRole(id) {
|
||||
deleteRoleById(id, { app_id: this.$route.name.split('_')[0] }).then((res) => {
|
||||
this.$message.success(`删除成功`)
|
||||
this.handleOk()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
cancel(e) {
|
||||
return false
|
||||
},
|
||||
handlePageChange({ currentPage, pageSize }) {
|
||||
this.tablePage.currentPage = currentPage
|
||||
this.tablePage.pageSize = pageSize
|
||||
this.loadData()
|
||||
},
|
||||
filterTableMethod({ column, property, values, datas, filterList, $event }) {
|
||||
if (property === 'uid') {
|
||||
if (values[0] === 1) {
|
||||
this.filterTableValue = { user_role: 0 }
|
||||
} else if (values[0] === 0) {
|
||||
this.filterTableValue = {
|
||||
user_only: 1,
|
||||
}
|
||||
} else {
|
||||
this.filterTableValue = {
|
||||
user_role: 1,
|
||||
user_only: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
this.tablePage.currentPage = 1
|
||||
this.loadData()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.acl-role {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
.roles-action-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
.ant-checkbox-wrapper {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
.vxe-table--filter-body {
|
||||
min-height: 60px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
98
cmdb-ui/src/modules/acl/views/secretKey.vue
Normal file
98
cmdb-ui/src/modules/acl/views/secretKey.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="acl-secret-key">
|
||||
<a-form-model
|
||||
ref="secretKeyForm"
|
||||
:model="displayForm"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 12 }"
|
||||
>
|
||||
<a-form-model-item label="Key" prop="key">
|
||||
<a-input disabled v-model="displayForm.key" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="Secret" prop="secret">
|
||||
<a-input disabled v-model="displayForm.secret" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label=" " :colon="false">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="changeVisible">{{ !visible ? '查看' : '隐藏' }}</a-button>
|
||||
<a-button type="danger" ghost @click="handleSumbit">重置</a-button>
|
||||
<!-- <a-button @click="handleCancel">取消</a-button> -->
|
||||
</a-space>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getSecret, updateSecret } from '../api/secretKey'
|
||||
export default {
|
||||
name: 'SecretKey',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
key: '',
|
||||
secret: '',
|
||||
},
|
||||
rules: {
|
||||
key: [{ required: true, message: 'key is required' }],
|
||||
secret: [{ required: true, message: 'secret is required' }],
|
||||
},
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayForm() {
|
||||
return {
|
||||
key: this.visible ? this.form.key : this.form.key.replace(/./g, '*'),
|
||||
secret: this.visible ? this.form.secret : this.form.secret.replace(/./g, '*'),
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getOriginSecret()
|
||||
},
|
||||
methods: {
|
||||
getOriginSecret() {
|
||||
getSecret().then((res) => {
|
||||
const { key, secret } = res
|
||||
this.form = { key, secret }
|
||||
})
|
||||
},
|
||||
handleSumbit() {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '重置',
|
||||
content: '确定重置用户密钥?',
|
||||
onOk() {
|
||||
that.$refs.secretKeyForm.validate((valid) => {
|
||||
if (valid) {
|
||||
updateSecret().then((res) => {
|
||||
that.$message.success('重置成功')
|
||||
const { key, secret } = res
|
||||
that.form = { key, secret }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
handleCancel() {
|
||||
this.getOriginSecret()
|
||||
},
|
||||
changeVisible() {
|
||||
this.visible = !this.visible
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.acl-secret-key {
|
||||
background-color: #fff;
|
||||
padding: 24px;
|
||||
.ant-input[disabled] {
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
318
cmdb-ui/src/modules/acl/views/trigger.vue
Normal file
318
cmdb-ui/src/modules/acl/views/trigger.vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<div :style="{ backgroundColor: '#fff', padding: '24px' }">
|
||||
<div class="trigger-action-btn">
|
||||
<a-button type="primary" @click="handleCreateTrigger">新增触发器</a-button>
|
||||
<a-input-search
|
||||
:style="{ display: 'inline', marginLeft: '10px', width: '200px' }"
|
||||
placeholder="搜索 | 名称"
|
||||
v-model="searchName"
|
||||
allowClear
|
||||
@search="filter"
|
||||
></a-input-search>
|
||||
</div>
|
||||
|
||||
<vxe-grid
|
||||
:columns="tableColumns"
|
||||
:data="filterTriggers"
|
||||
:max-height="`${windowHeight - 185}px`"
|
||||
highlight-hover-row
|
||||
>
|
||||
<template #wildcard_default="{row}">
|
||||
<div style="word-break: break-word">
|
||||
<span>{{ row.wildcard }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #resourceTypeRender_default="{row}">
|
||||
{{
|
||||
resourceTypeList.filter((item) => item.id === row.resource_type_id)[0]
|
||||
? resourceTypeList.filter((item) => item.id === row.resource_type_id)[0].name
|
||||
: 'unkown'
|
||||
}}
|
||||
</template>
|
||||
<template #users_default="{row}">
|
||||
<span
|
||||
v-for="(u, index) in row.users"
|
||||
:key="index"
|
||||
>{{ u }}<a-divider
|
||||
v-if="index < row.users.length - 1"
|
||||
type="vertical"
|
||||
/></span>
|
||||
</template>
|
||||
<template #permissions_default="{row}">
|
||||
<a-tag v-for="(p, index) in row.permissions" :key="index">{{ p }}</a-tag>
|
||||
</template>
|
||||
<template #enabled_default="{row}">
|
||||
<a-tag v-if="row.enabled" color="#2db7f5">启用</a-tag>
|
||||
<a-tag v-else color="grey">禁用</a-tag>
|
||||
</template>
|
||||
<template #action_default="{row}">
|
||||
<a-space>
|
||||
<a-tooltip title="应用">
|
||||
<a @click="handleApplyTrigger(row)" :style="{ color: '#0f9d58' }"><a-icon type="appstore"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="取消">
|
||||
<a @click="handleCancelTrigger(row)" :style="{ color: 'orange' }"><a-icon type="stop"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="查看正则匹配结果">
|
||||
<a @click="handlePattern(row)" :style="{ color: 'purple' }"><a-icon type="eye"/></a>
|
||||
</a-tooltip>
|
||||
<a @click="handleEditTrigger(row)"><a-icon type="edit"/></a>
|
||||
<a @click="handleDeleteTrigger(row)" :style="{ color: 'red' }"><a-icon type="delete"/></a>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
<trigger-form
|
||||
ref="triggerForm"
|
||||
:roles="roles"
|
||||
:resourceTypeList="resourceTypeList"
|
||||
:id2perms="id2perms"
|
||||
@refresh="loadTriggers"
|
||||
:app_id="app_id"
|
||||
></trigger-form>
|
||||
<TriggerPattern ref="triggerPattern" :roles="roles" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import TriggerForm from './module/triggerForm'
|
||||
import TriggerPattern from './module/triggerPattern'
|
||||
import { getTriggers, deleteTrigger, applyTrigger, cancelTrigger } from '@/modules/acl/api/trigger'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
|
||||
export default {
|
||||
name: 'ACLTrigger',
|
||||
components: { TriggerForm, TriggerPattern },
|
||||
data() {
|
||||
return {
|
||||
roles: [],
|
||||
searchName: '',
|
||||
resourceTypeList: [],
|
||||
filterTriggers: [],
|
||||
triggers: [],
|
||||
id2parents: [],
|
||||
id2perms: {},
|
||||
tableColumns: [
|
||||
{
|
||||
title: '名称',
|
||||
field: 'name',
|
||||
sortable: true,
|
||||
minWidth: '150px',
|
||||
fixed: 'left',
|
||||
showOverflow: 'tooltip',
|
||||
},
|
||||
{
|
||||
title: '资源名',
|
||||
field: 'wildcard',
|
||||
minWidth: '250px',
|
||||
showOverflow: 'tooltip',
|
||||
slots: {
|
||||
default: 'wildcard_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '资源类型',
|
||||
field: 'resource_type_id',
|
||||
minWidth: '100px',
|
||||
slots: {
|
||||
default: 'resourceTypeRender_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
field: 'users',
|
||||
minWidth: '150px',
|
||||
showOverflow: 'tooltip',
|
||||
slots: {
|
||||
default: 'users_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
field: 'roles',
|
||||
minWidth: '150px',
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
const roleNameList = row.roles.map((item) => {
|
||||
const temp = this.roles.find((a) => a.id === item)
|
||||
if (temp) {
|
||||
return temp.name
|
||||
}
|
||||
return 'unknown'
|
||||
})
|
||||
return [
|
||||
<div>
|
||||
{roleNameList.length <= 1 && roleNameList.map((item) => <span key={item}>{item}</span>)}
|
||||
{roleNameList.length > 1 && (
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{roleNameList.map((item, idx) => (
|
||||
<span>
|
||||
<span key={item}>{item}</span>
|
||||
{idx !== roleNameList.length - 1 && <a-divider type="vertical" />}
|
||||
</span>
|
||||
))}
|
||||
</template>
|
||||
<span>{roleNameList[0]}...</span>
|
||||
</a-tooltip>
|
||||
)}
|
||||
</div>,
|
||||
]
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '权限',
|
||||
field: 'permissions',
|
||||
minWidth: '250px',
|
||||
slots: {
|
||||
default: 'permissions_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
field: 'enabled',
|
||||
minWidth: '100px',
|
||||
slots: {
|
||||
default: 'enabled_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
field: 'action',
|
||||
minWidth: '200px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'action_default',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadRoles()
|
||||
this.loadResourceTypeList()
|
||||
},
|
||||
beforeMount() {
|
||||
this.loadTriggers()
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: (state) => state.windowHeight,
|
||||
}),
|
||||
app_id() {
|
||||
return this.$route.name.split('_')[0]
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
loadTriggers() {
|
||||
this.searchName = ''
|
||||
getTriggers({ app_id: this.app_id }).then((res) => {
|
||||
this.triggers = res
|
||||
this.filterTriggers = res
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
loadRoles() {
|
||||
searchRole({ app_id: this.app_id, page_size: 9999 }).then((res) => {
|
||||
this.roles = res.roles
|
||||
this.id2parents = res.id2parents
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
loadResourceTypeList() {
|
||||
searchResourceType({ app_id: this.app_id, page_size: 9999 }).then((res) => {
|
||||
this.resourceTypeList = res['groups']
|
||||
this.id2perms = res['id2perms']
|
||||
})
|
||||
// .catch(err => this.$httpError(err))
|
||||
},
|
||||
handleCreateTrigger() {
|
||||
this.$refs.triggerForm.handleEdit(null)
|
||||
},
|
||||
handleDeleteTrigger(record) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '删除',
|
||||
content: '确认删除该触发器吗?',
|
||||
onOk() {
|
||||
deleteTrigger(record.id).then((res) => {
|
||||
that.$message.success('删除成功')
|
||||
that.loadTriggers()
|
||||
})
|
||||
// .catch(err => that.$httpError(err))
|
||||
},
|
||||
})
|
||||
},
|
||||
handleApplyTrigger(record) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '规则应用',
|
||||
content: '是否确定应用该触发器?',
|
||||
onOk() {
|
||||
applyTrigger(record.id).then((res) => {
|
||||
that.$message.success('提交成功!')
|
||||
})
|
||||
// .catch(err => that.$httpError(err))
|
||||
},
|
||||
})
|
||||
},
|
||||
handleCancelTrigger(record) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '规则应用',
|
||||
content: '是否取消应用该触发器?',
|
||||
onOk() {
|
||||
cancelTrigger(record.id).then((res) => {
|
||||
that.$message.success('提交成功!')
|
||||
})
|
||||
// .catch(err => that.$httpError(err))
|
||||
},
|
||||
})
|
||||
},
|
||||
handleEditTrigger(record) {
|
||||
this.$refs.triggerForm.handleEdit(record)
|
||||
},
|
||||
filter() {
|
||||
if (this.searchName) {
|
||||
this.filterTriggers = this.triggers.filter((item) =>
|
||||
item.name.toLowerCase().includes(this.searchName.toLowerCase())
|
||||
)
|
||||
} else {
|
||||
this.filterTriggers = this.triggers
|
||||
}
|
||||
},
|
||||
handlePattern(row) {
|
||||
const { wildcard, uid, resource_type_id } = row
|
||||
this.$refs.triggerPattern.open({
|
||||
resource_type_id,
|
||||
app_id: this.app_id,
|
||||
owner: uid,
|
||||
pattern: wildcard,
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$route.name': function(oldName, newName) {
|
||||
this.loadTriggers()
|
||||
},
|
||||
searchName: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (!newVal) {
|
||||
this.filter()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.trigger-action-btn {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
181
cmdb-ui/src/modules/acl/views/users.vue
Normal file
181
cmdb-ui/src/modules/acl/views/users.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<div class="user-action-btn">
|
||||
<a-button v-if="isAclAdmin" @click="handleCreate" type="primary">{{ btnName }}</a-button>
|
||||
<a-input-search
|
||||
allowClear
|
||||
:style="{ display: 'inline', marginLeft: '10px' }"
|
||||
placeholder="搜索 | 用户名、中文名"
|
||||
v-model="searchName"
|
||||
></a-input-search>
|
||||
</div>
|
||||
<a-spin :spinning="loading">
|
||||
<vxe-grid :columns="tableColumns" :data="tableData" highlight-hover-row :max-height="`${windowHeight - 185}px`">
|
||||
<template #block_default="{row}">
|
||||
<a-icon type="lock" v-if="row.block" />
|
||||
</template>
|
||||
<template #action_default="{row}">
|
||||
<template>
|
||||
<a :disabled="isAclAdmin ? false : true" @click="handleEdit(row)">
|
||||
<a-icon type="edit" />
|
||||
</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-tooltip title="权限汇总">
|
||||
<a @click="handlePermCollect(row)"><a-icon type="solution"/></a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</a-spin>
|
||||
<userForm ref="userForm" :handleOk="handleOk"> </userForm>
|
||||
<perm-collect-form ref="permCollectForm"></perm-collect-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import userForm from './module/userForm'
|
||||
import PermCollectForm from './module/permCollectForm'
|
||||
import { deleteUserById, searchUser, getOnDutyUser } from '@/modules/acl/api/user'
|
||||
|
||||
export default {
|
||||
name: 'Users',
|
||||
components: {
|
||||
userForm,
|
||||
PermCollectForm,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
tableColumns: [
|
||||
{
|
||||
title: '用户名',
|
||||
field: 'username',
|
||||
sortable: true,
|
||||
minWidth: '100px',
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: '中文名',
|
||||
field: 'nickname',
|
||||
minWidth: '100px',
|
||||
},
|
||||
{
|
||||
title: '加入时间',
|
||||
field: 'date_joined',
|
||||
minWidth: '160px',
|
||||
},
|
||||
{
|
||||
title: '锁定',
|
||||
field: 'block',
|
||||
minWidth: '100px',
|
||||
slots: {
|
||||
default: 'block_default',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
field: 'action',
|
||||
minWidth: '180px',
|
||||
fixed: 'right',
|
||||
slots: {
|
||||
default: 'action_default',
|
||||
},
|
||||
},
|
||||
],
|
||||
onDutuUids: [],
|
||||
btnName: '新增用户',
|
||||
allUsers: [],
|
||||
tableData: [],
|
||||
searchName: '',
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
async beforeMount() {
|
||||
this.loading = true
|
||||
await getOnDutyUser().then(res => {
|
||||
this.onDutuUids = res.map(i => i.uid)
|
||||
this.search()
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
windowHeight: state => state.windowHeight,
|
||||
}),
|
||||
isAclAdmin: function() {
|
||||
if (this.$store.state.user.roles.permissions.filter(item => item === 'acl_admin').length > 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
inject: ['reload'],
|
||||
|
||||
methods: {
|
||||
search() {
|
||||
searchUser({ page_size: 10000 }).then(res => {
|
||||
const ret = res.users.filter(u => this.onDutuUids.includes(u.uid))
|
||||
this.allUsers = ret
|
||||
this.tableData = ret
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handlePermCollect(record) {
|
||||
this.$refs['permCollectForm'].collect(record)
|
||||
},
|
||||
handleEdit(record) {
|
||||
this.$refs.userForm.handleEdit(record)
|
||||
},
|
||||
handleDelete(record) {
|
||||
this.deleteUser(record.uid)
|
||||
},
|
||||
handleOk() {
|
||||
this.searchName = ''
|
||||
this.search()
|
||||
},
|
||||
|
||||
handleCreate() {
|
||||
this.$refs.userForm.handleCreate()
|
||||
},
|
||||
|
||||
deleteUser(attrId) {
|
||||
deleteUserById(attrId).then(res => {
|
||||
this.$message.success(`删除成功`)
|
||||
this.handleOk()
|
||||
})
|
||||
// .catch(err => this.requestFailed(err))
|
||||
},
|
||||
// requestFailed(err) {
|
||||
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
|
||||
// this.$message.error(`${msg}`)
|
||||
// },
|
||||
},
|
||||
watch: {
|
||||
searchName: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
if (newVal) {
|
||||
this.tableData = this.allUsers.filter(
|
||||
item =>
|
||||
item.username.toLowerCase().includes(newVal.toLowerCase()) ||
|
||||
item.nickname.toLowerCase().includes(newVal.toLowerCase())
|
||||
)
|
||||
} else {
|
||||
this.tableData = this.allUsers
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-action-btn {
|
||||
display: inline-flex;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div :style="{'margin-topd':(graphSetting.viewSize.height-height)+'px', height: height+'px', 'margin-top': -height+'px' }" class="c-rg-bottom-panel">
|
||||
<slot name="bottomPanel" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import SeeksRGStore from './core4vue/SeeksRGStore'
|
||||
// import { mapState } from 'vuex'
|
||||
// var _parent = this.$parent
|
||||
// console.log('GraphSettingPanel.vue:', _parent)
|
||||
export default {
|
||||
name: 'GraphBottomPanel',
|
||||
props: {
|
||||
graphSetting: {
|
||||
mustUseProp: true,
|
||||
default: () => { return {} },
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
height: 50,
|
||||
search_text: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.height = this.$slots['bottomPanel'][0].elm.offsetHeight
|
||||
if (window.SeeksGraphDebug) console.log('SeeksGraph bootomPanel height:', this.height)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.c-rg-bottom-panel{
|
||||
width:100%;
|
||||
margin-left:0px;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
padding:0px;
|
||||
overflow: hidden;
|
||||
border-radius: 0px;
|
||||
position: absolute;
|
||||
z-index: 99;
|
||||
opacity: 1;
|
||||
}
|
||||
.c-fixedLayout{
|
||||
position: fixed;
|
||||
bottom:0px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div :style="{'margin-left':(graphSetting.viewELSize.width-350)+'px'}" class="c-mini-namefilter">
|
||||
<!-- <el-autocomplete-->
|
||||
<!-- v-model="$parent.search_text"-->
|
||||
<!-- :fetch-suggestions="$parent.querySearchAsync"-->
|
||||
<!-- :trigger-on-focus="false"-->
|
||||
<!-- :label="'xxxx'"-->
|
||||
<!-- size="small"-->
|
||||
<!-- placeholder="图谱节点定位,请输入节点名称"-->
|
||||
<!-- clearable-->
|
||||
<!-- style="width: 320px;box-shadow: 0px 0px 8px #cccccc;"-->
|
||||
<!-- @select="$parent.handleSelect"-->
|
||||
<!-- >-->
|
||||
<!-- <template slot-scope="{ item }">-->
|
||||
<!-- <div class="c-suggestion-name">{{ item.text }}</div>-->
|
||||
<!-- </template>-->
|
||||
<!-- <el-button slot="append" style="color: #2E4E8F;" icon="el-icon-search" />-->
|
||||
<!-- </el-autocomplete>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'GraphMiniNameFilter',
|
||||
props: {
|
||||
graphSetting: {
|
||||
mustUseProp: true,
|
||||
default: () => { return {} },
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.c-mini-namefilter{
|
||||
height:60px;
|
||||
position: absolute;
|
||||
margin-top:10px;
|
||||
z-index: 999;
|
||||
}
|
||||
.c-fixedLayout{
|
||||
position: fixed;
|
||||
top:145px;
|
||||
}
|
||||
</style>
|
||||
377
cmdb-ui/src/modules/cmdb/3rd/relation-graph/GraphMiniToolBar.vue
Normal file
377
cmdb-ui/src/modules/cmdb/3rd/relation-graph/GraphMiniToolBar.vue
Normal file
@@ -0,0 +1,377 @@
|
||||
<template>
|
||||
<div
|
||||
:style="{
|
||||
'margin-left': graphSetting.viewELSize.width - 50 + 'px',
|
||||
'margin-top': (graphSetting.viewELSize.height - 260) / 2 + 'px',
|
||||
}"
|
||||
class="c-mini-toolbar"
|
||||
>
|
||||
<div class="c-mb-button" style="margin-top: 0px;" @click="graphSetting.fullscreen = !graphSetting.fullscreen">
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-resize-"></use></svg>
|
||||
<span class="c-mb-text">{{ graphSetting.fullscreen ? '退出' : '全屏' }}</span>
|
||||
</div>
|
||||
<div v-if="graphSetting.allowShowZoomMenu" class="c-mb-button" @click="$parent.zoom(20)">
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-fangda"></use></svg>
|
||||
<span class="c-mb-text">放大</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="graphSetting.allowShowZoomMenu"
|
||||
style="float:left;margin-top:0px;height:20px;width:40px;border-top:0px;border-bottom:0px;background-color: #ffffff;color: #262626;font-size: 10px;background-color: #efefef;text-align: center;line-height: 20px;"
|
||||
@click="printGraphJsonData"
|
||||
>
|
||||
{{ graphSetting.canvasZoom }}%
|
||||
</div>
|
||||
<!--<div style="float:left;margin-top:0px;height:20px;width:40px;border-top:0px;border-bottom:0px;background-color: #ffffff;color: #262626;font-size: 10px;background-color: #efefef;text-align: center;line-height: 20px;">{{ hits }}</div>-->
|
||||
<div v-if="graphSetting.allowShowZoomMenu" class="c-mb-button" style="margin-top:0px;" @click="$parent.zoom(-20)">
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-suoxiao"></use></svg>
|
||||
<span class="c-mb-text">缩小</span>
|
||||
</div>
|
||||
<div v-if="graphSetting.layouts.length > 1" class="c-mb-button">
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-yuanquanfenxiang"></use></svg>
|
||||
<span class="c-mb-text">布局</span>
|
||||
<div
|
||||
:style="{
|
||||
width: graphSetting.layouts.length * 70 + 6 + 'px',
|
||||
'margin-left': graphSetting.layouts.length * -70 - 5 + 'px',
|
||||
}"
|
||||
class="c-mb-child-panel"
|
||||
>
|
||||
<div
|
||||
v-for="thisLayoutSetting in graphSetting.layouts"
|
||||
:key="thisLayoutSetting.label"
|
||||
class="c-mb-button c-mb-button-c"
|
||||
:class="{ 'c-mb-button-on': graphSetting.layoutLabel === thisLayoutSetting.label }"
|
||||
style="width: 70px;"
|
||||
@click="switchLayout(thisLayoutSetting)"
|
||||
>
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-yuanquanfenxiang"></use></svg>
|
||||
<span class="c-mb-text">{{ thisLayoutSetting.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="graphSetting.allowSwitchLineShape" class="c-mb-button">
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-hj2"></use></svg>
|
||||
<span class="c-mb-text">线条</span>
|
||||
<div class="c-mb-child-panel" style="width:256px;margin-left:-255px;">
|
||||
<div
|
||||
:class="{ 'c-mb-button-on': graphSetting.defaultLineShape === 1 }"
|
||||
class="c-mb-button c-mb-button-c"
|
||||
style="width: 50px;"
|
||||
@click="graphSetting.defaultLineShape = 1"
|
||||
>
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-hj2"></use></svg>
|
||||
<span class="c-mb-text">直线</span>
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'c-mb-button-on': graphSetting.defaultLineShape === 2 }"
|
||||
class="c-mb-button c-mb-button-c"
|
||||
style="width: 50px;"
|
||||
@click="graphSetting.defaultLineShape = 2"
|
||||
>
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-lianjieliu"></use></svg>
|
||||
<span class="c-mb-text">简洁</span>
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'c-mb-button-on': graphSetting.defaultLineShape === 6 }"
|
||||
class="c-mb-button c-mb-button-c"
|
||||
style="width: 50px;"
|
||||
@click="graphSetting.defaultLineShape = 6"
|
||||
>
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-lianjieliu"></use></svg>
|
||||
<span class="c-mb-text">生动</span>
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'c-mb-button-on': graphSetting.defaultLineShape === 5 }"
|
||||
class="c-mb-button c-mb-button-c"
|
||||
style="width: 50px;"
|
||||
@click="graphSetting.defaultLineShape = 5"
|
||||
>
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-lianjieliu"></use></svg>
|
||||
<span class="c-mb-text">鱼尾</span>
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'c-mb-button-on': graphSetting.defaultLineShape === 4 }"
|
||||
class="c-mb-button c-mb-button-c"
|
||||
style="width: 50px;"
|
||||
@click="graphSetting.defaultLineShape = 4"
|
||||
>
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-hj2"></use></svg>
|
||||
<span class="c-mb-text">折线</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="graphSetting.allowSwitchJunctionPoint" class="c-mb-button">
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-lianjie_connecting5"></use></svg>
|
||||
<span class="c-mb-text">连接点</span>
|
||||
<div class="c-mb-child-panel" style="width:206px;margin-left:-205px;">
|
||||
<div
|
||||
:class="{ 'c-mb-button-on': graphSetting.defaultJunctionPoint === 'border' }"
|
||||
class="c-mb-button c-mb-button-c"
|
||||
style="width: 50px;"
|
||||
@click="graphSetting.defaultJunctionPoint = 'border'"
|
||||
>
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-lianjie_connecting5"></use></svg>
|
||||
<span class="c-mb-text">边缘</span>
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'c-mb-button-on': graphSetting.defaultJunctionPoint === 'ltrb' }"
|
||||
class="c-mb-button c-mb-button-c"
|
||||
style="width: 50px;"
|
||||
@click="graphSetting.defaultJunctionPoint = 'ltrb'"
|
||||
>
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-lianjie_connecting5"></use></svg>
|
||||
<span class="c-mb-text">四点</span>
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'c-mb-button-on': graphSetting.defaultJunctionPoint === 'tb' }"
|
||||
class="c-mb-button c-mb-button-c"
|
||||
style="width: 50px;"
|
||||
@click="graphSetting.defaultJunctionPoint = 'tb'"
|
||||
>
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-lianjie_connecting5"></use></svg>
|
||||
<span class="c-mb-text">上下</span>
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'c-mb-button-on': graphSetting.defaultJunctionPoint === 'lr' }"
|
||||
class="c-mb-button c-mb-button-c"
|
||||
style="width: 50px;"
|
||||
@click="graphSetting.defaultJunctionPoint = 'lr'"
|
||||
>
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-lianjie_connecting5"></use></svg>
|
||||
<span class="c-mb-text">左右</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="graphSetting.isNeedShowAutoLayoutButton"
|
||||
:title="graphSetting.autoLayouting ? '点击停止自动布局' : '点击开始自动调整布局'"
|
||||
:class="{ 'c-mb-button-on': graphSetting.autoLayouting }"
|
||||
class="c-mb-button"
|
||||
@click="toggleAutoLayout"
|
||||
>
|
||||
<svg v-if="!graphSetting.autoLayouting" class="rg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-zidong"></use>
|
||||
</svg>
|
||||
<svg v-else class="c-loading-icon rg-icon" aria-hidden="true"><use xlink:href="#icon-lianjiezhong"></use></svg>
|
||||
<span class="c-mb-text">自动</span>
|
||||
</div>
|
||||
<div class="c-mb-button" @click="refresh">
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-ico_reset"></use></svg>
|
||||
<span class="c-mb-text">刷新</span>
|
||||
</div>
|
||||
<div class="c-mb-button">
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-ziyuan"></use></svg>
|
||||
<span class="c-mb-text">下载</span>
|
||||
<div
|
||||
:style="{ width: downloadPanelWidth + 'px', 'margin-left': downloadPanelWidth * -1 + 'px' }"
|
||||
class="c-mb-child-panel"
|
||||
>
|
||||
<div class="c-mb-button c-mb-button-c" style="width: 50px;" @click="$parent.downloadAsImage('png')">
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-tupian"></use></svg>
|
||||
<span class="c-mb-text">PNG</span>
|
||||
</div>
|
||||
<div class="c-mb-button c-mb-button-c" style="width: 50px;" @click="$parent.downloadAsImage('jpg')">
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-tupian"></use></svg>
|
||||
<span class="c-mb-text">JPG</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="typeof $parent.onDownloadExcel === 'function'"
|
||||
class="c-mb-button c-mb-button-c"
|
||||
style="width: 50px;"
|
||||
@click="$parent.onDownloadExcel()"
|
||||
>
|
||||
<svg class="rg-icon" aria-hidden="true"><use xlink:href="#icon-ziyuan"></use></svg>
|
||||
<span class="c-mb-text">Excel</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
import SeeksRGLayouters from './core4vue/SeeksRGLayouters'
|
||||
export default {
|
||||
name: 'GraphMiniToolBar',
|
||||
props: {
|
||||
graphSetting: {
|
||||
mustUseProp: true,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
height: 275,
|
||||
hits: 0,
|
||||
downloadPanelWidth: 106,
|
||||
}
|
||||
},
|
||||
// watch: {
|
||||
// 'graphSetting.layoutName': function(newV, oldV) {
|
||||
// console.log('change layout:', newV, oldV)
|
||||
// SeeksRGLayouters.switchLayout(newV, this.graphSetting)
|
||||
// this.refresh()
|
||||
// }
|
||||
// },
|
||||
mounted() {
|
||||
if (this.$parent.onDownloadExcel !== null) {
|
||||
this.downloadPanelWidth += 50
|
||||
}
|
||||
if (this.graphSetting.layouts.length > 1) {
|
||||
this.height -= 40
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
refresh() {
|
||||
this.$parent.refresh()
|
||||
},
|
||||
switchLayout(layoutConfig) {
|
||||
if (window.SeeksGraphDebug) console.log('change layout:', layoutConfig)
|
||||
SeeksRGLayouters.switchLayout(layoutConfig, this.graphSetting)
|
||||
this.refresh()
|
||||
},
|
||||
toggleAutoLayout() {
|
||||
// console.log('this.graphSetting.autoLayouting', this.graphSetting.autoLayouting)
|
||||
this.graphSetting.autoLayouting = !this.graphSetting.autoLayouting
|
||||
if (this.graphSetting.autoLayouting) {
|
||||
if (!this.graphSetting.layouter.autoLayout) {
|
||||
console.log('当前布局不支持自动布局!')
|
||||
} else {
|
||||
this.graphSetting.layouter.autoLayout(true)
|
||||
}
|
||||
} else {
|
||||
if (!this.graphSetting.layouter.stop) {
|
||||
console.log('当前布局不支持自动布局stop!')
|
||||
} else {
|
||||
this.graphSetting.layouter.stop()
|
||||
}
|
||||
}
|
||||
},
|
||||
printGraphJsonData() {
|
||||
this.hits++
|
||||
setTimeout(() => {
|
||||
if (this.hits > 0) this.hits--
|
||||
}, 2000)
|
||||
if (this.hits > 5) {
|
||||
this.hits = 0
|
||||
this.$parent.printGraphJsonData()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: -3px;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
.c-mini-toolbar {
|
||||
width: 44px;
|
||||
position: absolute;
|
||||
margin-top: 170px;
|
||||
margin-right: 10px;
|
||||
z-index: 999;
|
||||
border: #bbbbbb solid 1px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0px 0px 8px #cccccc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.c-fixedLayout {
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
}
|
||||
.c-mb-button {
|
||||
height: 44px;
|
||||
width: 42px;
|
||||
margin-top: 0px;
|
||||
background-color: #ffffff;
|
||||
border-top: #efefef solid 1px;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
padding-top: 3px;
|
||||
cursor: pointer;
|
||||
color: #999999;
|
||||
font-size: 18px;
|
||||
float: left;
|
||||
box-sizing: border-box;
|
||||
line-height: 21px;
|
||||
}
|
||||
.c-mb-button .c-mb-text {
|
||||
display: inline-block;
|
||||
height: 14px;
|
||||
width: 42px;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
margin-top: 24px;
|
||||
margin-left: -28px;
|
||||
position: absolute;
|
||||
color: #262626;
|
||||
}
|
||||
.c-mb-button-on {
|
||||
background-color: #2e74b5;
|
||||
border-top: #2e4e8f solid 1px;
|
||||
color: #ffffff;
|
||||
}
|
||||
.c-mb-button:hover {
|
||||
background-color: #2e4e8f;
|
||||
border-top: #2e4e8f solid 1px;
|
||||
color: #ffffff;
|
||||
}
|
||||
.c-mb-button:hover .c-mb-text,
|
||||
.c-mb-button-on .c-mb-text {
|
||||
color: #ffffff;
|
||||
}
|
||||
.c-mb-button .c-mb-child-panel {
|
||||
height: 46px;
|
||||
position: absolute;
|
||||
margin-top: -26px;
|
||||
background-color: #ffffff;
|
||||
display: none;
|
||||
border: #bbbbbb solid 1px;
|
||||
box-shadow: 0px 0px 8px #cccccc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.c-mb-button:hover .c-mb-child-panel {
|
||||
display: block;
|
||||
}
|
||||
.c-mb-button .c-mb-button {
|
||||
height: 44px;
|
||||
width: 42px;
|
||||
margin: 0px;
|
||||
border: none;
|
||||
}
|
||||
.c-mb-button-c .c-mb-text {
|
||||
color: #262626 !important;
|
||||
}
|
||||
.c-mb-button-c:hover .c-mb-text,
|
||||
.c-mb-button-on .c-mb-text {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.c-loading-icon {
|
||||
animation: turn 1s linear infinite;
|
||||
}
|
||||
@keyframes turn {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
-webkit-transform: rotate(90deg);
|
||||
}
|
||||
50% {
|
||||
-webkit-transform: rotate(180deg);
|
||||
}
|
||||
75% {
|
||||
-webkit-transform: rotate(270deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
103
cmdb-ui/src/modules/cmdb/3rd/relation-graph/GraphMiniView.vue
Normal file
103
cmdb-ui/src/modules/cmdb/3rd/relation-graph/GraphMiniView.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div ref="miniView" class="c-mini-graph">
|
||||
<div :style="{width:(100 * zoom) + 'px',height:(graphSetting.canvasNVInfo.height * 100/graphSetting.canvasNVInfo.width * zoom)+'px'}" class="c-mini-canvas">
|
||||
<template v-for="thisNode in $parent.nodeViewList">
|
||||
<div v-if="isAllowShowNode(thisNode)" :key="thisNode.id" :style="{'margin-left':(thisNode.x * 100/graphSetting.canvasSize.width * zoom)+'px','margin-top':(thisNode.y * 100/graphSetting.canvasSize.width * zoom)+'px'}" class="c-mini-node" />
|
||||
</template>
|
||||
</div>
|
||||
<div :style="getPositionData()" class="c-mini-view">
|
||||
<i class="el-icon-view" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SeeksGraphMath from './core4vue/SeeksGraphMath'
|
||||
export default {
|
||||
name: 'GraphMiniView',
|
||||
props: {
|
||||
graphSetting: {
|
||||
mustUseProp: true,
|
||||
default: () => { return {} },
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
zoom: 1
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
getPositionData() {
|
||||
var _c_width = 100
|
||||
var _r = _c_width / this.graphSetting.canvasNVInfo.width
|
||||
var _width = this.graphSetting.viewNVInfo.width * _r
|
||||
var _height = this.graphSetting.viewNVInfo.height * _r
|
||||
var _view_x = (this.graphSetting.viewNVInfo.x - this.graphSetting.canvasNVInfo.x) * _r
|
||||
var _view_y = (this.graphSetting.viewNVInfo.y - this.graphSetting.canvasNVInfo.y) * _r
|
||||
if (_width > 100) {
|
||||
_height = _height * 100 / _width
|
||||
_view_x = _view_x * 100 / _width
|
||||
_view_y = _view_y * 100 / _width
|
||||
this.zoom = 100 / _width
|
||||
_width = 100
|
||||
} else {
|
||||
this.zoom = 1
|
||||
}
|
||||
// console.log('Mini View style:', _view_center_x, _canvas_center_x)
|
||||
var style = {
|
||||
width: _width + 'px',
|
||||
height: _height + 'px',
|
||||
'margin-left': _view_x + 'px',
|
||||
'margin-top': _view_y + 'px'
|
||||
}
|
||||
return style
|
||||
},
|
||||
isAllowShowNode(nodeData) {
|
||||
return SeeksGraphMath.isAllowShowNode(nodeData)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.c-mini-graph{
|
||||
height:100px;
|
||||
width:100px;
|
||||
position: absolute;
|
||||
margin-left: 60px;
|
||||
margin-top:100px;
|
||||
z-index: 999;
|
||||
}
|
||||
.c-fixedLayout{
|
||||
position: fixed;
|
||||
top:100px;
|
||||
}
|
||||
.c-mini-canvas{
|
||||
background-color: #AACBFF;
|
||||
border: #7BA8FF solid 1px;
|
||||
opacity: 0.8;
|
||||
position: absolute;
|
||||
}
|
||||
.c-mini-view{
|
||||
background-color: #F5A565;
|
||||
border: #C03639 solid 1px;
|
||||
opacity: 0.5;
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
}
|
||||
.c-mini-node{
|
||||
position: absolute;
|
||||
width:2px;
|
||||
height:2px;
|
||||
background-color: #000000;
|
||||
border-radius: 1px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :class="[$parent.isNeedFixedTools?'c-fixedLayout':'']" class="c-setting-panel-button" @click="toggleSettingPanel">
|
||||
<i class="el-icon-setting" />
|
||||
</div>
|
||||
<div v-if="showSettingPanel" :class="[$parent.isNeedFixedTools?'c-fixedLayout':'']" class="c-setting-panel">
|
||||
<!-- <div style="padding:3px;">-->
|
||||
<!-- 搜索节点:-->
|
||||
<!-- <el-autocomplete-->
|
||||
<!-- v-model="$parent.search_text"-->
|
||||
<!-- :fetch-suggestions="$parent.querySearchAsync"-->
|
||||
<!-- :trigger-on-focus="true"-->
|
||||
<!-- :label="'xxxx'"-->
|
||||
<!-- size="small"-->
|
||||
<!-- placeholder="输入节点名称"-->
|
||||
<!-- clearable-->
|
||||
<!-- style="width: 220px;"-->
|
||||
<!-- @select="$parent.handleSelect">-->
|
||||
<!-- <template slot-scope="{ item }">-->
|
||||
<!-- <div class="c-suggestion-name">{{ item.text }}</div>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-autocomplete>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div style="padding:3px;">-->
|
||||
<!-- 布局方式:-->
|
||||
<!-- <el-radio-group v-model="currentlayoutName" size="mini" @change="switchLayout">-->
|
||||
<!-- <el-radio-button label="center">中心</el-radio-button>-->
|
||||
<!-- <el-radio-button label="circle">环形</el-radio-button>-->
|
||||
<!-- <el-radio-button label="tree">树状(L)</el-radio-button>-->
|
||||
<!-- <el-radio-button label="tree-plus-r">树状(R)</el-radio-button>-->
|
||||
<!-- <el-radio-button label="tree-plus-t">树状(T)</el-radio-button>-->
|
||||
<!-- <el-radio-button label="tree-plus-b">树状(B)</el-radio-button>-->
|
||||
<!-- </el-radio-group>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div style="padding:3px;">-->
|
||||
<!-- 线条样式:-->
|
||||
<!-- <el-radio-group v-model="graphSetting.defaultLineShape" size="small">-->
|
||||
<!-- <el-radio-button :label="1">直线</el-radio-button>-->
|
||||
<!-- <el-radio-button :label="2">简洁</el-radio-button>-->
|
||||
<!-- <el-radio-button :label="3">生动</el-radio-button>-->
|
||||
<!-- <el-radio-button :label="5">鱼尾</el-radio-button>-->
|
||||
<!-- <el-radio-button :label="4">折线</el-radio-button>-->
|
||||
<!-- </el-radio-group>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div style="padding:3px;">-->
|
||||
<!-- 节点样式:-->
|
||||
<!-- <el-radio-group v-model="graphSetting.defaultNodeShape" size="small">-->
|
||||
<!-- <el-radio-button :label="0">圆形</el-radio-button>-->
|
||||
<!-- <el-radio-button :label="1">长方形</el-radio-button>-->
|
||||
<!-- </el-radio-group>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div style="padding:3px;">-->
|
||||
<!-- 图像缩放:-->
|
||||
<!-- <el-input-number v-model="graphSetting.canvasZoom" :max="500" :min="10" :step="20" size="small"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div style="padding:3px;">-->
|
||||
<!-- 连线标签:-->
|
||||
<!-- <el-switch-->
|
||||
<!-- v-model="graphSetting.defaultShowLineLabel"-->
|
||||
<!-- active-color="#13ce66"-->
|
||||
<!-- inactive-color="#ff4949"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div style="padding:3px;">-->
|
||||
<!-- 联动拖拽:-->
|
||||
<!-- <el-switch-->
|
||||
<!-- v-model="graphSetting.isMoveByParentNode"-->
|
||||
<!-- active-color="#13ce66"-->
|
||||
<!-- inactive-color="#ff4949"/>-->
|
||||
<!-- 拖动父节点后子节点跟随-->
|
||||
<!-- </div>-->
|
||||
<!-- <div style="padding:3px;display: none;">-->
|
||||
<!-- 最大层级:-->
|
||||
<!-- <el-radio-group v-model="graphSetting.maxLevel" size="small">-->
|
||||
<!-- <el-radio-button :label="1">1级</el-radio-button>-->
|
||||
<!-- <el-radio-button :label="2">2级</el-radio-button>-->
|
||||
<!-- <el-radio-button :label="3">3级</el-radio-button>-->
|
||||
<!-- <el-radio-button :label="4">4级</el-radio-button>-->
|
||||
<!-- <el-radio-button :label="5">5级</el-radio-button>-->
|
||||
<!-- </el-radio-group>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div style="padding:3px;">-->
|
||||
<!-- 自动布局:-->
|
||||
<!-- <el-switch-->
|
||||
<!-- v-model="graphSetting.autoLayouting"-->
|
||||
<!-- active-color="#13ce66"-->
|
||||
<!-- inactive-color="#ff4949"-->
|
||||
<!-- @change="toggleAutoLayout" />-->
|
||||
<!-- </div>-->
|
||||
<!-- <div style="padding:3px;">-->
|
||||
<!-- 全屏展示:-->
|
||||
<!-- <el-switch-->
|
||||
<!-- v-model="graphSetting.fullscreen"-->
|
||||
<!-- active-color="#13ce66"-->
|
||||
<!-- inactive-color="#ff4949"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div style="padding:3px;">-->
|
||||
<!-- 刷新布局:-->
|
||||
<!-- <el-button size="small" type="primary" icon="el-icon-refresh" @click="refresh">刷新</el-button>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div style="padding:3px;">-->
|
||||
<!-- 图片下载:-->
|
||||
<!-- <el-button size="small" type="primary" icon="el-icon-download" @click="$parent.downloadAsImage('png')">下载png</el-button>-->
|
||||
<!-- <el-button size="small" type="primary" icon="el-icon-download" @click="$parent.downloadAsImage('jpg')">下载jpg</el-button>-->
|
||||
<!-- </div>-->
|
||||
<!-- <slot :setting="graphSetting" name="settingPanelPlus"/>-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SeeksRGLayouters from './core4vue/SeeksRGLayouters'
|
||||
// import SeeksRGStore from './core4vue/SeeksRGStore'
|
||||
// import { mapState } from 'vuex'
|
||||
// var _parent = this.$parent
|
||||
// console.log('GraphSettingPanel.vue:', _parent)
|
||||
export default {
|
||||
name: 'GraphSettingPanel',
|
||||
props: {
|
||||
graphSetting: {
|
||||
mustUseProp: true,
|
||||
default: () => { return {} },
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
search_text: '',
|
||||
showSettingPanel: false,
|
||||
currentLayoutName: ''
|
||||
}
|
||||
},
|
||||
// computed: mapState({
|
||||
// graphSetting: () => _parent.graphSetting
|
||||
// }),
|
||||
// watch: {
|
||||
// 'graphSetting.layoutName': function(newV, oldV) {
|
||||
// console.log('change layout:', newV, oldV)
|
||||
// SeeksRGLayouters.switchLayout(newV, this.graphSetting)
|
||||
// this.$parent.refresh()
|
||||
// }
|
||||
// },
|
||||
methods: {
|
||||
toggleSettingPanel() {
|
||||
this.showSettingPanel = !this.showSettingPanel
|
||||
},
|
||||
toggleAutoLayout() {
|
||||
if (this.graphSetting.autoLayouting) {
|
||||
if (!this.graphSetting.layouter.autoLayout) {
|
||||
console.log('当前布局不支持自动布局!')
|
||||
} else {
|
||||
this.graphSetting.layouter.autoLayout(true)
|
||||
}
|
||||
} else {
|
||||
if (!this.graphSetting.layouter.stop) {
|
||||
console.log('当前布局不支持自动布局stop!')
|
||||
} else {
|
||||
this.graphSetting.layouter.stop()
|
||||
}
|
||||
}
|
||||
},
|
||||
switchLayout() {
|
||||
if (window.SeeksGraphDebug) console.log('change layout:', this.currentLayoutName)
|
||||
SeeksRGLayouters.switchLayout(this.currentLayoutName, this.graphSetting)
|
||||
this.refresh()
|
||||
},
|
||||
refresh() {
|
||||
this.$parent.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.c-setting-panel{
|
||||
--height:500px;
|
||||
--width:500px;
|
||||
width:500px;
|
||||
height:500px;
|
||||
position: absolute;
|
||||
margin-left:10px;
|
||||
margin-top:5px;
|
||||
font-size: 12px;
|
||||
color: rgb(58, 91, 178);
|
||||
padding:10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 0px 5px rgb(58, 91, 178);
|
||||
border-radius: 5px;
|
||||
z-index: 1000;
|
||||
background-color: #ffffff;
|
||||
border: rgb(58, 91, 178) solid 1px;
|
||||
padding-top:60px;
|
||||
}
|
||||
.c-setting-panel-button{
|
||||
height:45px;
|
||||
width:45px;
|
||||
font-size: 20px;
|
||||
line-height: 45px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
margin-left:25px;
|
||||
margin-top:20px;
|
||||
background-color: rgb(58, 91, 178);
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
z-index: 1001;
|
||||
box-shadow: 0px 0px 8px rgb(46, 78, 143);
|
||||
}
|
||||
.c-setting-panel-button:hover{
|
||||
box-shadow: 0px 0px 20px #FFA20A;
|
||||
border:#ffffff solid 1px;
|
||||
color: #FFA20A;
|
||||
-moz-transform: rotate(-89deg) translateX(-190px);
|
||||
animation-timing-function:linear;
|
||||
animation: flashButton 2s infinite;
|
||||
}
|
||||
.c-fixedLayout{
|
||||
position: fixed;
|
||||
top: 125px
|
||||
}
|
||||
@keyframes flashButton {
|
||||
from {
|
||||
box-shadow: 0px 0px 8px rgb(46, 78, 143);
|
||||
}
|
||||
30% {
|
||||
box-shadow: 0px 0px 20px #FFA20A;
|
||||
}
|
||||
to {
|
||||
box-shadow: 0px 0px 8px rgb(46, 78, 143);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,543 @@
|
||||
var STAITC_MAP_ANGLE = 0
|
||||
var SeeksGraphMath = {
|
||||
getRectPoint: function(x1, y1, x2, y2, n1w, n1h, n2w, n2h) {
|
||||
var fx = x1 + n1w / 2
|
||||
var fy = y1 + n1h / 2
|
||||
var tx = x2 + n2w / 2
|
||||
var ty = y2 + n2h / 2
|
||||
var _ar_x = fx < tx ? 1 : -1
|
||||
var _ar_y = fy < ty ? 1 : -1
|
||||
if (ty === fy) {
|
||||
return { x: fx + _ar_x * n1w / 2, y: fy }
|
||||
}
|
||||
var __tan = Math.abs((tx - fx) / (ty - fy))
|
||||
var rectAngle = n1w / n1h
|
||||
var __w = 0
|
||||
var __h = 0
|
||||
if (__tan < rectAngle) {
|
||||
__w = _ar_x * n1h / 2 * __tan
|
||||
__h = _ar_y * n1h / 2
|
||||
} else {
|
||||
__w = _ar_x * n1w / 2
|
||||
__h = _ar_y * n1w / 2 / __tan
|
||||
}
|
||||
return { x: fx + __w, y: fy + __h }
|
||||
},
|
||||
getRectPointBasic: function(x1, y1, x2, y2, n1w, n1h, n2w, n2h) {
|
||||
var fx = x1 + n1w / 2
|
||||
var fy = y1 + n1h / 2
|
||||
var tx = x2 + n2w / 2
|
||||
var ty = y2 + n2h / 2
|
||||
// var x = fx - tx
|
||||
// var y = fy - ty
|
||||
// var z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
|
||||
// var cos = y / z
|
||||
// var radina = Math.acos(cos)// 用反三角函数求弧度
|
||||
// var angle = Math.floor(180 / (Math.PI / radina))// 将弧度转换成角度
|
||||
// n1h = n1h + 10
|
||||
// n1w = n1w + 10
|
||||
|
||||
var __tanA = ty === fy ? 0 : (tx - fx) / (ty - fy)
|
||||
if (__tanA === 0)__tanA = (tx - fx) / (ty - fy + 1)
|
||||
var rectAngle = n1w / n2h
|
||||
var __w = 0
|
||||
var __h = 0
|
||||
var _case = '1'
|
||||
// var __A_angle = Math.atan(__tanA) / (Math.PI / 180)
|
||||
if ((-1 * rectAngle) < __tanA && __tanA < rectAngle) {
|
||||
_case = '2'
|
||||
if (fy < ty) {
|
||||
__w = n1h / 2 * __tanA
|
||||
__h = n1h / 2
|
||||
} else {
|
||||
__w = -1 * n1h / 2 * __tanA
|
||||
__h = -1 * n1h / 2
|
||||
}
|
||||
} else {
|
||||
if (fx < tx) {
|
||||
__w = 1 * n1w / 2
|
||||
__h = 1 * n1w / 2 / __tanA
|
||||
} else {
|
||||
__w = -1 * n1w / 2
|
||||
__h = -1 * n1w / 2 / __tanA
|
||||
}
|
||||
_case = '3'
|
||||
}
|
||||
// var __w = (n1h / 2) * Math.tan(__A_angle)
|
||||
// var __w = ty === fy ? parseInt(n1w / 2) : parseInt(((n1h / 2) * (tx - fx)) / (ty - fy))
|
||||
// var __h = tx === fx ? parseInt(n1h / 2) : parseInt((__w * (ty - fy)) / (tx - fx))
|
||||
return { x: fx + __w, y: fy + __h, _case }
|
||||
},
|
||||
getRectJoinPoint: function(x1, y1, x2, y2, n1w, n1h, n2w, n2h) {
|
||||
var _from_c_x = x1 + n1w / 2
|
||||
var _from_c_y = y1 + n1h / 2
|
||||
var _to_c_x = x2 + n2w / 2
|
||||
var _to_c_y = y2 + n2h / 2
|
||||
var _atan2 = parseInt(Math.atan2(_to_c_y - _from_c_y, _to_c_x - _from_c_x) * 180 / 3.14) + 135
|
||||
if (_atan2 >= 0 && _atan2 < 90) { // top
|
||||
return { x: x1 + n1w / 2, y: y1 - 5 }
|
||||
} else if (_atan2 >= 90 && _atan2 < 180) { // right
|
||||
return { x: x1 + n1w + 5, y: y1 + n1h / 2 }
|
||||
} else if (_atan2 >= 180 && _atan2 < 270) { // bottom
|
||||
return { x: x1 + n1w / 2, y: y1 + n1h + 5 }
|
||||
} else { // left
|
||||
return { x: x1 - 5, y: y1 + n1h / 2 }
|
||||
}
|
||||
},
|
||||
getRectHJoinPoint: function(x1, y1, x2, y2, n1w, n1h, n2w) {
|
||||
var _hH = n1h / 2
|
||||
// var _hW = n1w / 2
|
||||
if ((x1 + n1w) < x2) {
|
||||
return { x: x1 + n1w + 5, y: y1 + _hH }
|
||||
} else if ((x1 + n1w) < (x2 + n2w)) {
|
||||
return { x: x1 - 5, y: y1 + _hH }
|
||||
} else {
|
||||
return { x: x1 - 5, y: y1 + _hH }
|
||||
}
|
||||
},
|
||||
getRectVJoinPoint: function(x1, y1, x2, y2, n1w, n1h, n2w, n2h) {
|
||||
var _hW = n1w / 2
|
||||
// var _hW = n1w / 2
|
||||
if ((y1 + n1h) < y2) {
|
||||
return { y: y1 + n1h + 5, x: x1 + _hW }
|
||||
} else if ((y1 + n1h) < (y2 + n2h)) {
|
||||
return { y: y1 - 5, x: x1 + _hW }
|
||||
} else {
|
||||
return { y: y1 - 5, x: x1 + _hW }
|
||||
}
|
||||
},
|
||||
getBorderPoint: function(x1, y1, x2, y2, n1w, n1h, n2w, n2h, n1style) {
|
||||
if (n1style === 0) {
|
||||
return this.getCirclePoint(x1, y1, x2, y2, n1w, n1h, n2w, n2h)
|
||||
} else {
|
||||
return this.getRectPoint(x1, y1, x2, y2, n1w, n1h, n2w, n2h)
|
||||
}
|
||||
},
|
||||
getBorderPoint4MultiLine: function(x1, y1, x2, y2, n1w, n1h, n2w, n2h, n1style, isReserve, allSize, indexOfAll) {
|
||||
if (n1style === 0) {
|
||||
return this.getCirclePoint4MultiLine(x1, y1, x2, y2, n1w, n1h, n2w, n2h, isReserve, allSize, indexOfAll)
|
||||
} else {
|
||||
return this.getRectPoint(x1, y1, x2, y2, n1w, n1h, n2w, n2h)
|
||||
}
|
||||
},
|
||||
getCirclePoint: function(x1, y1, x2, y2, n1w, n1h, n2w, n2h) {
|
||||
var fx = x2 + n2w / 2
|
||||
var fy = y2 + n2h / 2
|
||||
var tx = x1 + n1w / 2
|
||||
var ty = y1 + n1h / 2
|
||||
var buff_h = fx - tx
|
||||
if (buff_h === 0) {
|
||||
return { x: tx, y: ty - (n1h / 2) * (fy < ty ? 1 : -1) }
|
||||
}
|
||||
var buff_v = fy - ty
|
||||
var k = buff_v / buff_h
|
||||
// var m = ty - tx * k
|
||||
var __x = Math.sqrt(1 / ((1 / Math.pow(n1w / 2, 2)) + (k ** 2 / Math.pow(n1h / 2, 2)))) * (fx < tx ? 1 : -1)
|
||||
var __y = k * __x
|
||||
// this.c = Math.sqrt(this.h * this.h + this.s * this.s)
|
||||
// // this.l = this.c - radius
|
||||
// this.v = (this.c - n1w / 2) * this.h / this.c * -1
|
||||
// this.t = (this.c - n1h / 2) * this.s / this.c * -1
|
||||
// alert(this.h+","+this.s+","+this.c+","+this.l+","+this.v+","+this.t);
|
||||
return { x: tx - __x, y: ty - __y }
|
||||
},
|
||||
getCirclePoint4MultiLine: function(x1, y1, x2, y2, n1w, n1h, n2w, n2h, isReserve, allSize, indexOfAll) {
|
||||
// console.log(indexOfAll, 'of', allSize, isReserve)
|
||||
if (isReserve) {
|
||||
indexOfAll = allSize - indexOfAll - 1
|
||||
// console.log(indexOfAll, 'of', allSize, '|indexOfAll changed!')
|
||||
}
|
||||
var to_x = x2 + n2w / 2
|
||||
var to_y = y2 + n2h / 2
|
||||
var from_x = x1 + n1w / 2
|
||||
var from_y = y1 + n1h / 2
|
||||
var buff_h = to_x - from_x
|
||||
if (buff_h === 0) {
|
||||
return { x: from_x, y: from_y - (n1h / 2) * (to_y < from_y ? 1 : -1) }
|
||||
}
|
||||
var distance = ((40 / (allSize + 1)) * (indexOfAll + 1)) - 20
|
||||
var buff_v = to_y - from_y
|
||||
var b = Math.sqrt(Math.pow(buff_h, 2) + Math.pow(buff_v, 2)) * distance / buff_h
|
||||
var k = buff_v / buff_h
|
||||
var m = n1w / 2
|
||||
var n = n1h / 2
|
||||
var __wow = (to_x < from_x ? 1 : -1)
|
||||
var __x = (-1 * (m ** 2) * k * b + (m * n * Math.sqrt((n ** 2 + (k ** 2) * (m ** 2) - b ** 2), 2)) / __wow) / (n ** 2 + m ** 2 * k ** 2)
|
||||
// var __x = Math.sqrt(1 / ((1 / Math.pow(n1w / 2, 2)) + (k ** 2 / Math.pow(n1h / 2, 2)))) * (to_x < from_x ? 1 : -1)
|
||||
var __y = k * __x + b
|
||||
// this.c = Math.sqrt(this.h * this.h + this.s * this.s)
|
||||
// // this.l = this.c - radius
|
||||
// this.v = (this.c - n1w / 2) * this.h / this.c * -1
|
||||
// this.t = (this.c - n1h / 2) * this.s / this.c * -1
|
||||
// alert(this.h+","+this.s+","+this.c+","+this.l+","+this.v+","+this.t);
|
||||
return { x: from_x - __x, y: from_y - __y }
|
||||
},
|
||||
getCirclePointBasic: function(x1, y1, x2, y2, n1w, n1h, n2w, n2h, radius) {
|
||||
var fx = x1 + n1w / 2
|
||||
var fy = y1 + n1h / 2
|
||||
var tx = x2 + n2w / 2
|
||||
var ty = y2 + n2h / 2
|
||||
this.h = tx - fx
|
||||
this.s = ty - fy
|
||||
this.c = Math.sqrt(this.h * this.h + this.s * this.s)
|
||||
this.l = this.c - radius
|
||||
this.v = this.l * this.h / this.c * -1
|
||||
this.t = this.l * this.s / this.c * -1
|
||||
// alert(this.h+","+this.s+","+this.c+","+this.l+","+this.v+","+this.t);
|
||||
return { x: tx + this.v, y: ty + this.t }
|
||||
},
|
||||
getCirclePointPlus: function(x1, y1, x2, y2, n1w, n1h, n2w, n2h) {
|
||||
var fx = x1 + n1w / 2
|
||||
var fy = y1 + n1h / 2
|
||||
var tx = x2 + n2w / 2
|
||||
var ty = y2 + n2h / 2
|
||||
this.h = tx - fx
|
||||
this.s = ty - fy
|
||||
this.c = Math.sqrt(this.h * this.h + this.s * this.s)
|
||||
// this.l = this.c - radius
|
||||
this.v = (this.c - n1w / 2) * this.h / this.c * -1
|
||||
this.t = (this.c - n1h / 2) * this.s / this.c * -1
|
||||
// alert(this.h+","+this.s+","+this.c+","+this.l+","+this.v+","+this.t);
|
||||
return { x: tx + this.v, y: ty + this.t }
|
||||
},
|
||||
getOvalPoint: function(c_x, c_y, c_r, c_i, c_n) {
|
||||
return {
|
||||
x: c_x + c_r * Math.sin((STAITC_MAP_ANGLE + (c_i * (360 / c_n)) + 0) * Math.PI / 180),
|
||||
y: c_y + c_r * Math.cos((STAITC_MAP_ANGLE + (c_i * (360 / c_n)) + 0) * Math.PI / 180) * -1
|
||||
}
|
||||
},
|
||||
getAngleType: function(buffer_x, buffer_y) {
|
||||
if (buffer_x >= 0 && buffer_y >= 0) { // 第一象限
|
||||
return 1
|
||||
} else if (buffer_x < 0 && buffer_y >= 0) { // 第二象限
|
||||
return 2
|
||||
} else if (buffer_x < 0 && buffer_y < 0) { // 第三象限
|
||||
return 3
|
||||
} else if (buffer_x >= 0 && buffer_y < 0) { // 第三象限
|
||||
return 4
|
||||
}
|
||||
},
|
||||
getTextAngle: function(fx, fy, tx, ty) {
|
||||
// 除数不能为0
|
||||
var tan = Math.atan(Math.abs((ty - fy) / (tx - fx))) * 180 / Math.PI
|
||||
|
||||
// return tan
|
||||
if (tx > fx && ty > fy) { // 第一象限
|
||||
} else if (tx > fx && ty < fy) { // 第二象限
|
||||
tan = -tan
|
||||
} else if (tx < fx && ty > fy) { // 第三象限
|
||||
tan = 180 - tan
|
||||
} else {
|
||||
tan = tan - 180
|
||||
}
|
||||
if (Math.abs(tan) > 90) {
|
||||
tan = tan + 180
|
||||
}
|
||||
// if (tan > 90 && tan < 270) {
|
||||
// tan = 0
|
||||
// }
|
||||
return parseInt(tan)
|
||||
},
|
||||
getTreePointFromTop: function(c_x, c_y, c_height, c_i, c_n, sizehelper) {
|
||||
if (!c_x) { // if root
|
||||
return {
|
||||
x: (sizehelper.canvas_width - sizehelper.node_width) / 2,
|
||||
y: (sizehelper.canvas_height - sizehelper.node_height) / 2 - 200
|
||||
}
|
||||
}
|
||||
var sssss = {
|
||||
x: c_x - 300 + (Math.max(600 / ((c_n === 1 ? 2 : c_n) - 1), 80)) * c_i,
|
||||
y: c_y + c_height
|
||||
}
|
||||
return sssss
|
||||
},
|
||||
getTreePointFromRight: function(c_x, c_y, c_height, c_i, c_n, sizehelper) {
|
||||
if (!c_x) { // if root
|
||||
return {
|
||||
x: (sizehelper.canvas_width - sizehelper.node_width) / 2 + 300,
|
||||
y: (sizehelper.canvas_height - sizehelper.node_height) / 2
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: c_x - c_height,
|
||||
y: c_y - 200 + (Math.max(400 / ((c_n === 1 ? 2 : c_n) - 1), 80)) * c_i
|
||||
}
|
||||
},
|
||||
getTreePointFromBottom: function(c_x, c_y, c_height, c_i, c_n, sizehelper) {
|
||||
if (!c_x) { // if root
|
||||
return {
|
||||
x: (sizehelper.canvas_width - sizehelper.node_width) / 2,
|
||||
y: (sizehelper.canvas_height - sizehelper.node_height) / 2 + 200
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: c_x - 300 + (Math.max(600 / ((c_n === 1 ? 2 : c_n) - 1), 80)) * c_i,
|
||||
y: c_y - c_height
|
||||
}
|
||||
},
|
||||
getTreePointFromLeft: function(c_x, c_y, c_height, c_i, c_n, sizehelper) {
|
||||
if (!c_x) { // if root
|
||||
return {
|
||||
x: (sizehelper.canvas_width - sizehelper.node_width) / 2 - 300,
|
||||
y: (sizehelper.canvas_height - sizehelper.node_height) / 2
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: c_x + c_height,
|
||||
y: c_y - 200 + (Math.max(400 / ((c_n === 1 ? 2 : c_n) - 1), 80)) * c_i
|
||||
}
|
||||
},
|
||||
analysisNodes: function(willLayoutNodes, thisLevelNodes, thisDeep, analyticResult, config) {
|
||||
if (thisLevelNodes.length > analyticResult.max_length) {
|
||||
analyticResult.max_length = thisLevelNodes.length
|
||||
}
|
||||
if (thisDeep > analyticResult.max_deep) {
|
||||
analyticResult.max_deep = thisDeep
|
||||
}
|
||||
var __thisLOT_subling = {
|
||||
level: thisDeep,
|
||||
all_size: thisLevelNodes.length,
|
||||
all_strength: 0
|
||||
}
|
||||
var newLevelNodes = []
|
||||
thisLevelNodes.forEach(thisNode => {
|
||||
if (!thisNode.lot)thisNode.lot = {}
|
||||
thisNode.lot.eached = true
|
||||
thisNode.lot.subling = __thisLOT_subling
|
||||
thisNode.lot.level = thisDeep
|
||||
willLayoutNodes.push(thisNode)
|
||||
})
|
||||
var __thisLevel_index = 0
|
||||
// var __prev_node
|
||||
thisLevelNodes.forEach(thisNode => {
|
||||
// console.log('Build node::', thisNode.name, thisNode.targetNodes.length)
|
||||
var __thisNode_child_size = 0
|
||||
if (thisNode.targetNodes) {
|
||||
let __thisTargetIndex = 0
|
||||
thisNode.targetNodes.forEach((thisTarget) => {
|
||||
if (!thisTarget.lot)thisTarget.lot = { eached: false }
|
||||
if (!thisTarget.lot.eached) {
|
||||
if (SeeksGraphMath.isAllowShowNode(thisTarget)) {
|
||||
thisTarget.lot.eached = true
|
||||
thisTarget.lot.parent = thisNode
|
||||
thisTarget.lot.index_of_parent = __thisTargetIndex++
|
||||
// thisTarget.lot.prevNode = __prev_node
|
||||
// if (__prev_node)__prev_node.lot.nextNode = thisTarget
|
||||
// __prev_node = thisTarget
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
newLevelNodes.push(thisTarget)
|
||||
__thisNode_child_size++
|
||||
} else {
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
// console.log('hide node:', thisTarget.name, 'from:', thisNode.text)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
thisNode.lot.strength = __thisNode_child_size > 0 ? __thisNode_child_size : 1
|
||||
__thisLOT_subling.all_strength += thisNode.lot.strength
|
||||
thisNode.lot.strength_plus = __thisLOT_subling.all_strength
|
||||
thisNode.lot.index_of_level = __thisLevel_index
|
||||
thisNode.lot.childs_size = __thisNode_child_size
|
||||
__thisLevel_index++
|
||||
})
|
||||
if (__thisLOT_subling.all_strength > analyticResult.max_strength) {
|
||||
analyticResult.max_strength = __thisLOT_subling.all_strength
|
||||
}
|
||||
// console.log(thisDeep, 'next level nodes:', newLevelNodes.length)
|
||||
if (newLevelNodes.length > 0) {
|
||||
// console.log('thisLevelNodes.length:', thisLevelNodes, thisLevelNodes.length)
|
||||
this.analysisNodes(willLayoutNodes, newLevelNodes, thisDeep + 1, analyticResult, config)
|
||||
} else {
|
||||
willLayoutNodes.forEach(thisNode => {
|
||||
if (thisNode.lot.childs_size > 0) {
|
||||
thisNode.lot.strengthWithChilds = 0
|
||||
}
|
||||
})
|
||||
willLayoutNodes.forEach(thisNode => {
|
||||
if (thisNode.lot.childs_size === 0) {
|
||||
thisNode.lot.strengthWithChilds = 1
|
||||
this.conductStrengthToParents(thisNode)
|
||||
}
|
||||
})
|
||||
this.analysisDataTree([willLayoutNodes[0]], 0)
|
||||
// willLayoutNodes.forEach(thisNode => {
|
||||
// thisNode.text = thisNode.lot.strengthWithChilds_from + ':' + thisNode.lot.strengthWithChilds + '/' + thisNode.lot.strength
|
||||
// })
|
||||
}
|
||||
},
|
||||
analysisNodes4Didirectional: function(willLayoutNodes, thisLevelNodes, thisDeep, analyticResult, levelDirect) {
|
||||
if (thisLevelNodes.length > analyticResult.max_length) {
|
||||
analyticResult.max_length = thisLevelNodes.length
|
||||
}
|
||||
if (thisDeep > analyticResult.max_deep) {
|
||||
analyticResult.max_deep = thisDeep
|
||||
}
|
||||
var __thisLOT_subling = {
|
||||
level: thisDeep,
|
||||
all_size: thisLevelNodes.length,
|
||||
all_strength: 0
|
||||
}
|
||||
var newLevelNodes = []
|
||||
thisLevelNodes.forEach(thisNode => {
|
||||
if (!thisNode.lot)thisNode.lot = {}
|
||||
thisNode.lot.eached = true
|
||||
thisNode.lot.subling = __thisLOT_subling
|
||||
thisNode.lot.level = thisDeep
|
||||
willLayoutNodes.push(thisNode)
|
||||
})
|
||||
var __thisLevel_index = 0
|
||||
// var __prev_node
|
||||
thisLevelNodes.forEach(thisNode => {
|
||||
var __thisNode_child_size = 0
|
||||
if (levelDirect === 0) {
|
||||
// console.log('Build node::from::', thisNode.name, thisNode.targetNodes.length)
|
||||
let __thisTargetIndex = 0
|
||||
thisNode.targetNodes.forEach((thisTarget) => {
|
||||
if (!thisTarget.lot)thisTarget.lot = { eached: false }
|
||||
if (!thisTarget.lot.eached) {
|
||||
if (SeeksGraphMath.isAllowShowNode(thisTarget)) {
|
||||
thisTarget.lot.eached = true
|
||||
thisTarget.lot.parent = thisNode
|
||||
thisTarget.lot.index_of_parent = __thisTargetIndex++
|
||||
// thisTarget.lot.prevNode = __prev_node
|
||||
// if (__prev_node)__prev_node.lot.nextNode = thisTarget
|
||||
// __prev_node = thisTarget
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
newLevelNodes.push(thisTarget)
|
||||
__thisNode_child_size++
|
||||
} else {
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
// console.log('hide node:', thisTarget.text, 'from:', thisNode.text)
|
||||
}
|
||||
} else {
|
||||
// console.log('solved node:', thisTarget.text, 'from:', thisNode.text)
|
||||
}
|
||||
})
|
||||
} else if (levelDirect === -1) {
|
||||
// console.log('Build node::from::', thisNode.name, thisNode.targetFrom.length)
|
||||
let __thisTargetIndex = 0
|
||||
thisNode.targetFrom.forEach((thisTarget) => {
|
||||
if (!thisTarget.lot)thisTarget.lot = { eached: false }
|
||||
if (!thisTarget.lot.eached) {
|
||||
if (SeeksGraphMath.isAllowShowNode(thisTarget)) {
|
||||
thisTarget.lot.eached = true
|
||||
thisTarget.lot.parent = thisNode
|
||||
thisTarget.lot.index_of_parent = __thisTargetIndex++
|
||||
// thisTarget.lot.prevNode = __prev_node
|
||||
// if (__prev_node)__prev_node.lot.nextNode = thisTarget
|
||||
// __prev_node = thisTarget
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
newLevelNodes.push(thisTarget)
|
||||
__thisNode_child_size++
|
||||
} else {
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
// console.log('hide node:', thisTarget.name, 'from:', thisNode.text)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// console.log('Build node::to::', thisNode.name, thisNode.targetTo.length)
|
||||
let __thisTargetIndex = 0
|
||||
thisNode.targetTo.forEach((thisTarget) => {
|
||||
if (!thisTarget.lot)thisTarget.lot = { eached: false }
|
||||
if (!thisTarget.lot.eached) {
|
||||
if (SeeksGraphMath.isAllowShowNode(thisTarget)) {
|
||||
thisTarget.lot.eached = true
|
||||
thisTarget.lot.parent = thisNode
|
||||
thisTarget.lot.index_of_parent = __thisTargetIndex++
|
||||
// thisTarget.lot.prevNode = __prev_node
|
||||
// if (__prev_node)__prev_node.lot.nextNode = thisTarget
|
||||
// __prev_node = thisTarget
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
newLevelNodes.push(thisTarget)
|
||||
__thisNode_child_size++
|
||||
} else {
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
// console.log('hide node:', thisTarget.name, 'from:', thisNode.text)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
thisNode.lot.strength = __thisNode_child_size > 0 ? __thisNode_child_size : 1
|
||||
__thisLOT_subling.all_strength += thisNode.lot.strength
|
||||
thisNode.lot.strength_plus = __thisLOT_subling.all_strength
|
||||
thisNode.lot.index_of_level = __thisLevel_index
|
||||
thisNode.lot.childs_size = __thisNode_child_size
|
||||
__thisLevel_index++
|
||||
})
|
||||
if (__thisLOT_subling.all_strength > analyticResult.max_strength) {
|
||||
analyticResult.max_strength = __thisLOT_subling.all_strength
|
||||
}
|
||||
// console.log(thisDeep, 'next level nodes:', newLevelNodes.length)
|
||||
if (newLevelNodes.length > 0) {
|
||||
// console.log('thisLevelNodes.length:', thisLevelNodes, thisLevelNodes.length)
|
||||
SeeksGraphMath.analysisNodes4Didirectional(willLayoutNodes, newLevelNodes, thisDeep + (levelDirect === -1 ? -1 : 1), analyticResult, levelDirect)
|
||||
} else {
|
||||
willLayoutNodes.forEach(thisNode => {
|
||||
if (thisNode.lot.childs_size > 0) {
|
||||
thisNode.lot.strengthWithChilds = 0
|
||||
}
|
||||
})
|
||||
willLayoutNodes.forEach(thisNode => {
|
||||
if (thisNode.lot.childs_size === 0) {
|
||||
thisNode.lot.strengthWithChilds = 1
|
||||
SeeksGraphMath.conductStrengthToParents(thisNode)
|
||||
}
|
||||
})
|
||||
SeeksGraphMath.analysisDataTree([willLayoutNodes[0]], 0, levelDirect)
|
||||
// willLayoutNodes.forEach(thisNode => {
|
||||
// thisNode.text = thisNode.lot.strengthWithChilds_from + ':' + thisNode.lot.strengthWithChilds + '/' + thisNode.lot.strength
|
||||
// })
|
||||
}
|
||||
},
|
||||
conductStrengthToParents(node) {
|
||||
if (node.lot.parent) {
|
||||
node.lot.parent.lot.strengthWithChilds += 1
|
||||
this.conductStrengthToParents(node.lot.parent)
|
||||
}
|
||||
},
|
||||
analysisDataTree: function(thisLevelNodes, thisDeep, levelDirect) {
|
||||
if (levelDirect === undefined) levelDirect = 1
|
||||
var newLevelNodes = []
|
||||
var currentLevelStrengthWidthChilds = 0
|
||||
thisLevelNodes.forEach(thisNode => {
|
||||
// console.log('Place node aaaaaa:', levelDirect, thisNode.text, (thisNode.lot.level < 0 ? -1 : 1))
|
||||
if (thisNode.lot.level === 0 || levelDirect === (thisNode.lot.level < 0 ? -1 : 1)) {
|
||||
if (thisNode.lot.childs_size > 0) {
|
||||
thisNode.lot.childs.forEach((thisTarget) => {
|
||||
newLevelNodes.push(thisTarget)
|
||||
})
|
||||
}
|
||||
if (thisNode.lot.parent && currentLevelStrengthWidthChilds < thisNode.lot.parent.lot.strengthWithChilds_from) {
|
||||
currentLevelStrengthWidthChilds = thisNode.lot.parent.lot.strengthWithChilds_from
|
||||
}
|
||||
thisNode.lot.strengthWithChilds_from = currentLevelStrengthWidthChilds
|
||||
currentLevelStrengthWidthChilds += thisNode.lot.strengthWithChilds
|
||||
}
|
||||
})
|
||||
// console.log(thisDeep, 'next level nodes:', newLevelNodes.length)
|
||||
if (newLevelNodes.length > 0) {
|
||||
this.analysisDataTree(newLevelNodes, thisDeep + levelDirect, levelDirect)
|
||||
}
|
||||
},
|
||||
// conductStrengthToParents(node) {
|
||||
// if (node.lot.childs_size === 0) {
|
||||
// return 1
|
||||
// } else {
|
||||
// var _sum = 0
|
||||
// node.lot.childs.forEach(thisChild => {
|
||||
// thisChild.lot.strengthWithChilds = this.conductStrengthToParents(thisChild)
|
||||
// _sum += thisChild.lot.strengthWithChilds
|
||||
// })
|
||||
// return _sum
|
||||
// }
|
||||
// },
|
||||
isAllowShowNode: function(thisNode) {
|
||||
const _r = thisNode.isShow !== false && thisNode.isHide !== true && (!thisNode.lot.parent || this.isAllowShowNode(thisNode.lot.parent, false) === true)
|
||||
// if (derict !== false && _r === false) console.log('hide node by:', thisNode.isShow !== false, thisNode.isHide !== true)
|
||||
return _r
|
||||
}
|
||||
}
|
||||
|
||||
export default SeeksGraphMath
|
||||
@@ -0,0 +1,63 @@
|
||||
import SeeksBidirectionalTreeLayouter from './layouters/SeeksBidirectionalTreeLayouter'
|
||||
import SeeksCenterLayouter from './layouters/SeeksCenterLayouter'
|
||||
import SeeksCircleLayouter from './layouters/SeeksCircleLayouter'
|
||||
import SeeksAutoLayouter from './layouters/SeeksAutoLayouter'
|
||||
import SeeksFixedLayouter from './layouters/SeeksFixedLayouter'
|
||||
|
||||
var SeeksRGLayouters = {
|
||||
createLayout(layoutSetting, _graphSetting) {
|
||||
_graphSetting.canvasZoom = 100
|
||||
_graphSetting.layoutClassName = layoutSetting.layoutClassName
|
||||
_graphSetting.layoutLabel = layoutSetting.label
|
||||
_graphSetting.layoutName = layoutSetting.layoutName
|
||||
_graphSetting.layoutDirection = layoutSetting.layoutDirection
|
||||
|
||||
if (layoutSetting.useLayoutStyleOptions === true) {
|
||||
_graphSetting.defaultExpandHolderPosition = layoutSetting.defaultExpandHolderPosition
|
||||
_graphSetting.defaultJunctionPoint = layoutSetting.defaultJunctionPoint
|
||||
|
||||
_graphSetting.defaultNodeColor = layoutSetting.defaultNodeColor
|
||||
_graphSetting.defaultNodeFontColor = layoutSetting.defaultNodeFontColor
|
||||
_graphSetting.defaultNodeBorderColor = layoutSetting.defaultNodeBorderColor
|
||||
_graphSetting.defaultNodeBorderWidth = layoutSetting.defaultNodeBorderWidth
|
||||
_graphSetting.defaultLineColor = layoutSetting.defaultLineColor
|
||||
_graphSetting.defaultLineWidth = layoutSetting.defaultLineWidth
|
||||
_graphSetting.defaultLineShape = layoutSetting.defaultLineShape
|
||||
_graphSetting.defaultNodeShape = layoutSetting.defaultNodeShape
|
||||
_graphSetting.defaultNodeWidth = layoutSetting.defaultNodeWidth
|
||||
_graphSetting.defaultNodeHeight = layoutSetting.defaultNodeHeight
|
||||
_graphSetting.defaultLineMarker = layoutSetting.defaultLineMarker
|
||||
_graphSetting.defaultShowLineLabel = layoutSetting.defaultShowLineLabel
|
||||
}
|
||||
var _layout = null
|
||||
if (layoutSetting.layoutName === 'SeeksBidirectionalTreeLayouter' || layoutSetting.layoutName === 'tree') {
|
||||
_layout = new SeeksBidirectionalTreeLayouter(layoutSetting, _graphSetting)
|
||||
} else if (layoutSetting.layoutName === 'SeeksCenterLayouter' || layoutSetting.layoutName === 'center') {
|
||||
_layout = new SeeksCenterLayouter(layoutSetting, _graphSetting)
|
||||
} else if (layoutSetting.layoutName === 'SeeksCircleLayouter' || layoutSetting.layoutName === 'circle') {
|
||||
_layout = new SeeksCircleLayouter(layoutSetting, _graphSetting)
|
||||
} else if (layoutSetting.layoutName === 'SeeksAutoLayouter' || layoutSetting.layoutName === 'force') {
|
||||
_layout = new SeeksAutoLayouter(layoutSetting, _graphSetting)
|
||||
} else if (layoutSetting.layoutName === 'SeeksFixedLayouter' || layoutSetting.layoutName === 'fixed') {
|
||||
_layout = new SeeksFixedLayouter(layoutSetting, _graphSetting)
|
||||
}
|
||||
_graphSetting.isNeedShowAutoLayoutButton = layoutSetting.allowAutoLayoutIfSupport !== false && _layout.autoLayout !== undefined
|
||||
return _layout
|
||||
},
|
||||
switchLayout(layoutLabelOrSetting, _graphSetting) {
|
||||
const __origin_nodes = _graphSetting.layouter ? _graphSetting.layouter.__origin_nodes : []
|
||||
const __rootNode = _graphSetting.layouter ? _graphSetting.layouter.rootNode : null
|
||||
if ((typeof layoutLabelOrSetting) === 'string') {
|
||||
for (var thisLayoutSetting in _graphSetting.layouts) {
|
||||
if (thisLayoutSetting.label === layoutLabelOrSetting) {
|
||||
layoutLabelOrSetting = thisLayoutSetting
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
_graphSetting.layouter = SeeksRGLayouters.createLayout(layoutLabelOrSetting, _graphSetting)
|
||||
_graphSetting.layouter.__origin_nodes = __origin_nodes
|
||||
_graphSetting.layouter.rootNode = __rootNode
|
||||
}
|
||||
}
|
||||
export default SeeksRGLayouters
|
||||
@@ -0,0 +1,571 @@
|
||||
<template>
|
||||
<g
|
||||
v-if="lineProps.isHide !== true && isAllowShowNode(lineProps.fromNode) && isAllowShowNode(lineProps.toNode)"
|
||||
ref="seeksRGLink"
|
||||
transform="translate(0,0)"
|
||||
>
|
||||
<!--<path :d="createLinePath(lineProps.fromNode, lineProps.toNode)" :class="[lineProps.styleClass,graphSetting.checkedLineId==lineProps.seeks_id?'c-rg-line-checked':'']" :stroke="lineProps.color?linkProps.color:graphSetting.defaultLineColor" :marker-end="'url(\'#' + (linkProps.arrow?linkProps.arrow:'arrow-default') + '\')'" fill="none" class="c-rg-line" @click="onClick($event)" />-->
|
||||
<!--<g v-if="graphSetting.defaultShowLineLabel" :transform="getTextTransform(textPositon.x,textPositon.y,textPositon.rotate)">-->
|
||||
<!--<text v-for="thisRelation in lineProps.relations" :key="'t-'+thisRelation.id" :x="0" :y="0" :style="{fill:(thisRelation.fontColor?thisRelation.fontColor:(thisRelation.color?thisRelation.color:undefined))}" class="c-rg-link-text" @click="onClick($event)">-->
|
||||
<!--<!–<textPath :xlink:href="'#'+lineProps.id">{{ lineProps.text }}</textPath>–>-->
|
||||
<!--{{ thisRelation.text }}-->
|
||||
<!--</text>-->
|
||||
<!--</g>-->
|
||||
<template v-for="(thisRelation, ri) in lineProps.relations">
|
||||
<g v-if="thisRelation.isHide === false" :key="'l-' + thisRelation.id">
|
||||
<path
|
||||
:d="createLinePath(lineProps.fromNode, lineProps.toNode, ri, thisRelation)"
|
||||
:class="[
|
||||
thisRelation.styleClass,
|
||||
graphSetting.checkedLineId == lineProps.seeks_id ? 'c-rg-line-checked' : '',
|
||||
]"
|
||||
:stroke="thisRelation.color ? thisRelation.color : graphSetting.defaultLineColor"
|
||||
:style="{
|
||||
'stroke-width': (thisRelation.lineWidth ? thisRelation.lineWidth : graphSetting.defaultLineWidth) + 'px',
|
||||
}"
|
||||
:marker-end="getArrow(thisRelation.isHideArrow, thisRelation.arrow, thisRelation.color)"
|
||||
fill="none"
|
||||
class="c-rg-line"
|
||||
@click="onClick($event)"
|
||||
/>
|
||||
<g
|
||||
v-if="graphSetting.defaultShowLineLabel && graphSetting.canvasZoom > 40"
|
||||
:transform="
|
||||
getTextTransform(
|
||||
thisRelation,
|
||||
thisRelation.textPositon.x,
|
||||
thisRelation.textPositon.y,
|
||||
thisRelation.textPositon.rotate
|
||||
)
|
||||
"
|
||||
>
|
||||
<text
|
||||
:key="'t-' + thisRelation.id"
|
||||
:x="0"
|
||||
:y="0"
|
||||
:style="{
|
||||
fill: thisRelation.fontColor
|
||||
? thisRelation.fontColor
|
||||
: thisRelation.color
|
||||
? thisRelation.color
|
||||
: undefined,
|
||||
}"
|
||||
class="c-rg-link-text"
|
||||
@click="onClick($event)"
|
||||
>
|
||||
<!--<textPath :xlink:href="'#'+lineProps.id">{{ lineProps.text }}</textPath>-->
|
||||
{{ thisRelation.text }}
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</template>
|
||||
</g>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
// import SeeksRGStore from './SeeksRGStore'
|
||||
import SeeksGraphMath from './SeeksGraphMath'
|
||||
// import Velocity from 'velocity-animate'
|
||||
// import { mapState } from 'vuex'
|
||||
// var _parent = this.$parent
|
||||
const JUNCTION_POINT_STYLE = {
|
||||
border: 'border',
|
||||
ltrb: 'ltrb',
|
||||
tb: 'tb',
|
||||
lr: 'lr',
|
||||
}
|
||||
export default {
|
||||
name: 'SeeksRGLink',
|
||||
props: {
|
||||
graphSetting: {
|
||||
mustUseProp: true,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
type: Object,
|
||||
},
|
||||
lineProps: {
|
||||
mustUseProp: true,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
type: Object,
|
||||
},
|
||||
onLineClick: {
|
||||
mustUseProp: false,
|
||||
default: () => {
|
||||
return () => {}
|
||||
},
|
||||
type: Function,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
is_flashing: false,
|
||||
}
|
||||
},
|
||||
show() {
|
||||
this.isShow = true
|
||||
},
|
||||
watch: {},
|
||||
mounted() {
|
||||
// this.refresh()
|
||||
// var __this = this
|
||||
// setInterval(this.onLineClick, 1000)
|
||||
},
|
||||
// beforeDestroy() {
|
||||
// const elx = this.$refs.seeksRGLink
|
||||
// elx.remove()
|
||||
// },
|
||||
methods: {
|
||||
getTextTransform(thisRelation, x, y, rotate) {
|
||||
if (!x || !y) {
|
||||
return 'translate(0,0)'
|
||||
}
|
||||
var __lineShape =
|
||||
thisRelation.lineShape === undefined ? this.graphSetting.defaultLineShape : thisRelation.lineShape
|
||||
if (__lineShape === 1 || __lineShape === 4) {
|
||||
return 'translate(' + x + ',' + y + ')rotate(' + (rotate || 0) + ')'
|
||||
} else {
|
||||
return 'translate(' + x + ',' + y + ')'
|
||||
}
|
||||
},
|
||||
getArrow(isHideArrow, arrow, color) {
|
||||
// console.log('xxxxxxxxxxxx')
|
||||
if (isHideArrow) {
|
||||
return 'none'
|
||||
} else {
|
||||
var _arrow = this.$parent.getLineArrow(color)
|
||||
return "url('#" + _arrow + "')"
|
||||
}
|
||||
},
|
||||
createLinePath(from, to, ri, relationData) {
|
||||
// console.log('redrawLine:', this.lineProps.fromNode.id, this.lineProps.toNode.id, ri)
|
||||
// console.log('_point:', _point)
|
||||
if (!ri) ri = 0
|
||||
var __lineShape =
|
||||
relationData.lineShape === undefined ? this.graphSetting.defaultLineShape : relationData.lineShape
|
||||
var __lineDirection =
|
||||
relationData.lineDirection === undefined ? this.graphSetting.layoutDirection : relationData.lineDirection
|
||||
var from_x = from.x
|
||||
var from_y = from.y
|
||||
var to_x = to.x
|
||||
var to_y = to.y
|
||||
if (isNaN(from_x) || isNaN(from_y)) {
|
||||
console.error('error start node:', from)
|
||||
relationData.textPositon.x = 50
|
||||
relationData.textPositon.y = 50
|
||||
relationData.textPositon.rotate = 0
|
||||
return 'M 0 0 L 100 100'
|
||||
}
|
||||
if (isNaN(to_x) || isNaN(to_y)) {
|
||||
console.error('error start point:', from)
|
||||
relationData.textPositon.x = 50
|
||||
relationData.textPositon.y = 50
|
||||
relationData.textPositon.rotate = 0
|
||||
return 'M 0 0 L 100 100'
|
||||
}
|
||||
var f_W = from.el.offsetWidth || from.width || from.w
|
||||
var f_H = from.el.offsetHeight || from.height || from.h
|
||||
if (isNaN(f_W) || isNaN(f_H)) {
|
||||
// console.log('error from node size:', f_W, f_H)
|
||||
relationData.textPositon.x = 50
|
||||
relationData.textPositon.y = 50
|
||||
relationData.textPositon.rotate = 0
|
||||
return 'M 0 0 L 100 100'
|
||||
}
|
||||
var t_W = to.el.offsetWidth || to.width || to.w
|
||||
var t_H = to.el.offsetHeight || to.height || to.h
|
||||
if (isNaN(t_W) || isNaN(t_H)) {
|
||||
// console.log('error to node size:', f_W, f_H)
|
||||
relationData.textPositon.x = 50
|
||||
relationData.textPositon.y = 50
|
||||
relationData.textPositon.rotate = 0
|
||||
return 'M 0 0 L 100 100'
|
||||
}
|
||||
if (relationData.isReverse) {
|
||||
;[from_x, from_y, to_x, to_y, f_W, f_H, t_W, t_H] = [to_x, to_y, from_x, from_y, t_W, t_H, f_W, f_H]
|
||||
}
|
||||
var __params4start = [
|
||||
from_x,
|
||||
from_y,
|
||||
to_x,
|
||||
to_y,
|
||||
f_W,
|
||||
f_H,
|
||||
t_W,
|
||||
t_H,
|
||||
this.graphSetting.defaultNodeShape,
|
||||
relationData.isReverse,
|
||||
this.lineProps.relations.length,
|
||||
ri,
|
||||
]
|
||||
var __params4end = [
|
||||
to_x,
|
||||
to_y,
|
||||
from_x,
|
||||
from_y,
|
||||
t_W,
|
||||
t_H,
|
||||
f_W,
|
||||
f_H,
|
||||
this.graphSetting.defaultNodeShape,
|
||||
!relationData.isReverse,
|
||||
this.lineProps.relations.length,
|
||||
ri,
|
||||
]
|
||||
var __start, __end
|
||||
var _junctionPointStyle = this.graphSetting.defaultJunctionPoint
|
||||
if (!_junctionPointStyle) {
|
||||
_junctionPointStyle = JUNCTION_POINT_STYLE.border
|
||||
}
|
||||
if (_junctionPointStyle === JUNCTION_POINT_STYLE.border) {
|
||||
__start = SeeksGraphMath.getBorderPoint4MultiLine(...__params4start)
|
||||
__end = SeeksGraphMath.getBorderPoint4MultiLine(...__params4end)
|
||||
} else if (_junctionPointStyle === JUNCTION_POINT_STYLE.ltrb) {
|
||||
__start = SeeksGraphMath.getRectJoinPoint(...__params4start)
|
||||
__end = SeeksGraphMath.getRectJoinPoint(...__params4end)
|
||||
} else if (_junctionPointStyle === JUNCTION_POINT_STYLE.tb) {
|
||||
__start = SeeksGraphMath.getRectVJoinPoint(...__params4start)
|
||||
__end = SeeksGraphMath.getRectVJoinPoint(...__params4end)
|
||||
} else if (_junctionPointStyle === JUNCTION_POINT_STYLE.lr) {
|
||||
__start = SeeksGraphMath.getRectHJoinPoint(...__params4start)
|
||||
__end = SeeksGraphMath.getRectHJoinPoint(...__params4end)
|
||||
}
|
||||
var fx = __start.x
|
||||
var fy = __start.y
|
||||
var tx = __end.x
|
||||
var ty = __end.y
|
||||
if (isNaN(fx) || isNaN(fy)) {
|
||||
console.error('error start point:', from)
|
||||
relationData.textPositon.x = 50
|
||||
relationData.textPositon.y = 50
|
||||
relationData.textPositon.rotate = 0
|
||||
return 'M 0 0 L 100 100'
|
||||
}
|
||||
if (isNaN(tx) || isNaN(ty)) {
|
||||
console.error('error end point:', to)
|
||||
relationData.textPositon.x = 50
|
||||
relationData.textPositon.y = 50
|
||||
relationData.textPositon.rotate = 0
|
||||
return 'M 0 0 L 100 100'
|
||||
}
|
||||
var __buff_x = __end.x - __start.x
|
||||
var __buff_y = __end.y - __start.y
|
||||
var __buff_type = __end.x > __start.x ? 1 : -1
|
||||
if (__lineDirection === 'v') {
|
||||
__buff_type = __end.y > __start.y ? 1 : -1
|
||||
}
|
||||
var __path = ''
|
||||
if (__lineShape === 4) {
|
||||
const distanceRate = (60 / (this.lineProps.relations.length + 1)) * (ri + 1) - 30
|
||||
if (__lineDirection === 'v') {
|
||||
__buff_y = __buff_y - (__buff_type * 33 + distanceRate)
|
||||
relationData.textPositon.x = fx + __buff_x + 5
|
||||
relationData.textPositon.y = fy + __buff_type * 40 + distanceRate
|
||||
relationData.textPositon.rotate = 90
|
||||
__path =
|
||||
'M ' +
|
||||
fx +
|
||||
' ' +
|
||||
fy +
|
||||
' v' +
|
||||
(__buff_type * 33 + distanceRate) +
|
||||
' h' +
|
||||
(__buff_x + distanceRate) +
|
||||
' v' +
|
||||
__buff_y
|
||||
} else {
|
||||
if (relationData.reverseText === true) {
|
||||
relationData.textPositon.x = fx + __buff_type * 10 - (__buff_type < 0 ? 30 : 0)
|
||||
relationData.textPositon.y = fy - 5
|
||||
__buff_x = __buff_x - __buff_type * 120
|
||||
__path = 'M ' + fx + ' ' + fy + ' h' + __buff_type * 120 + ' v' + __buff_y + ' h' + __buff_x
|
||||
} else {
|
||||
relationData.textPositon.x = fx + __buff_type * 50 - (__buff_type < 0 ? 30 : 0)
|
||||
relationData.textPositon.y = fy + __buff_y - 5 + distanceRate
|
||||
__buff_x = __buff_x - (__buff_type * 33 + distanceRate)
|
||||
__buff_y = __buff_y + __buff_type * distanceRate
|
||||
__path = 'M ' + fx + ' ' + fy + ' h' + (__buff_type * 33 + distanceRate) + ' v' + __buff_y + ' h' + __buff_x
|
||||
}
|
||||
}
|
||||
} else if (__lineShape === 2) {
|
||||
// var __buff_type_x = __end.x > __start.x ? 1 : -1
|
||||
const __buff_type_y = __end.y > __start.y ? 1 : -1
|
||||
const _base = Math.abs(__buff_x) + Math.abs(__buff_y)
|
||||
relationData.textPositon.x = parseInt(__end.x - (__buff_x / _base) * 60 - 20)
|
||||
relationData.textPositon.y = parseInt(__end.y - (__buff_y / _base) * 60 - 20 * __buff_type_y)
|
||||
const distanceRate = (1 / (this.lineProps.relations.length + 1)) * (ri + 1) - 0.5 + 0.5
|
||||
if (__lineDirection === 'v') {
|
||||
__path =
|
||||
'M' +
|
||||
fx +
|
||||
',' +
|
||||
fy +
|
||||
' c' +
|
||||
0 +
|
||||
',' +
|
||||
__buff_type * 30 +
|
||||
' ' +
|
||||
__buff_x * distanceRate +
|
||||
',' +
|
||||
__buff_type * -10 +
|
||||
' ' +
|
||||
__buff_x +
|
||||
',' +
|
||||
__buff_y
|
||||
} else {
|
||||
__path =
|
||||
'M' +
|
||||
fx +
|
||||
',' +
|
||||
fy +
|
||||
' c' +
|
||||
__buff_type * 30 +
|
||||
',' +
|
||||
0 +
|
||||
' ' +
|
||||
__buff_type * -10 +
|
||||
',' +
|
||||
__buff_y * distanceRate +
|
||||
' ' +
|
||||
__buff_x +
|
||||
',' +
|
||||
__buff_y
|
||||
}
|
||||
} else if (__lineShape === 6) {
|
||||
// const __buff_type_x = __end.x > __start.x ? 1 : -1
|
||||
const __buff_type_y = __end.y > __start.y ? 1 : -1
|
||||
const _base = Math.abs(__buff_x) + Math.abs(__buff_y)
|
||||
relationData.textPositon.x = parseInt(__end.x - (__buff_x / _base) * 60 - 20)
|
||||
relationData.textPositon.y = parseInt(__end.y - (__buff_y / _base) * 60 - 20 * __buff_type_y)
|
||||
if (__lineDirection === 'v') {
|
||||
__path =
|
||||
'M' +
|
||||
fx +
|
||||
',' +
|
||||
fy +
|
||||
' c' +
|
||||
0 +
|
||||
',' +
|
||||
__buff_y / 2 +
|
||||
' ' +
|
||||
__buff_x +
|
||||
',' +
|
||||
__buff_y / 2 +
|
||||
' ' +
|
||||
__buff_x +
|
||||
',' +
|
||||
__buff_y
|
||||
} else {
|
||||
__path =
|
||||
'M' +
|
||||
fx +
|
||||
',' +
|
||||
fy +
|
||||
' c' +
|
||||
__buff_x / 2 +
|
||||
',' +
|
||||
0 +
|
||||
' ' +
|
||||
__buff_x / 2 +
|
||||
',' +
|
||||
__buff_y +
|
||||
' ' +
|
||||
__buff_x +
|
||||
',' +
|
||||
__buff_y
|
||||
}
|
||||
} else if (__lineShape === 3) {
|
||||
relationData.textPositon.x = __end.x - __buff_type * 63
|
||||
relationData.textPositon.y = __end.y + 3
|
||||
const distanceRate = (1 / (this.lineProps.relations.length + 1)) * (ri + 1) - 0.5 + 0.5
|
||||
if (__lineDirection === 'v') {
|
||||
__path =
|
||||
'M' +
|
||||
fx +
|
||||
',' +
|
||||
fy +
|
||||
' c' +
|
||||
0 +
|
||||
',' +
|
||||
__buff_y * distanceRate +
|
||||
' ' +
|
||||
0 +
|
||||
',' +
|
||||
0 +
|
||||
' ' +
|
||||
__buff_x +
|
||||
',' +
|
||||
__buff_y
|
||||
} else {
|
||||
// console.log('start:', __start, __end, __buff_x, __buff_y)
|
||||
__path =
|
||||
'M' +
|
||||
fx +
|
||||
',' +
|
||||
fy +
|
||||
' c' +
|
||||
__buff_type * 30 +
|
||||
',' +
|
||||
0 +
|
||||
' ' +
|
||||
__buff_type * -10 +
|
||||
',' +
|
||||
__buff_y * distanceRate +
|
||||
' ' +
|
||||
__buff_x +
|
||||
',' +
|
||||
__buff_y
|
||||
}
|
||||
} else if (__lineShape === 5) {
|
||||
// relationData.text.x = __start.x + __buff_x / 2 - 33
|
||||
// relationData.text.y = __start.y + __buff_y / 2 - 3
|
||||
relationData.textPositon.x = __end.x - __buff_type * 63
|
||||
relationData.textPositon.y = __end.y + 3
|
||||
const distanceRate = (1 / (this.lineProps.relations.length + 1)) * (ri + 1) - 0.5 + 0.5
|
||||
if (__lineDirection === 'v') {
|
||||
__path =
|
||||
'M' +
|
||||
fx +
|
||||
',' +
|
||||
fy +
|
||||
' c' +
|
||||
0 +
|
||||
',' +
|
||||
0 +
|
||||
' ' +
|
||||
0 +
|
||||
',' +
|
||||
__buff_y * distanceRate +
|
||||
' ' +
|
||||
__buff_x +
|
||||
',' +
|
||||
__buff_y // 鱼尾
|
||||
} else {
|
||||
__path =
|
||||
'M' +
|
||||
fx +
|
||||
',' +
|
||||
fy +
|
||||
' c' +
|
||||
0 +
|
||||
',' +
|
||||
0 +
|
||||
' ' +
|
||||
__buff_x * distanceRate +
|
||||
',' +
|
||||
0 +
|
||||
' ' +
|
||||
__buff_x +
|
||||
',' +
|
||||
__buff_y // 鱼尾
|
||||
}
|
||||
// __path = 'M' + fx + ',' + fy + ' c' + (0) + ',' + (0) + ' ' + (0) + ',' + (__buff_y * 0.5) + ' ' + __buff_x + ',' + __buff_y
|
||||
// __path = 'M' + fx + ',' + fy + ' c' + (0) + ',' + (0) + ' ' + (-100) + ',' + (__buff_y * 0.5) + ' ' + __buff_x + ',' + __buff_y
|
||||
// __path = 'M' + fx + ',' + fy + ' c' + (30) + ',' + (0) + ' ' + (-10) + ',' + (__buff_y * 0.5) + ' ' + __buff_x + ',' + __buff_y
|
||||
// __path = 'M' + fx + ',' + fy + ' c' + (50) + ',' + (0) + ' ' + (-50) + ',' + (__buff_y * 0.5) + ' ' + __buff_x + ',' + __buff_y
|
||||
// __path = 'M' + fx + ',' + fy + ' c' + (100) + ',' + (0) + ' ' + (10) + ',' + (__buff_y * 0.5) + ' ' + __buff_x + ',' + __buff_y
|
||||
// __path = 'M' + fx + ',' + fy + ' c' + (0) + ',' + (0) + ' ' + (__buff_x * 0.5) + ',' + (0) + ' ' + __buff_x + ',' + __buff_y // 类似鱼尾
|
||||
// __path = 'M' + fx + ',' + fy + ' c' + (__buff_x * 0.5) + ',' + (0) + ' ' + (0) + ',' + (0) + ' ' + __buff_x + ',' + __buff_y // 三角
|
||||
// __path = 'M' + fx + ',' + fy + ' c' + (0) + ',' + (0) + ' ' + (__buff_x * 0.5) + ',' + (0) + ' ' + __buff_x + ',' + __buff_y // 鱼尾
|
||||
// __path = 'M' + fx + ',' + fy + ' c' + (50) + ',' + (__buff_y * 0.5) + ' ' + (0) + ',' + (0) + ' ' + __buff_x + ',' + __buff_y //
|
||||
// __path = 'M' + fx + ',' + fy + ' c' + (50) + ',' + (__buff_y * 0.5) + ' ' + (0) + ',' + (0) + ' ' + __buff_x + ',' + __buff_y
|
||||
} else {
|
||||
var _angle_type = SeeksGraphMath.getAngleType(__buff_x, __buff_y)
|
||||
relationData.textPositon.rotate = SeeksGraphMath.getTextAngle(fx, fy, tx, ty)
|
||||
var _xxx = _angle_type === 2 || _angle_type === 4 ? -1 : 1
|
||||
var _x = (__buff_y === 0 ? -50 : (Math.sin(Math.atan(__buff_x / __buff_y)) * 10) / Math.sin(90)) * _xxx
|
||||
var _y = __buff_x === 0 ? -50 : (Math.sin(Math.atan(__buff_y / __buff_x)) * 10) / Math.sin(90)
|
||||
relationData.textPositon.x = parseInt(__start.x + __buff_x / 2 - _x)
|
||||
relationData.textPositon.y = parseInt(__start.y + __buff_y / 2 - _y)
|
||||
if (isNaN(relationData.textPositon.rotate)) {
|
||||
relationData.textPositon.rotate = 0
|
||||
console.log('NaN rotate:', relationData)
|
||||
}
|
||||
// this.lineProps.text = relationData.text.rotate
|
||||
__path = 'M ' + fx + ' ' + fy + ' L ' + tx + ' ' + ty
|
||||
}
|
||||
return __path
|
||||
},
|
||||
onClick(e) {
|
||||
// RGStore.commit('setCurrentLineId', this.lineProps.id)
|
||||
this.graphSetting.checkedLineId = this.lineProps.seeks_id
|
||||
this.lineProps.fromNode.selected = true
|
||||
this.lineProps.toNode.selected = true
|
||||
// Velocity(this.$refs.seeksRGLink, { strokDashoffset: 50 }, { duration: 3000, loop: 5 })
|
||||
setTimeout(
|
||||
function() {
|
||||
this.lineProps.fromNode.selected = false
|
||||
this.lineProps.toNode.selected = false
|
||||
}.bind(this),
|
||||
2000
|
||||
)
|
||||
if (this.onLineClick) {
|
||||
this.onLineClick(this.lineProps, e)
|
||||
}
|
||||
},
|
||||
isAllowShowNode: function(thisNode) {
|
||||
const _r =
|
||||
thisNode.isShow !== false &&
|
||||
thisNode.isHide !== true &&
|
||||
(!thisNode.lot.parent || this.isAllowShowNode(thisNode.lot.parent, false) === true)
|
||||
// if (derict !== false && _r === false) console.log('be hide node:', thisNode.text)
|
||||
return _r
|
||||
},
|
||||
flash() {},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="">
|
||||
/*.RGLine-enter-active {*/
|
||||
/*transition: all .3s ease;*/
|
||||
/*}*/
|
||||
/*.RGLine-leave-active {*/
|
||||
/*transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);*/
|
||||
/*}*/
|
||||
.c-rg-link-text {
|
||||
fill: #888888;
|
||||
font-size: 12px;
|
||||
}
|
||||
.c-rg-line {
|
||||
z-index: 1000;
|
||||
fill-rule: nonzero;
|
||||
/*marker-end: url('#arrow');*/
|
||||
/* firefox bug fix - won't rotate at 90deg angles */
|
||||
/*-moz-transform: rotate(-89deg) translateX(-190px);*/
|
||||
/*animation-timing-function:linear;*/
|
||||
/*animation: ACTRGLineInit 1s;*/
|
||||
}
|
||||
.c-rg-line-tool {
|
||||
stroke-dasharray: 5, 5, 5;
|
||||
}
|
||||
.c-rg-line-flash {
|
||||
/* firefox bug fix - won't rotate at 90deg angles */
|
||||
-moz-transform: rotate(-89deg) translateX(-190px);
|
||||
animation-timing-function: linear;
|
||||
animation: ACTRGLineChecked 10s;
|
||||
}
|
||||
@keyframes ACTRGLineInit {
|
||||
from {
|
||||
stroke-dashoffset: 10px;
|
||||
stroke-dasharray: 20, 20, 20;
|
||||
}
|
||||
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
stroke-dasharray: 0, 0, 0;
|
||||
}
|
||||
}
|
||||
@keyframes ACTRGLineChecked {
|
||||
from {
|
||||
stroke-dashoffset: 352px;
|
||||
}
|
||||
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,479 @@
|
||||
<template>
|
||||
<div
|
||||
v-show="isAllowShowNode(nodeProps)"
|
||||
ref="seeksRGNode"
|
||||
:style="{'left':nodeProps.x + 'px','top':nodeProps.y + 'px', 'opacity': (nodeProps.opacity>1?nodeProps.opacity/100:nodeProps.opacity) }"
|
||||
class="rel-node-peel"
|
||||
@mousedown.left.stop="onDragStart($event)"
|
||||
@mouseover.stop="onMouseHover($event)"
|
||||
@mouseout.stop="onMouseOut($event)"
|
||||
@click.stop="onclick($event)"
|
||||
>
|
||||
<div v-if="(nodeProps.expandHolderPosition&&nodeProps.expandHolderPosition!=='hide')||(graphSetting.defaultExpandHolderPosition&&graphSetting.defaultExpandHolderPosition!=='hide'&&nodeProps.lot.childs&&nodeProps.lot.childs.length>0)" :class="[('c-expand-positon-'+(nodeProps.expandHolderPosition||graphSetting.defaultExpandHolderPosition))]" class="c-btn-open-close">
|
||||
<span :class="expandButtonClass" :style="{'background-color':(nodeProps.color||graphSetting.defaultNodeColor)}" @click.stop="expandOrCollapseNode">
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="nodeProps.html" v-html="nodeProps.html" />
|
||||
<div
|
||||
v-else
|
||||
:class="['rel-node-shape-'+(nodeProps.nodeShape===undefined?graphSetting.defaultNodeShape:nodeProps.nodeShape),'rel-node-type-'+nodeProps.type, (nodeProps.id===graphSetting.checkedNodeId?'rel-node-checked':''), (nodeProps.selected?'rel-node-selected':''), nodeProps.styleClass, (hovering?'rel-node-hover':''), (nodeProps.innerHTML?'rel-diy-node':'')]"
|
||||
:style="{'background-color':(nodeProps.color===undefined?graphSetting.defaultNodeColor:nodeProps.color),'color':(nodeProps.fontColor===undefined?graphSetting.defaultNodeFontColor:nodeProps.fontColor),'border': (nodeProps.borderColor || graphSetting.defaultNodeBorderColor) + ' solid '+(nodeProps.borderWidth || graphSetting.defaultNodeBorderWidth)+'px', 'width':(nodeProps.width || graphSetting.defaultNodeWidth)+'px', 'height':(nodeProps.height||graphSetting.defaultNodeHeight)+'px'}"
|
||||
class="rel-node"
|
||||
>
|
||||
<template v-if="!(graphSetting.hideNodeContentByZoom === true && graphSetting.canvasZoom<40)">
|
||||
<slot :node="nodeProps" name="node">
|
||||
<div v-if="!nodeProps.innerHTML" :style="{'color':(nodeProps.fontColor || graphSetting.defaultNodeFontColor)}" class="c-node-text">
|
||||
<span v-html="getNodeName()" />
|
||||
</div>
|
||||
<div v-else v-html="nodeProps.innerHTML" />
|
||||
</slot>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
// import SeeksRGStore from './SeeksRGStore'
|
||||
// import SeeksGraphMath from './SeeksGraphMath'
|
||||
import SeeksRGUtils from './SeeksRGUtils'
|
||||
// import Velocity from 'velocity-animate'
|
||||
// import { mapState } from 'vuex'
|
||||
// var _parent = this.$parent
|
||||
// function isAllowShowNode(isShow, isHide, parent) {
|
||||
// const _r = isShow !== false && isHide !== true && (!parent || isAllowShowNode(parent.isShow, parent.isHide, parent.lot.parent) === true)
|
||||
// return _r
|
||||
// }
|
||||
export default {
|
||||
name: 'SeeksRGNode',
|
||||
components: { },
|
||||
props: {
|
||||
graphSetting: {
|
||||
mustUseProp: true,
|
||||
default: () => { return {} },
|
||||
type: Object
|
||||
},
|
||||
nodeProps: {
|
||||
mustUseProp: true,
|
||||
default: () => { return {} },
|
||||
type: Object
|
||||
},
|
||||
onNodeClick: {
|
||||
mustUseProp: false,
|
||||
default: () => { return () => {} },
|
||||
type: Function
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hovering: false,
|
||||
borderColor: '',
|
||||
dragging: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
expandButtonClass() {
|
||||
return this.nodeProps.expanded===false ? 'c-expanded' : 'c-collapsed'
|
||||
}
|
||||
},
|
||||
// show() {
|
||||
//
|
||||
// },
|
||||
watch: {
|
||||
// 'nodeProps.isShow': function(v) {
|
||||
// console.log('nodeProps.isShow:', v)
|
||||
// if (v === true) {
|
||||
// this.$nextTick(() => {
|
||||
// this.nodeProps.el.offsetWidth = this.$refs.seeksRGNode.offsetWidth
|
||||
// this.nodeProps.el.offsetHeight = this.$refs.seeksRGNode.offsetHeight
|
||||
// console.log('node 挂载 el size:', this.$refs.seeksRGNode.offsetWidth, this.$refs.seeksRGNode.offsetHeight)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
},
|
||||
created() {
|
||||
// Vue.version
|
||||
},
|
||||
mounted() {
|
||||
this.refreshNodeProperties()
|
||||
// this.leave(this.$refs.seeksRGNode)
|
||||
// console.log('node show:', this.nodeProps.text, this.$parent.$slots.node)
|
||||
},
|
||||
beforeDestroy() {
|
||||
const elx = this.$refs.seeksRGNode
|
||||
elx.remove()
|
||||
},
|
||||
methods: {
|
||||
refreshNodeProperties() {
|
||||
this.nodeProps.el = this.$refs.seeksRGNode
|
||||
// console.log('node 挂载 el:', this.nodeProps.text, this.nodeProps.el.offsetWidth, this.nodeProps.el.offsetHeight)
|
||||
// this.$nextTick(() => {
|
||||
// this.nodeProps.el.offsetWidth = this.$refs.seeksRGNode.offsetWidth
|
||||
// this.nodeProps.el.offsetHeight = this.$refs.seeksRGNode.offsetHeight
|
||||
// console.log('node 挂载 el size:', this.$refs.seeksRGNode.offsetWidth, this.$refs.seeksRGNode.offsetHeight)
|
||||
// })
|
||||
// this.nodeProps.em = true
|
||||
// if (this.nodeProps.style === 0) {
|
||||
// this.nodeProps.name = SeeksRGUtils.transName4Circle(this.nodeProps.name, this.nodeProps.el.offsetWidth)
|
||||
// console.log('resize node name:', this.name)
|
||||
// }
|
||||
// this.nodeProps.el_width = this.$refs.seeksRGNode.offsetWidth
|
||||
// this.nodeProps.el_height = this.$refs.seeksRGNode.offsetHeight
|
||||
// var __this = this
|
||||
// setInterval(function() {
|
||||
// __this.nodeProps.x = __this.nodeProps.x
|
||||
// __this.nodeProps.y = __this.nodeProps.y
|
||||
// }, 1000)
|
||||
},
|
||||
getNodeName() {
|
||||
// if (this.hovering) return 'N-' + this.nodeProps.seeks_id
|
||||
if (this.hovering) {
|
||||
return this.nodeProps.text
|
||||
}
|
||||
if (this.nodeProps.width === undefined && this.nodeProps.nodeShape !== 0) {
|
||||
return this.nodeProps.text
|
||||
}
|
||||
var _w = this.nodeProps.el.offsetWidth
|
||||
var _h = this.nodeProps.el.offsetHeight
|
||||
var _num_l = parseInt((_w - 20) / 20)
|
||||
var _num_c = parseInt((_h - 20) / 20)
|
||||
if (_num_l === -1 || _num_c === -1) {
|
||||
return this.nodeProps.text
|
||||
}
|
||||
var _length = _num_l * _num_c * 2
|
||||
var _text_arr = []
|
||||
var _current_length = 0
|
||||
for (var i = 0; i < this.nodeProps.text.length; i++) {
|
||||
var _thisChar = this.nodeProps.text[i]
|
||||
var _charCode = _thisChar.charCodeAt(0)
|
||||
var _charLength = 1
|
||||
if (_charCode < 0 || _charCode > 255) {
|
||||
_charLength = 2
|
||||
}
|
||||
if ((_current_length + _charLength) > _length) {
|
||||
return _text_arr.join('') + '...'
|
||||
} else {
|
||||
_current_length += _charLength
|
||||
_text_arr.push(_thisChar)
|
||||
}
|
||||
}
|
||||
return _text_arr.join('')
|
||||
// return _num_l + '/' + _num_c
|
||||
// return this.nodeProps.text
|
||||
},
|
||||
expandOrCollapseNode(e) {
|
||||
if (this.nodeProps.expanded === false) {
|
||||
this.nodeProps.expanded = true
|
||||
this.nodeProps.lot.childs.forEach(thisNode => {
|
||||
thisNode.isShow = true
|
||||
})
|
||||
this.$parent.onNodeExpandEvent(this.nodeProps, e)
|
||||
} else {
|
||||
this.nodeProps.expanded = false
|
||||
this.nodeProps.lot.childs.forEach(thisNode => {
|
||||
thisNode.isShow = false
|
||||
})
|
||||
this.$parent.onNodeCollapseEvent(this.nodeProps, e)
|
||||
}
|
||||
},
|
||||
onDragStart(e) {
|
||||
if (this.graphSetting.disableDragNode || this.nodeProps.disableDrag) {
|
||||
return
|
||||
}
|
||||
this.dragging = true
|
||||
this.hovering = false
|
||||
SeeksRGUtils.startDrag(e, this.nodeProps, this.onNodeDraged)
|
||||
},
|
||||
onNodeDraged(x, y) {
|
||||
if (this.graphSetting.isMoveByParentNode) {
|
||||
this.nodeProps.lot.childs.forEach(thisCnode => {
|
||||
thisCnode.x += x
|
||||
thisCnode.y += y
|
||||
})
|
||||
}
|
||||
if (Math.abs(x) + Math.abs(y) > 6) {
|
||||
setTimeout(function() {
|
||||
if (window.SeeksGraphDebug) console.log('delay end dragging', this.dragging)
|
||||
this.dragging = false
|
||||
}.bind(this), 100)
|
||||
} else {
|
||||
this.dragging = false
|
||||
}
|
||||
},
|
||||
onMouseHover() {
|
||||
if (this.dragging) {
|
||||
return
|
||||
}
|
||||
this.hovering = true
|
||||
},
|
||||
onMouseOut() {
|
||||
this.hovering = false
|
||||
},
|
||||
onclick(e) {
|
||||
if (this.dragging) {
|
||||
return
|
||||
}
|
||||
if (!this.nodeProps.disableDefaultClickEffect) {
|
||||
this.graphSetting.checkedNodeId = this.nodeProps.id
|
||||
}
|
||||
if (this.onNodeClick) {
|
||||
this.onNodeClick(this.nodeProps, e)
|
||||
}
|
||||
},
|
||||
// beforeEnter(el) {
|
||||
// console.log('beforeEnter')
|
||||
// el.style.opacity = 0
|
||||
// el.style.transformOrigin = 'left'
|
||||
// },
|
||||
// enter(el, done) {
|
||||
// console.log('enter')
|
||||
// Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
|
||||
// Velocity(el, { fontSize: '1em' }, { complete: done })
|
||||
// },
|
||||
// leave(el, done) {
|
||||
// console.log('leave')
|
||||
// Velocity(el, { translateX: '0px', rotateZ: '360deg' }, { duration: 600 })
|
||||
// // Velocity(el, { rotateZ: '180deg' }, { loop: 1 })
|
||||
// // Velocity(el, {
|
||||
// // rotateZ: '45deg',
|
||||
// // translateY: '30px',
|
||||
// // translateX: '30px',
|
||||
// // opacity: 0
|
||||
// // }, { complete: done })
|
||||
// },
|
||||
getLightColor(col) {
|
||||
// if (this.borderColor !== '') {
|
||||
// return this.borderColor
|
||||
// }
|
||||
if (col[0] === '#') {
|
||||
var _s = col.substring(1)
|
||||
if (_s.length === 3) {
|
||||
_s = _s[0] + _s[0] + _s[1] + _s[1] + _s[2] + _s[2]
|
||||
}
|
||||
var _rgb_arr = [
|
||||
parseInt(_s[0] + '' + _s[1], 16),
|
||||
parseInt(_s[2] + '' + _s[3], 16),
|
||||
parseInt(_s[4] + '' + _s[5], 16)
|
||||
]
|
||||
if (window.SeeksGraphDebug) console.log('getLightColor1:', col, ':', _rgb_arr.join(','))
|
||||
col = 'rgb(' + _rgb_arr.join(',') + ')'
|
||||
}
|
||||
var _st = col.substring(col.indexOf('(') + 1)
|
||||
_st = _st.substring(0, _st.indexOf(')'))
|
||||
var _rgb_string = _st.split(',')
|
||||
// console.log('getLightColor444:', _st, ':', _rgb_string.join(','))
|
||||
if (_rgb_string.length >= 3) {
|
||||
var _rgb_number = [
|
||||
parseInt(parseInt(_rgb_string[0]) * 0.9),
|
||||
parseInt(parseInt(_rgb_string[1]) * 0.9),
|
||||
parseInt(parseInt(_rgb_string[2]) * 0.9)
|
||||
]
|
||||
if (window.SeeksGraphDebug) console.log('getLightColor2:', col, ':', _rgb_number.join(','))
|
||||
this.borderColor = 'rgb(' + _rgb_number.join(',') + ', 0.3)'
|
||||
return this.borderColor
|
||||
} else {
|
||||
this.borderColor = col
|
||||
return col
|
||||
}
|
||||
},
|
||||
isAllowShowNode(thisNode) {
|
||||
const _r = thisNode.isShow !== false && thisNode.isHide !== true && (!thisNode.lot.parent || this.isAllowShowNode(thisNode.lot.parent, false) === true)
|
||||
return _r
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.rg-icon {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
vertical-align: 0px;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
.el-icon-remove,.el-icon-circle-plus{
|
||||
cursor: pointer;
|
||||
}
|
||||
.rel-node-peel{
|
||||
clear: both;
|
||||
padding:8px;
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
/*border:green solid 1px;*/
|
||||
}
|
||||
.rel-node{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rel-node-shape-1{
|
||||
/*border: #FD8B37 solid 1px;*/
|
||||
border-radius: 8px;
|
||||
padding:5px;
|
||||
padding-left:15px;
|
||||
padding-right:15px;
|
||||
}
|
||||
.c-node-text{
|
||||
height:100%;
|
||||
width:100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.rel-node-shape-0{
|
||||
padding:10px;
|
||||
}
|
||||
.rel-node-shape-0{
|
||||
width:80px;
|
||||
height:80px;
|
||||
border-radius: 50%;
|
||||
/*border: #FD8B37 solid 2px;*/
|
||||
/*text-align: left;*/
|
||||
/*padding:10px;*/
|
||||
/*white-space: nowrap;*/
|
||||
/*text-overflow: ellipsis;*/
|
||||
/*overflow: hidden;*/
|
||||
/*word-break: break-all;*/
|
||||
}
|
||||
.rel-node-shape-0:hover{
|
||||
/*overflow: visible;*/
|
||||
/*text-overflow: inherit;*/
|
||||
box-shadow: 0px 0px 5px #FFC5A6;
|
||||
}
|
||||
/*.rel-node{*/
|
||||
/*display: table;*/
|
||||
/*}*/
|
||||
/*.rel-node span{*/
|
||||
/*display: table-cell;*/
|
||||
/*vertical-align:middle;*/
|
||||
/*}*/
|
||||
.rel-node-type-button{
|
||||
border-radius: 25px;
|
||||
color: blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
.rel-node-hover{
|
||||
}
|
||||
.rel-node-checked{
|
||||
box-shadow: 0px 0px 10px #FD8B37;
|
||||
/*border-color: #BA7909;*/
|
||||
/*background-color: #FD8B37;*/
|
||||
/*color: #ffffff;*/
|
||||
/* firefox bug fix - won't rotate at 90deg angles */
|
||||
-moz-transform: rotate(-89deg) translateX(-190px);
|
||||
animation-timing-function:linear;
|
||||
animation: ACTRGNodeInit 2s;
|
||||
}
|
||||
.rel-node-selected{
|
||||
box-shadow: 0px 0px 10px #FD8B37;
|
||||
/*border-color: #BA7909;*/
|
||||
/*background-color: #FD8B37;*/
|
||||
/*color: #ffffff;*/
|
||||
/* firefox bug fix - won't rotate at 90deg angles */
|
||||
-moz-transform: rotate(-89deg) translateX(-190px);
|
||||
animation-timing-function:linear;
|
||||
animation: ACTRGNodeInit 2s;
|
||||
}
|
||||
.rel-node-vtree-2 {
|
||||
transform-origin:0 0;/* 设置旋转中心为左上角*/
|
||||
/*transform-origin:50% 50%;!* 设置放大中心为元素中心 *!*/
|
||||
transform: rotate(30deg) translateX(0px);
|
||||
}
|
||||
.rel-node-vtree {
|
||||
width:130px;
|
||||
height:45px;
|
||||
text-align: left;
|
||||
}
|
||||
/*.c-node-text{*/
|
||||
/*font-size: 12px;*/
|
||||
/*display: inline-block;*/
|
||||
/*width:100px;*/
|
||||
/*height:16px;*/
|
||||
/*margin-top:40px;*/
|
||||
/*margin-left:-25px;*/
|
||||
/*position:absolute;*/
|
||||
/*word-break:keep-all;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;*/
|
||||
/*text-align:center;*/
|
||||
/*}*/
|
||||
.c-btn-open-close{
|
||||
position: absolute;
|
||||
height:100%;
|
||||
width:19px;
|
||||
/*border:red solid 1px;*/
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/*border:#ff0000 solid 1px;*/
|
||||
}
|
||||
.c-btn-open-close span{
|
||||
width: 19px;
|
||||
height:19px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
border-radius: 15px;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
font-size: 19px;
|
||||
line-height: 16px;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
.c-expanded{
|
||||
background-image: url(data:image/svg+xml;%20charset=utf8,%3Csvg%20t=%221606310217820%22%20viewBox=%220%200%201024%201024%22%20version=%221.1%22%20xmlns=%22http://www.w3.org/2000/svg%22%20p-id=%223373%22%20width=%2232%22%20height=%2232%22%3E%3Cpath%20d=%22M853.333333%20480H544V170.666667c0-17.066667-14.933333-32-32-32s-32%2014.933333-32%2032v309.333333H170.666667c-17.066667%200-32%2014.933333-32%2032s14.933333%2032%2032%2032h309.333333V853.333333c0%2017.066667%2014.933333%2032%2032%2032s32-14.933333%2032-32V544H853.333333c17.066667%200%2032-14.933333%2032-32s-14.933333-32-32-32z%22%20p-id=%223374%22%20fill=%22white%22%3E%3C/path%3E%3C/svg%3E);
|
||||
}
|
||||
.c-collapsed{
|
||||
background-image: url(data:image/svg+xml;%20charset=utf8,%3Csvg%20t=%221606310454619%22%20class=%22icon%22%20viewBox=%220%200%201024%201024%22%20version=%221.1%22%20xmlns=%22http://www.w3.org/2000/svg%22%20p-id=%223662%22%20width=%22128%22%20height=%22128%22%3E%3Cpath%20d=%22M853.333333%20554.666667H170.666667c-23.466667%200-42.666667-19.2-42.666667-42.666667s19.2-42.666667%2042.666667-42.666667h682.666666c23.466667%200%2042.666667%2019.2%2042.666667%2042.666667s-19.2%2042.666667-42.666667%2042.666667z%22%20p-id=%223663%22%20fill=%22white%22%3E%3C/path%3E%3C/svg%3E);
|
||||
}
|
||||
.c-expand-positon-left{
|
||||
margin-top:-8px;
|
||||
margin-left:-18px;
|
||||
}
|
||||
.c-expand-positon-right{
|
||||
height:100%;
|
||||
width:100%;
|
||||
justify-content: center;
|
||||
}
|
||||
.c-expand-positon-right span{
|
||||
margin-top:-18px;
|
||||
margin-right:-18px;
|
||||
margin-left:100%;
|
||||
}
|
||||
.c-expand-positon-bottom{
|
||||
height:100%;
|
||||
width:100%;
|
||||
margin-top:7px;
|
||||
margin-left:-8px;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
.c-expand-positon-top{
|
||||
height:18px;
|
||||
width:100%;
|
||||
margin-top:-20px;
|
||||
margin-left:-6px;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
@keyframes ACTRGNodeInit {
|
||||
from {
|
||||
box-shadow: 0px 0px 15px #FD8B37;
|
||||
}
|
||||
15% {
|
||||
box-shadow: 0px 0px 1px rgb(46, 78, 143);
|
||||
}
|
||||
30% {
|
||||
box-shadow: 0px 0px 15px #FD8B37;
|
||||
}
|
||||
45% {
|
||||
box-shadow: 0px 0px 1px rgb(46, 78, 143);
|
||||
}
|
||||
60% {
|
||||
box-shadow: 0px 0px 15px #FD8B37;
|
||||
}
|
||||
to {
|
||||
box-shadow: 0px 0px 1px rgb(46, 78, 143);
|
||||
}
|
||||
}
|
||||
.rel-diy-node{
|
||||
padding:0px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,249 @@
|
||||
/* eslint-disable */
|
||||
const SeeksStoreManager = {
|
||||
createDefaultConfig(userGraphSetting) {
|
||||
var _graphSetting = {
|
||||
instanceId: 'SeeksGraph',
|
||||
debug: true,
|
||||
allowShowSettingPanel: false,
|
||||
backgrounImage: '',
|
||||
disableZoom: false,
|
||||
disableDragNode: false,
|
||||
moveToCenterWhenResize: true,
|
||||
defaultFocusRootNode: true,
|
||||
allowShowZoomMenu: true,
|
||||
backgrounImageNoRepeat: false,
|
||||
allowShowMiniToolBar: true,
|
||||
allowShowMiniView: false,
|
||||
allowShowMiniNameFilter: true,
|
||||
fullscreen: false,
|
||||
allowSwitchLineShape: false,
|
||||
allowSwitchJunctionPoint: false,
|
||||
isMoveByParentNode: false,
|
||||
checkedNodeId: '',
|
||||
checkedLineId: '',
|
||||
layouts: [],
|
||||
layoutLabel: '',
|
||||
layoutName: 'tree',
|
||||
layoutClassName: '',
|
||||
layoutDirection: 'h',
|
||||
defaultExpandHolderPosition: 'hide',
|
||||
autoLayouting: false,
|
||||
layouter: undefined,
|
||||
allowAutoLayoutIfSupport: true,
|
||||
isNeedShowAutoLayoutButton: false,
|
||||
canvasZoom: 100,
|
||||
defaultNodeColor: '#67C23A',
|
||||
defaultNodeFontColor: '#ffffff',
|
||||
defaultNodeBorderColor: '#90EE90',
|
||||
defaultNodeBorderWidth: 5,
|
||||
defaultLineColor: '#dddddd',
|
||||
defaultLineWidth: 1,
|
||||
defaultLineShape: 1,
|
||||
defaultNodeShape: 0,
|
||||
defaultNodeWidth: undefined,
|
||||
defaultNodeHeight: undefined,
|
||||
defaultShowLineLabel: true,
|
||||
showSingleNode: true,
|
||||
showNodeLabel: true,
|
||||
showNodeShortLabel: true,
|
||||
hideNodeContentByZoom: false,
|
||||
defaultJunctionPoint: 'border',
|
||||
viewSize: { width: 300, height: 300 },
|
||||
viewELSize: { width: 1300, height: 800, left: 0, top: 100 },
|
||||
viewNVInfo: { width: 1300, height: 800, x: 0, y: 100 },
|
||||
canvasNVInfo: { width: 1300, height: 800, x: 0, y: 100 },
|
||||
// NMViewCenter: { x: 0, y: 0 },
|
||||
// NMCanvasCenter: { x: 0, y: 0 },
|
||||
defaultLineMarker: {
|
||||
markerWidth: 12,
|
||||
markerHeight: 12,
|
||||
refX: 6,
|
||||
refY: 6,
|
||||
color: undefined,
|
||||
data: 'M2,2 L10,6 L2,10 L6,6 L2,2'
|
||||
},
|
||||
// defaultLineMarker: {
|
||||
// markerWidth: 6,
|
||||
// markerHeight: 6,
|
||||
// refX: 3,
|
||||
// refY: 3,
|
||||
// color: undefined,
|
||||
// data: 'M 0 0, V 6, L 4 3, Z'
|
||||
// },
|
||||
// defaultLineMarker: { // 另一种箭头样式
|
||||
// markerWidth: 15,
|
||||
// markerHeight: 15,
|
||||
// refX: 50,
|
||||
// refY: 7,
|
||||
// color: '#128bed',
|
||||
// data: 'M 14 7 L 1 .3 L 4 7 L .4 13 L 14 7, Z'
|
||||
// },
|
||||
canvasSize: {
|
||||
width: 2000,
|
||||
height: 2000
|
||||
},
|
||||
canvasOffset: {
|
||||
x: 25,
|
||||
y: 27,
|
||||
zoom_buff_x: 0,
|
||||
zoom_buff_y: 0
|
||||
},
|
||||
resetViewSize: (config) => {
|
||||
// config.canvasOffset.x = parseInt(config.viewSize.width - config.canvasSize.width) / 2
|
||||
// config.canvasOffset.y = parseInt(config.viewSize.height - config.canvasSize.height) / 2
|
||||
config.canvasOffset.x = config.viewNVInfo.width / 2 - 100
|
||||
config.canvasOffset.y = config.viewNVInfo.height / 2 - 100
|
||||
}
|
||||
}
|
||||
var _debug = userGraphSetting.debug !== true ? false : true
|
||||
if (_debug) console.log('user instance graphSetting:', userGraphSetting)
|
||||
if (window) {
|
||||
window.SeeksGraphDebug = _debug
|
||||
}
|
||||
if (userGraphSetting) {
|
||||
Object.keys(userGraphSetting).forEach(key => {
|
||||
var _thisUserValue = userGraphSetting[key]
|
||||
if (typeof _thisUserValue === 'object') {
|
||||
if (window.SeeksGraphDebug) console.log('user setting object:', key, _thisUserValue)
|
||||
var _objectValue = _graphSetting[key]
|
||||
if (_objectValue) {
|
||||
if (_objectValue && !Array.isArray(_objectValue) && _thisUserValue) {
|
||||
Object.keys(_objectValue).forEach(l2Key => {
|
||||
if (window.SeeksGraphDebug) console.log(' user setting:', key + '.' + l2Key, _thisUserValue[l2Key])
|
||||
_objectValue[l2Key] = _thisUserValue[l2Key]
|
||||
})
|
||||
} else if(Array.isArray(_objectValue)) {
|
||||
if (window.SeeksGraphDebug) console.log(' user setting array:', key, 'size:', _thisUserValue.length)
|
||||
var _new_arr = []
|
||||
_thisUserValue.forEach(thisItem => {
|
||||
if (window.SeeksGraphDebug) console.log(' user setting array:', key, 'push:', thisItem)
|
||||
if (thisItem && typeof thisItem === 'object') {
|
||||
_new_arr.push(JSON.parse(JSON.stringify(thisItem)))
|
||||
} else {
|
||||
_new_arr.push(thisItem)
|
||||
}
|
||||
})
|
||||
_graphSetting[key] = _new_arr
|
||||
// if (window.SeeksGraphDebug) console.log(' user setting array:', key, 'copy size:', _new_arr.length)
|
||||
} else {
|
||||
if (window.SeeksGraphDebug) console.log('user setting value:', key)
|
||||
_graphSetting[key] = _thisUserValue
|
||||
}
|
||||
} else {
|
||||
console.log('ignore option:', key)
|
||||
}
|
||||
} else {
|
||||
if (window.SeeksGraphDebug) console.log('user setting:', key, _thisUserValue)
|
||||
_graphSetting[key] = _thisUserValue
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!_graphSetting.layouts || _graphSetting.layouts.length === 0) {
|
||||
_graphSetting.layouts = [{
|
||||
label: '中心',
|
||||
layoutName: 'center',
|
||||
layoutDirection: 'v',
|
||||
defaultExpandHolderPosition: 'hide',
|
||||
defaultNodeShape: 0,
|
||||
defaultLineShape: 1,
|
||||
defaultJunctionPoint: 'border'
|
||||
}]
|
||||
}
|
||||
if (!Array.isArray(_graphSetting.layouts)) {
|
||||
_graphSetting.layouts = [_graphSetting.layouts]
|
||||
}
|
||||
_graphSetting.layouts.forEach(thisLayout => {
|
||||
SeeksStoreManager.appendDefaultOptions4Layout(thisLayout)
|
||||
})
|
||||
return _graphSetting
|
||||
},
|
||||
appendDefaultOptions4Layout(thisLayout) {
|
||||
if (thisLayout.useLayoutStyleOptions === undefined) thisLayout.useLayoutStyleOptions = false
|
||||
if (thisLayout.defaultNodeColor === undefined) thisLayout.defaultNodeColor = '#FFC5A6'
|
||||
if (thisLayout.defaultNodeFontColor === undefined) thisLayout.defaultNodeFontColor = '#000000'
|
||||
if (thisLayout.defaultNodeBorderColor === undefined) thisLayout.defaultNodeBorderColor = '#efefef'
|
||||
if (thisLayout.defaultNodeBorderWidth === undefined) thisLayout.defaultNodeBorderWidth = 1
|
||||
if (thisLayout.defaultLineColor === undefined) thisLayout.defaultLineColor = '#FD8B37'
|
||||
if (thisLayout.defaultLineWidth === undefined) thisLayout.defaultLineWidth = 1
|
||||
// if (thisLayout.defaultLineShape === undefined) thisLayout.defaultLineShape = 2
|
||||
// if (thisLayout.defaultNodeShape === undefined) thisLayout.defaultNodeShape = 1
|
||||
if (thisLayout.defaultNodeWidth === undefined) thisLayout.defaultNodeWidth = undefined
|
||||
if (thisLayout.defaultNodeHeight === undefined) thisLayout.defaultNodeHeight = undefined
|
||||
if (thisLayout.defaultShowLineLabel === undefined) thisLayout.defaultShowLineLabel = true
|
||||
if (thisLayout.defaultExpandHolderPosition === undefined) thisLayout.defaultExpandHolderPosition = undefined
|
||||
if (thisLayout.defaultJunctionPoint === undefined) thisLayout.defaultJunctionPoint = undefined
|
||||
if (thisLayout.defaultLineMarker === undefined) {
|
||||
thisLayout.defaultLineMarker = {
|
||||
markerWidth: 12,
|
||||
markerHeight: 12,
|
||||
refX: 6,
|
||||
refY: 6,
|
||||
color: undefined,
|
||||
data: 'M2,2 L10,6 L2,10 L6,6 L2,2'
|
||||
}
|
||||
}
|
||||
if (thisLayout.layoutName === 'SeeksCenterLayouter' || thisLayout.layoutName === 'center') {
|
||||
if (thisLayout.label === undefined) thisLayout.label = '中心'
|
||||
if (thisLayout.layoutClassName === undefined) thisLayout.layoutClassName = 'seeks-layout-' + thisLayout.layoutName
|
||||
if (thisLayout.defaultNodeShape === undefined) thisLayout.defaultNodeShape = 0
|
||||
if (thisLayout.defaultLineShape === undefined) thisLayout.defaultLineShape = 1
|
||||
if (thisLayout.defaultExpandHolderPosition === undefined) thisLayout.defaultExpandHolderPosition = 'hide'
|
||||
if (thisLayout.defaultJunctionPoint === undefined) thisLayout.defaultJunctionPoint = 'border'
|
||||
if (thisLayout.layoutDirection === undefined) thisLayout.layoutDirection = 'h'
|
||||
if (thisLayout.centerOffset_x === undefined) thisLayout.centerOffset_x = 0
|
||||
if (thisLayout.centerOffset_y === undefined) thisLayout.centerOffset_y = 0
|
||||
if (thisLayout.levelDistance === undefined) thisLayout.levelDistance = ''
|
||||
if (thisLayout.min_per_width === undefined) thisLayout.min_per_width = 30
|
||||
if (thisLayout.max_per_width === undefined) thisLayout.max_per_width = 200
|
||||
if (thisLayout.min_per_height === undefined) thisLayout.min_per_height = 100
|
||||
if (thisLayout.max_per_height === undefined) thisLayout.max_per_height = 500
|
||||
} else if (thisLayout.layoutName === 'SeeksBidirectionalTreeLayouter' || thisLayout.layoutName === 'tree') {
|
||||
if (thisLayout.label === undefined) thisLayout.label = '树状'
|
||||
if (thisLayout.layoutClassName === undefined) thisLayout.layoutClassName = 'seeks-layout-' + thisLayout.layoutName
|
||||
if (thisLayout.defaultNodeShape === undefined) thisLayout.defaultNodeShape = 1
|
||||
if (thisLayout.defaultLineShape === undefined) thisLayout.defaultLineShape = 2
|
||||
if (thisLayout.defaultExpandHolderPosition === undefined) thisLayout.defaultExpandHolderPosition = 'hide'
|
||||
if (thisLayout.defaultJunctionPoint === undefined) thisLayout.defaultJunctionPoint = 'ltrb'
|
||||
if (thisLayout.layoutDirection === undefined) thisLayout.layoutDirection = 'h'
|
||||
if (thisLayout.centerOffset_x === undefined) thisLayout.centerOffset_x = 0
|
||||
if (thisLayout.centerOffset_y === undefined) thisLayout.centerOffset_y = 0
|
||||
if (thisLayout.from === undefined) thisLayout.from = 'top'
|
||||
if (thisLayout.levelDistance === undefined) thisLayout.levelDistance = ''
|
||||
if (thisLayout.min_per_width === undefined) thisLayout.min_per_width = 30
|
||||
if (thisLayout.max_per_width === undefined) thisLayout.max_per_width = 200
|
||||
if (thisLayout.min_per_height === undefined) thisLayout.min_per_height = 100
|
||||
if (thisLayout.max_per_height === undefined) thisLayout.max_per_height = 500
|
||||
if (thisLayout.from === 'top' || thisLayout.from === 'bottom') thisLayout.layoutDirection = 'v'
|
||||
}
|
||||
},
|
||||
createNewStore(userGraphSetting) {
|
||||
if (window.SeeksGraphDebug) console.log('Create new GraphSetting:')
|
||||
var _graphSetting = SeeksStoreManager.createDefaultConfig(userGraphSetting)
|
||||
return new SeeksRGStore(_graphSetting)
|
||||
}
|
||||
}
|
||||
function SeeksRGStore(_graphSetting) {
|
||||
this.graphSetting = _graphSetting
|
||||
this.resetViewSize = function() {
|
||||
// state.graphSetting.canvasOffset.x = parseInt(state.graphSetting.viewSize.width - state.graphSetting.canvasSize.width) / 2
|
||||
// state.graphSetting.canvasOffset.y = parseInt(state.graphSetting.viewSize.height - state.graphSetting.canvasSize.height) / 2
|
||||
this.graphSetting.canvasOffset.x = 0 // state.graphSetting.viewNVInfo.width / 2 - 100
|
||||
this.graphSetting.canvasOffset.y = 0 // state.graphSetting.viewNVInfo.height / 2 - 100
|
||||
// console.log('resetViewSize:', state.graphSetting.viewSize.width, state.graphSetting.canvasSize.width, state.graphSetting.canvasZoom / 100, state.graphSetting.canvasSize.width * (state.graphSetting.canvasZoom / 100), state.graphSetting.canvasOffset.x)
|
||||
}
|
||||
this.getOptions = function() {
|
||||
var _options = {}
|
||||
var _ignore = [
|
||||
'layouter', 'autoLayouting', 'canvasNVInfo', 'canvasOffset', 'canvasZoom', 'fullscreen', 'instanceId', 'layoutClassName', 'layoutDirection',
|
||||
'layoutLabel', 'layoutName', 'resetViewSize', 'viewELSize', 'viewNVInfo', 'viewSize', 'canvasSize'
|
||||
]
|
||||
Object.keys(this.graphSetting).forEach(thisKey => {
|
||||
if (_ignore.indexOf(thisKey) === -1) {
|
||||
_options[thisKey] = this.graphSetting[thisKey]
|
||||
}
|
||||
})
|
||||
return _options
|
||||
}
|
||||
console.log('relation-graph instance full option:', this.getOptions())
|
||||
}
|
||||
export default SeeksStoreManager
|
||||
@@ -0,0 +1,170 @@
|
||||
/* eslint-disable */
|
||||
var __tmp_basePosition = { x: 0, y: 0 }
|
||||
var __tmp_positionModel = { x: 0, y: 0 }
|
||||
var __ondraged
|
||||
var __start_info = { x: 0, y: 0 }
|
||||
var SeeksRGUtils = {
|
||||
startDrag(e, positionModel, ondraged) {
|
||||
__ondraged = ondraged
|
||||
// console.log('startDrag:', __tmp_basePosition, e.clientX, e.clientY)
|
||||
__tmp_positionModel = positionModel
|
||||
__start_info.x = __tmp_positionModel.x
|
||||
__start_info.y = __tmp_positionModel.y
|
||||
__tmp_basePosition.x = parseInt(__tmp_positionModel.x) - e.clientX
|
||||
__tmp_basePosition.y = parseInt(__tmp_positionModel.y) - e.clientY
|
||||
document.body.addEventListener('mousemove', SeeksRGUtils.onNodeMove)
|
||||
document.body.addEventListener('mouseup', SeeksRGUtils.onNodeDragend)
|
||||
},
|
||||
onNodeMove(e) {
|
||||
// console.log('move', __tmp_basePosition, e.clientX, e.clientY)
|
||||
__tmp_positionModel.x = e.clientX + __tmp_basePosition.x
|
||||
__tmp_positionModel.y = e.clientY + __tmp_basePosition.y
|
||||
},
|
||||
onNodeDragend() {
|
||||
// console.log('onNodeDragend', __tmp_positionModel.x - __start_info.x, __tmp_positionModel.y - __start_info.y)
|
||||
document.body.removeEventListener('mousemove', SeeksRGUtils.onNodeMove)
|
||||
document.body.removeEventListener('mouseup', SeeksRGUtils.onNodeDragend)
|
||||
if (__ondraged) {
|
||||
__ondraged(__tmp_positionModel.x - __start_info.x, __tmp_positionModel.y - __start_info.y)
|
||||
}
|
||||
},
|
||||
transName4Circle(name) {
|
||||
var _thisLevel = 0
|
||||
var _thisLevelCharsArr = []
|
||||
var result = []
|
||||
for (var i = 0; i < name.length; i++) {
|
||||
_thisLevelCharsArr.push(name[i])
|
||||
if (_thisLevelCharsArr.length === circle_node_text_set[_thisLevel]) {
|
||||
result.push(_thisLevelCharsArr.join(''))
|
||||
_thisLevel++
|
||||
_thisLevelCharsArr = []
|
||||
}
|
||||
}
|
||||
if (_thisLevelCharsArr.length > 0) {
|
||||
result.push(_thisLevelCharsArr.join(''))
|
||||
}
|
||||
// if (result.length < 3) {
|
||||
// result.unshift('')
|
||||
// if (result.length < 3) {
|
||||
// result.unshift('')
|
||||
// if (result.length < 3) {
|
||||
// result.unshift('')
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return result.join('<br>')
|
||||
},
|
||||
getColorId(color) {
|
||||
color = color.replace('#', '')
|
||||
color = color.replace('(', '')
|
||||
color = color.replace(')', '')
|
||||
color = color.replace(/,/, '-')
|
||||
return color
|
||||
}
|
||||
}
|
||||
SeeksRGUtils.json2Node = function(originData) {
|
||||
if (originData.id === undefined) throw Error('node must has option[id]:', originData)
|
||||
originData.text = originData.text || originData.name || originData.id
|
||||
var jsonData = {
|
||||
id: originData.id,
|
||||
text: originData.text !== undefined ? originData.text : '',
|
||||
type: originData.type !== undefined ? originData.type : 'node',
|
||||
isShow: originData.isShow !== undefined ? originData.isShow : true,
|
||||
isHide: originData.isHide !== undefined ? originData.isHide : false,
|
||||
expanded: originData.expanded !== undefined ? originData.expanded : true,
|
||||
selected: originData.selected !== undefined ? originData.selected : false,
|
||||
styleClass: originData.styleClass !== undefined ? originData.styleClass : '',
|
||||
targetNodes: originData.targetNodes !== undefined ? originData.targetNodes : [],
|
||||
targetFrom: originData.targetFrom !== undefined ? originData.targetFrom : [],
|
||||
targetTo: originData.targetTo !== undefined ? originData.targetTo : [],
|
||||
nodeShape: originData.nodeShape !== undefined ? originData.nodeShape : undefined,
|
||||
borderWidth: originData.borderWidth !== undefined ? originData.borderWidth : undefined,
|
||||
borderColor: originData.borderColor !== undefined ? originData.borderColor : undefined,
|
||||
fontColor: originData.fontColor !== undefined ? originData.fontColor : undefined,
|
||||
color: originData.color !== undefined ? originData.color : undefined,
|
||||
opacity: originData.opacity !== undefined ? originData.opacity : 1,
|
||||
fixed: originData.fixed !== undefined ? originData.fixed : false,
|
||||
width: originData.width !== undefined ? originData.width : undefined,
|
||||
height: originData.height !== undefined ? originData.height : undefined,
|
||||
x: originData.x !== undefined ? originData.x : 0,
|
||||
y: originData.y !== undefined ? originData.y : 0,
|
||||
Fx: originData.Fx !== undefined ? originData.Fx : 0,
|
||||
Fy: originData.Fy !== undefined ? originData.Fy : 0,
|
||||
offset_x: originData.offset_x !== undefined ? originData.offset_x : 0,
|
||||
offset_y: originData.offset_y !== undefined ? originData.offset_y : 0,
|
||||
expandHolderPosition: originData.expandHolderPosition !== undefined ? originData.expandHolderPosition : undefined,
|
||||
innerHTML: originData.innerHTML !== undefined ? originData.innerHTML : undefined,
|
||||
html: originData.html !== undefined ? originData.html : undefined,
|
||||
disableDefaultClickEffect: originData.disableDefaultClickEffect !== undefined ? originData.disableDefaultClickEffect : undefined,
|
||||
disableDrag: originData.disableDrag !== undefined ? originData.disableDrag : false,
|
||||
data: originData.data !== undefined ? originData.data : {}
|
||||
}
|
||||
if(jsonData.lot === undefined) jsonData.lot = { childs: [], parent: undefined, eached: false, strength: 0 }
|
||||
if(jsonData.lot.childs === undefined) jsonData.lot.childs = []
|
||||
if(jsonData.lot.parent === undefined) jsonData.lot.parent = undefined
|
||||
if(jsonData.lot.eached === undefined) jsonData.lot.eached = false
|
||||
if(jsonData.lot.strength === undefined) jsonData.lot.strength = 0
|
||||
if(jsonData.el === undefined) jsonData.el = { offsetWidth: 50, offsetHeight: 50 }
|
||||
if(jsonData.width !== undefined) jsonData.el.offsetWidth = jsonData.width
|
||||
if(jsonData.height !== undefined) jsonData.el.offsetHeight = jsonData.height
|
||||
return jsonData
|
||||
}
|
||||
SeeksRGUtils.json2Link = function(originData) {
|
||||
if (originData.from === undefined) throw Error('error,link must has option[from]:', originData)
|
||||
if (originData.to === undefined) throw Error('error,link must has option[to]:', originData)
|
||||
if (typeof originData.from !== 'string') throw Error('error link from, must be string:', originData)
|
||||
if (typeof originData.to !== 'string') throw Error('error link to, must be string:', originData)
|
||||
var jsonData = {
|
||||
text: originData.text !== undefined ? originData.text : '',
|
||||
color: originData.color !== undefined ? originData.color : undefined,
|
||||
fontColor: originData.fontColor !== undefined ? originData.fontColor : undefined,
|
||||
lineWidth: originData.lineWidth !== undefined ? originData.lineWidth : undefined,
|
||||
lineShape: originData.lineShape !== undefined ? originData.lineShape : undefined,
|
||||
styleClass: originData.styleClass !== undefined ? originData.styleClass : undefined,
|
||||
isHide: originData.isHide !== undefined ? originData.isHide : false,
|
||||
arrow: originData.arrow !== undefined ? originData.arrow : undefined,
|
||||
isHideArrow: originData.isHideArrow !== undefined ? originData.isHideArrow : undefined,
|
||||
hidden: originData.hidden !== undefined ? originData.hidden : false,
|
||||
lineDirection: originData.lineDirection !== undefined ? originData.lineDirection : undefined,
|
||||
reverseText: originData.reverseText !== undefined ? originData.reverseText : undefined,
|
||||
data: originData.data !== undefined ? originData.data : {},
|
||||
}
|
||||
return jsonData
|
||||
}
|
||||
|
||||
SeeksRGUtils.getPosition = function(el) {
|
||||
if (el.parentElement) {
|
||||
return SeeksRGUtils.getPosition(el.parentElement) + el.offsetTop
|
||||
}
|
||||
return el.offsetTop
|
||||
}
|
||||
var _ignore_node_keys = [ 'Fx', 'Fy', 'appended', 'el', 'targetFrom', 'targetNodes', 'targetTo', 'type', 'lot', 'seeks_id' ]
|
||||
SeeksRGUtils.transNodeToJson = function(node, nodes) {
|
||||
if (!node) return
|
||||
var _node_json = {}
|
||||
Object.keys(node).forEach(thisKey => {
|
||||
if (_ignore_node_keys.indexOf(thisKey) === -1) {
|
||||
if (node[thisKey] !== undefined) {
|
||||
_node_json[thisKey] = node[thisKey]
|
||||
}
|
||||
}
|
||||
})
|
||||
nodes.push(_node_json)
|
||||
}
|
||||
var _ignore_link_keys = [ 'arrow', 'id', 'reverseText', 'isReverse' ]
|
||||
SeeksRGUtils.transLineToJson = function(line, links) {
|
||||
if (!line) return
|
||||
line.relations.forEach(thisRelation => {
|
||||
var _link_json = {}
|
||||
Object.keys(thisRelation).forEach(thisKey => {
|
||||
if (_ignore_link_keys.indexOf(thisKey) === -1) {
|
||||
if (thisRelation[thisKey] !== undefined) {
|
||||
_link_json[thisKey] = thisRelation[thisKey]
|
||||
}
|
||||
}
|
||||
})
|
||||
links.push(_link_json)
|
||||
})
|
||||
}
|
||||
var circle_node_text_set = [4, 5, 6, 4, 2, 100]
|
||||
export default SeeksRGUtils
|
||||
@@ -0,0 +1,281 @@
|
||||
import SeeksGraphMath from '../SeeksGraphMath'
|
||||
|
||||
function SeeksAutoLayouter(layoutSetting, graphSetting) {
|
||||
this.graphSetting = graphSetting
|
||||
this.config = layoutSetting || {}
|
||||
this.rootNode = null
|
||||
this.allNodes = []
|
||||
this.__origin_nodes = []
|
||||
this.refresh = function() {
|
||||
this.placeNodes(this.__origin_nodes, this.rootNode)
|
||||
}
|
||||
this.placeNodes = function(allNodes, rootNode) {
|
||||
if (!rootNode) {
|
||||
console.log('root is null:', rootNode)
|
||||
return
|
||||
} else {
|
||||
if (window.SeeksGraphDebug) console.log('layout by root:', rootNode)
|
||||
}
|
||||
this.__origin_nodes = allNodes
|
||||
this.rootNode = rootNode
|
||||
allNodes.forEach(thisNode => {
|
||||
// thisNode.lot = { eached: false }
|
||||
thisNode.lot.eached = false
|
||||
thisNode.lot.notLeafNode = false
|
||||
thisNode.lot.childs = []
|
||||
// thisNode.lot.parent = undefined
|
||||
thisNode.lot.index_of_parent = 0
|
||||
thisNode.lot.strength = 0
|
||||
thisNode.lot.prevNode = undefined
|
||||
thisNode.lot.nextNode = undefined
|
||||
thisNode.lot.placed = false
|
||||
})
|
||||
this.allNodes = []
|
||||
var analyticResult = {
|
||||
max_deep: 1,
|
||||
max_length: 1
|
||||
}
|
||||
SeeksGraphMath.analysisNodes4Didirectional(this.allNodes, [this.rootNode], 0, analyticResult, 0)
|
||||
// console.log('analysisNodes:', analyticResult)
|
||||
// if (this.graphSetting.heightByContent) {
|
||||
// console.log('根据内容调整高度')
|
||||
// var __suitableHeight = analyticResult.max_deep * 2 * 300 + 500
|
||||
// this.graphSetting.viewSize.height = __suitableHeight
|
||||
// }
|
||||
if (window.SeeksGraphDebug) console.log('调整画布大小')
|
||||
// var __per_width = parseInt((__mapWidth - 10) / (analyticResult.max_deep + 2))
|
||||
// var __per_height = parseInt((__mapHeight - 10) / (analyticResult.max_length + 1))
|
||||
// console.log('per:', __per_width, __per_height)
|
||||
// var __level2_current_length = 0
|
||||
// this.allNodes.forEach(thisNode => {
|
||||
// if (thisNode.lot.subling.level === 1 && thisNode.lot.childs_size > 0) {
|
||||
// __level2_current_length += thisNode.lot.childs_size
|
||||
// var __thisNodeLength = __level2_current_length + parseInt((thisNode.lot.childs_size / 2).toFixed(0))
|
||||
// thisNode.lot.strength_plus = __level2_current_length
|
||||
// console.log('level2 parents:', thisNode.name, thisNode.lot.childs_size, { strength_plus: thisNode.lot.strength_plus, __thisNodeLength, strength: thisNode.lot.childs_size, __level2_current_length })
|
||||
// }
|
||||
// })
|
||||
// var __currentLevel = 0
|
||||
var __mapWidth = this.graphSetting.viewSize.width
|
||||
var __mapHeight = this.graphSetting.viewSize.height
|
||||
rootNode.lot.x = parseInt((__mapWidth - rootNode.el.offsetWidth) / 2)
|
||||
rootNode.lot.y = parseInt((__mapHeight - rootNode.el.offsetHeight) / 2)
|
||||
// this.rootNode.lot.x = 0
|
||||
// this.rootNode.lot.y = 0
|
||||
// if (this.rootNode.lot.y > 400) {
|
||||
// this.rootNode.lot.y = 400
|
||||
// }
|
||||
if (window.SeeksGraphDebug) console.log('[layout canvasOffset]', this.graphSetting.viewSize, this.graphSetting.canvasSize)
|
||||
this.placeRelativePosition(this.rootNode)
|
||||
this.allNodes.forEach(thisNode => {
|
||||
if (thisNode.fixed === true) return
|
||||
if (!SeeksGraphMath.isAllowShowNode(thisNode)) return
|
||||
thisNode.x = thisNode.lot.x
|
||||
thisNode.y = thisNode.lot.y
|
||||
thisNode.lot.placed = true
|
||||
})
|
||||
// var __graphIndex = 1
|
||||
// allNodes.forEach(thisNode => {
|
||||
// // thisNode.lot = { eached: false }
|
||||
// if (!SeeksGraphMath.isAllowShowNode(thisNode)) return
|
||||
// if (thisNode.lot.placed === false) {
|
||||
// this.allNodes = []
|
||||
// var analyticResult = {
|
||||
// max_deep: 1,
|
||||
// max_length: 1
|
||||
// }
|
||||
// SeeksGraphMath.analysisNodes(this.allNodes, [thisNode], 0, analyticResult, { prettyLevelPosition: this.graphSetting.prettyLevelPosition })
|
||||
// thisNode.lot.x = this.rootNode.lot.x
|
||||
// thisNode.lot.y = this.rootNode.lot.y + (__graphIndex++ * 1200)
|
||||
// this.graphSetting.canvasSize.height += 1200
|
||||
// this.placeRelativePosition(thisNode)
|
||||
// this.allNodes.forEach(thisNode => {
|
||||
// thisNode.x = thisNode.lot.x - __offsetX
|
||||
// thisNode.y = thisNode.lot.y - __offsetY
|
||||
// thisNode.lot.placed = true
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
if (window.SeeksGraphDebug) console.log('Start Auto Layout.....')
|
||||
this.autoLayout(true)
|
||||
// console.log('layout from root:', analyticResult.max_deep, analyticResult.max_length)
|
||||
// rootNode.x = (this.graphSetting.canvasSize.width - this.graphSetting.nodeSize.width) / 2
|
||||
// rootNode.y = (this.graphSetting.canvasSize.height - this.graphSetting.nodeSize.height) / 2
|
||||
// rootNode.placed = true
|
||||
// // rootNode.name = rootNode.x + ',' + rootNode.y
|
||||
// var newLevelNodes = []
|
||||
// newLevelNodes.push(rootNode)
|
||||
// this.setPlace(newLevelNodes, 0, rootNode)
|
||||
}
|
||||
this.placeRelativePosition = function(rootNode) {
|
||||
var __level1_r = 80
|
||||
this.allNodes.forEach(thisNode => {
|
||||
if (thisNode.lot.subling.level === 1) {
|
||||
__level1_r = thisNode.lot.subling.all_size * 20 / Math.PI / 2
|
||||
if (__level1_r < 80)__level1_r = 80
|
||||
// if (__level1_r > 500)__level1_r = 500
|
||||
const _point = SeeksGraphMath.getOvalPoint(rootNode.lot.x, rootNode.lot.y, thisNode.lot.subling.level * __level1_r, thisNode.lot.strength_plus - (thisNode.lot.strength / 2), thisNode.lot.subling.all_strength)
|
||||
// const _point = SeeksGraphMath.getOvalPoint(this.rootNode.x, this.rootNode.y, thisNode.lot.subling.level * __level1_r, thisNode.lot.index_of_level, thisNode.lot.subling.all_size)
|
||||
thisNode.lot.x = _point.x
|
||||
thisNode.lot.y = _point.y
|
||||
}
|
||||
})
|
||||
this.allNodes.forEach(thisNode => {
|
||||
if (thisNode.lot.subling.level > 1) {
|
||||
var __area_start = thisNode.lot.parent.lot.strength_plus - thisNode.lot.parent.lot.strength
|
||||
var __area_end = thisNode.lot.parent.lot.strength_plus
|
||||
var __buff = (__area_end - __area_start) / (thisNode.lot.parent.lot.childs_size + 1) * (thisNode.lot.index_of_parent + 1)
|
||||
const _point = SeeksGraphMath.getOvalPoint(rootNode.lot.x, rootNode.lot.y, (thisNode.lot.subling.level - 1) * 80 + __level1_r, __area_start + __buff, thisNode.lot.parent.lot.subling.all_strength)
|
||||
thisNode.lot.x = _point.x
|
||||
thisNode.lot.y = _point.y
|
||||
}
|
||||
})
|
||||
}
|
||||
this.layoutTimes = 0
|
||||
// var ___this = this
|
||||
this.autoLayout = function(forceLayout) {
|
||||
if (forceLayout) {
|
||||
this.layoutTimes = 0
|
||||
}
|
||||
if (window.SeeksGraphDebug) console.log('this.layoutTimes:', this.layoutTimes)
|
||||
if (this.layoutTimes > 300) {
|
||||
this.graphSetting.autoLayouting = false
|
||||
return
|
||||
}
|
||||
this.layoutTimes++
|
||||
this.__origin_nodes.forEach(thisNode => {
|
||||
thisNode.Fx = 0
|
||||
thisNode.Fy = 0
|
||||
})
|
||||
var __by_node = true // parseInt(this.layoutTimes / 10) % 2 === 1
|
||||
var __by_line = true // parseInt(this.layoutTimes / 10) % 2 === 0
|
||||
if (__by_node) {
|
||||
for (const i in this.__origin_nodes) {
|
||||
var __node1 = this.__origin_nodes[i]
|
||||
// if (__node1.text === '宣洁')console.log('宣洁:', __node1.x, __node1.y)
|
||||
if (__node1.lot.placed === true) {
|
||||
// var __thisNode = this.__origin_nodes[i]
|
||||
// __thisNode.targetNodes.forEach(thisTN_level1 => {
|
||||
// this.addGravityByNode(__thisNode, thisTN_level1)
|
||||
// thisTN_level1.targetNodes.forEach(thisTN_level2 => {
|
||||
// this.addGravityByNode(__thisNode, thisTN_level2)
|
||||
// })
|
||||
// })
|
||||
// 循环点,综合点与其他所有点点斥力及方向
|
||||
for (var j in this.__origin_nodes) {
|
||||
var __node2 = this.__origin_nodes[j]
|
||||
if (__node2.lot.placed === true) {
|
||||
// 循环点,计算i点与j点点斥力及方向
|
||||
if (i !== j) {
|
||||
// if (this.allNodes[i].lot.level === this.allNodes[j].lot.level) {
|
||||
this.addGravityByNode(__node1, __node2)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (__by_line) {
|
||||
for (const i in this.__origin_nodes) {
|
||||
// 循环线,设置每个点承受点力及力点方向
|
||||
if (this.__origin_nodes[i].lot.parent) {
|
||||
this.addElasticByLine(this.__origin_nodes[i].lot.parent, this.__origin_nodes[i])
|
||||
// break
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (this.layoutTimes % 5 === 0) { // 为提高布局效率,计算五次后更新位置
|
||||
for (const i in this.__origin_nodes) {
|
||||
this.applyToNodePosition(this.__origin_nodes[i])
|
||||
}
|
||||
// }
|
||||
window.setTimeout(function() { this.autoLayout() }.bind(this), 30)
|
||||
}
|
||||
this.stop = function() {
|
||||
this.layoutTimes = 1000
|
||||
}
|
||||
this.addElasticByLine = function(node1, node2) {
|
||||
var length = Math.sqrt(Math.pow((node1.y - node2.y), 2) + Math.pow((node1.x - node2.x), 2))
|
||||
if (length > 1000) {
|
||||
length = 1000
|
||||
}
|
||||
var Kf = length < 30 ? 0 : ((length - 30) * 0.05)
|
||||
var Kf_1 = Kf
|
||||
var Kf_2 = Kf
|
||||
// var Kf_1 = Kf / node1.lot.childs.length
|
||||
// var Kf_2 = Kf / node2.lot.childs.length
|
||||
var _buff_x = (node1.x - node2.x) / length
|
||||
var _buff_y = (node1.y - node2.y) / length
|
||||
this.addFtoNode(node1, _buff_x * Kf_1 * -1, _buff_y * Kf_1 * -1, 1)
|
||||
this.addFtoNode(node2, _buff_x * Kf_2, _buff_y * Kf_2, 1)
|
||||
}
|
||||
this.addGravityByNode = function(node1, node2) {
|
||||
var length = Math.sqrt(Math.pow((node1.y - node2.y), 2) + Math.pow((node1.x - node2.x), 2))
|
||||
var zero_length = 300
|
||||
var Kf = length > zero_length ? 0 : ((zero_length - length) * 0.03)
|
||||
if (zero_length < 30) {
|
||||
Kf = Kf * 100
|
||||
}
|
||||
// if (length < 100)Kf = Kf * 2
|
||||
var _buff_x = (node1.x - node2.x) / length
|
||||
var _buff_y = (node1.y - node2.y) / length
|
||||
// if (_buff_x < 30)_buff_x = 1
|
||||
// if (_buff_y < 30)_buff_y = 1
|
||||
// console.log({ Kf, _buff_x, _buff_y, zero_length })
|
||||
this.addFtoNode(node1, _buff_x * Kf, _buff_y * Kf, 0)
|
||||
this.addFtoNode(node2, _buff_x * Kf * -1, _buff_y * Kf * -1, 0)
|
||||
}
|
||||
this.getNodeFWeight = function(node) {
|
||||
var level = node.lot.level
|
||||
if (level > 7)level = 7
|
||||
if (level < 0)level = 0
|
||||
return (8 - level) / 8
|
||||
}
|
||||
this.addFtoNode = function(node, x, y) {
|
||||
// console.log('Add F:', node.text, type, parseInt(x), parseInt(y))
|
||||
if (isNaN(x) || isNaN(y)) {
|
||||
return
|
||||
}
|
||||
x = x / node.lot.strength
|
||||
y = y / node.lot.strength
|
||||
if (x > 50)x = 50
|
||||
if (y > 50)y = 50
|
||||
if (x < -50)x = -50
|
||||
if (y < -50)y = -50
|
||||
// if (isNaN(node.Fx)) {
|
||||
// if (node.text === '宣洁')console.log('宣洁!!!NaN B buff x:', x, node.lot.strength, node)
|
||||
// }
|
||||
node.Fx += x
|
||||
node.Fy += y
|
||||
// if (isNaN(node.Fx)) {
|
||||
// if (node.text === '宣洁')console.log('宣洁!!!NaN A buff x:', x, node.lot.strength, node)
|
||||
// }
|
||||
}
|
||||
this.applyToNodePosition = function(node) {
|
||||
// if (!node.lot.childs || node.lot.childs.length === 0) {
|
||||
// return
|
||||
// }
|
||||
// if (node.lot.level === 0) {
|
||||
// return
|
||||
// }
|
||||
// console.log('F add:', node.name, node.Fx, node.Fy)
|
||||
const __buff_x = parseInt(node.Fx)
|
||||
const __buff_y = parseInt(node.Fy)
|
||||
// console.log('F add:2:', node.name, __buff_x, __buff_y)
|
||||
node.x = node.x + __buff_x
|
||||
node.y = node.y + __buff_y
|
||||
// if (isNaN(node.x)) {
|
||||
// if (node.text === '宣洁')console.log('!!!NaN x:', node.text, __buff_x, node.Fx, node)
|
||||
// }
|
||||
// node.name = __buff_x + ',' + __buff_y
|
||||
// if (node.id === '8') {
|
||||
// console.log(node.id, __buff_x, __buff_y)
|
||||
// // console.log(node.x, node.y)
|
||||
// }
|
||||
node.Fx = 0
|
||||
node.Fy = 0
|
||||
}
|
||||
}
|
||||
|
||||
export default SeeksAutoLayouter
|
||||
@@ -0,0 +1,360 @@
|
||||
import SeeksGraphMath from '../SeeksGraphMath'
|
||||
|
||||
function SeeksBidirectionalTreeLayouter(layoutSetting, graphSetting) {
|
||||
this.graphSetting = graphSetting
|
||||
this.config = layoutSetting || {}
|
||||
console.log('new SeeksBidirectionalTreeLayouter:', this.config)
|
||||
if (!this.config.from) this.config.from = 'left'
|
||||
if (this.config.levelDistance && typeof this.config.levelDistance === 'string') {
|
||||
this.config.levelDistanceArr = this.config.levelDistance.split(',').map(thisNum => parseInt(thisNum))
|
||||
}
|
||||
this.rootNode = null
|
||||
this.allNodes = []
|
||||
this.__origin_nodes = []
|
||||
this.refresh = function() {
|
||||
console.log('SeeksBidirectionalTreeLayouter:refresh:nodes:', this.__origin_nodes.length)
|
||||
this.placeNodes(this.__origin_nodes, this.rootNode)
|
||||
}
|
||||
this.analysisNodes4Didirectional = function(willLayoutNodes, thisLevelNodes, thisDeep, analyticResult, levelDirect) {
|
||||
if (thisLevelNodes.length > analyticResult.max_length) {
|
||||
analyticResult.max_length = thisLevelNodes.length
|
||||
}
|
||||
if (thisDeep > analyticResult.max_deep) {
|
||||
analyticResult.max_deep = thisDeep
|
||||
}
|
||||
var __thisLOT_subling = {
|
||||
level: thisDeep,
|
||||
all_size: thisLevelNodes.length,
|
||||
all_strength: 0
|
||||
}
|
||||
var newLevelNodes = []
|
||||
thisLevelNodes.forEach(thisNode => {
|
||||
if (!thisNode.lot)thisNode.lot = {}
|
||||
thisNode.lot.eached = true
|
||||
thisNode.lot.subling = __thisLOT_subling
|
||||
thisNode.lot.level = thisDeep
|
||||
willLayoutNodes.push(thisNode)
|
||||
})
|
||||
var __thisLevel_index = 0
|
||||
// var __prev_node
|
||||
thisLevelNodes.forEach(thisNode => {
|
||||
var __thisNode_child_size = 0
|
||||
// console.log('Build node::', thisNode.text, thisNode.targetNodes.length)
|
||||
if (levelDirect === -1) {
|
||||
// console.log('Build node::from::', thisNode.text, thisNode.targetFrom.length)
|
||||
let __thisTargetIndex = 0
|
||||
thisNode.targetFrom.forEach((thisTarget) => {
|
||||
if (!thisTarget.lot)thisTarget.lot = { eached: false }
|
||||
if (!thisTarget.lot.eached) {
|
||||
if (SeeksGraphMath.isAllowShowNode(thisTarget)) {
|
||||
thisTarget.lot.eached = true
|
||||
thisTarget.lot.parent = thisNode
|
||||
thisTarget.lot.index_of_parent = __thisTargetIndex++
|
||||
// thisTarget.lot.prevNode = __prev_node
|
||||
// if (__prev_node)__prev_node.lot.nextNode = thisTarget
|
||||
// __prev_node = thisTarget
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
newLevelNodes.push(thisTarget)
|
||||
__thisNode_child_size++
|
||||
} else {
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
// console.log('hide node:', thisTarget.name, 'from:', thisNode.text)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// console.log('Build node::to::', thisNode.text, thisNode.targetTo.length)
|
||||
let __thisTargetIndex = 0
|
||||
thisNode.targetTo.forEach((thisTarget) => {
|
||||
if (!thisTarget.lot)thisTarget.lot = { eached: false }
|
||||
if (!thisTarget.lot.eached) {
|
||||
if (SeeksGraphMath.isAllowShowNode(thisTarget)) {
|
||||
thisTarget.lot.eached = true
|
||||
thisTarget.lot.parent = thisNode
|
||||
thisTarget.lot.index_of_parent = __thisTargetIndex++
|
||||
// thisTarget.lot.prevNode = __prev_node
|
||||
// if (__prev_node)__prev_node.lot.nextNode = thisTarget
|
||||
// __prev_node = thisTarget
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
newLevelNodes.push(thisTarget)
|
||||
__thisNode_child_size++
|
||||
} else {
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
// console.log('hide node:', thisTarget.name, 'from:', thisNode.text)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
thisNode.lot.strength = __thisNode_child_size > 0 ? __thisNode_child_size : 1
|
||||
__thisLOT_subling.all_strength += thisNode.lot.strength
|
||||
thisNode.lot.strength_plus = __thisLOT_subling.all_strength
|
||||
thisNode.lot.index_of_level = __thisLevel_index
|
||||
thisNode.lot.childs_size = __thisNode_child_size
|
||||
__thisLevel_index++
|
||||
})
|
||||
if (__thisLOT_subling.all_strength > analyticResult.max_strength) {
|
||||
analyticResult.max_strength = __thisLOT_subling.all_strength
|
||||
}
|
||||
// console.log(thisDeep, 'next level nodes:', newLevelNodes.length)
|
||||
if (newLevelNodes.length > 0) {
|
||||
// console.log('thisLevelNodes.length:', thisLevelNodes, thisLevelNodes.length)
|
||||
this.analysisNodes4Didirectional(willLayoutNodes, newLevelNodes, thisDeep + levelDirect, analyticResult, levelDirect)
|
||||
} else {
|
||||
willLayoutNodes.forEach(thisNode => {
|
||||
if (thisNode.lot.childs_size > 0) {
|
||||
thisNode.lot.strengthWithChilds = 0
|
||||
}
|
||||
})
|
||||
willLayoutNodes.forEach(thisNode => {
|
||||
if (thisNode.lot.childs_size === 0) {
|
||||
thisNode.lot.strengthWithChilds = 1
|
||||
SeeksGraphMath.conductStrengthToParents(thisNode)
|
||||
}
|
||||
})
|
||||
SeeksGraphMath.analysisDataTree([willLayoutNodes[0]], 0, levelDirect)
|
||||
// willLayoutNodes.forEach(thisNode => {
|
||||
// thisNode.text = thisNode.lot.strengthWithChilds_from + ':' + thisNode.lot.strengthWithChilds + '/' + thisNode.lot.strength
|
||||
// })
|
||||
}
|
||||
}
|
||||
this.placeNodes = function(allNodes, rootNode) {
|
||||
console.log('SeeksBidirectionalTreeLayouter:placeNodes')
|
||||
if (!rootNode) {
|
||||
console.error('root is null')
|
||||
return
|
||||
} else {
|
||||
console.log('layout by root:', rootNode)
|
||||
}
|
||||
this.__origin_nodes = allNodes
|
||||
this.rootNode = rootNode
|
||||
allNodes.forEach(thisNode => {
|
||||
// thisNode.lot = { eached: false }
|
||||
thisNode.lot.eached = false
|
||||
thisNode.lot.notLeafNode = false
|
||||
thisNode.lot.childs = []
|
||||
// thisNode.lot.parent = undefined
|
||||
thisNode.lot.index_of_parent = 0
|
||||
thisNode.lot.strength = 0
|
||||
thisNode.lot.strengthWithChilds_from = 0
|
||||
thisNode.lot.strengthWithChilds = 0
|
||||
thisNode.lot.prevNode = undefined
|
||||
thisNode.lot.nextNode = undefined
|
||||
thisNode.lot.placed = false
|
||||
})
|
||||
// this.rootNode.fixed = true
|
||||
this.allNodes = []
|
||||
var analyticResult = {
|
||||
max_deep: 1,
|
||||
max_length: 1,
|
||||
max_strength: 1
|
||||
}
|
||||
this.analysisNodes4Didirectional(this.allNodes, [this.rootNode], 0, analyticResult, -1)
|
||||
this.placeNodesPosition(this.rootNode, this.allNodes, analyticResult)
|
||||
this.allNodes = []
|
||||
analyticResult = {
|
||||
max_deep: 1,
|
||||
max_length: 1,
|
||||
max_strength: 1
|
||||
}
|
||||
this.analysisNodes4Didirectional(this.allNodes, [this.rootNode], 0, analyticResult, 1)
|
||||
this.placeNodesPosition(this.rootNode, this.allNodes, analyticResult)
|
||||
|
||||
// console.log('根据数据调整画板高度')
|
||||
// if (this.config.from === 'left' || this.config.from === 'right') {
|
||||
// let __suitableHeight = analyticResult.max_strength * 50 + 100
|
||||
// if (__suitableHeight < this.graphSetting.viewSize.height + 300)__suitableHeight = this.graphSetting.viewSize.height + 300
|
||||
// this.graphSetting.canvasSize.height = __suitableHeight
|
||||
// let __suitableWidth = analyticResult.max_deep * 1000 + 600
|
||||
// if (__suitableWidth < this.graphSetting.viewSize.width + 500)__suitableWidth = this.graphSetting.viewSize.width + 500
|
||||
// this.graphSetting.canvasSize.width = __suitableWidth
|
||||
// } else {
|
||||
// let __suitableWidth = analyticResult.max_strength * 320 + 1000
|
||||
// if (__suitableWidth < this.graphSetting.viewSize.width + 500)__suitableWidth = this.graphSetting.viewSize.width + 500
|
||||
// this.graphSetting.canvasSize.width = __suitableWidth
|
||||
// let __suitableHeight = analyticResult.max_deep * 400 + 200
|
||||
// if (__suitableHeight < this.graphSetting.viewSize.height + 300)__suitableHeight = this.graphSetting.viewSize.height + 300
|
||||
// this.graphSetting.canvasSize.height = __suitableHeight
|
||||
// }
|
||||
// if (this.graphSetting.heightByContent) {
|
||||
// console.log('根据数据调整视窗高度')
|
||||
// if (this.config.from === 'left' || this.config.from === 'right') {
|
||||
// this.graphSetting.viewSize.height = this.graphSetting.canvasSize.height
|
||||
// } else {
|
||||
// this.graphSetting.viewSize.height = analyticResult.max_deep * 500 + 300
|
||||
// }
|
||||
// }
|
||||
// this.graphSetting.canvasOffset.x = this.graphSetting.viewNVInfo.width / 2 - 100
|
||||
// this.graphSetting.canvasOffset.y = this.graphSetting.viewNVInfo.height / 2 - 100
|
||||
}
|
||||
this.placeNodesPosition = function(rootNode, allNodes, analyticResult) {
|
||||
var __mapWidth = this.graphSetting.viewSize.width
|
||||
var __mapHeight = this.graphSetting.viewSize.height
|
||||
// console.log('analysisNodes:', analyticResult, allNodes)
|
||||
// this.graphSetting.canvasOffset.x = 0
|
||||
// this.graphSetting.canvasOffset.y = 0
|
||||
var __offsetX = rootNode.offset_x || 0
|
||||
var __offsetY = rootNode.offset_y || 0
|
||||
// console.log('#############Seeks graph viewSize:Tree layout:', this.graphSetting.viewSize.width, this.graphSetting.viewSize.height)
|
||||
// console.log('[layout canvasOffset]', __mapHeight, this.graphSetting, this.graphSetting.canvasSize, this.config)
|
||||
// console.log('[Layout:AnalyticResult]', analyticResult)
|
||||
if (rootNode.fixed !== true) {
|
||||
var _center_offset_x = parseInt(this.config.centerOffset_x) || 0
|
||||
var _center_offset_y = parseInt(this.config.centerOffset_y) || 0
|
||||
if (this.config.from === 'top') {
|
||||
rootNode.lot.x = (__mapWidth - rootNode.el.offsetWidth) / 2 + _center_offset_x
|
||||
rootNode.lot.y = parseInt(__mapHeight * 0.3 - rootNode.el.offsetHeight) + _center_offset_y
|
||||
} else if (this.config.from === 'bottom') {
|
||||
rootNode.lot.x = (__mapWidth - rootNode.el.offsetWidth) / 2 + _center_offset_x
|
||||
rootNode.lot.y = parseInt(__mapHeight * 0.7 - rootNode.el.offsetHeight) + _center_offset_y
|
||||
} else if (this.config.from === 'right') {
|
||||
rootNode.lot.x = parseInt(__mapWidth * 0.7 - rootNode.el.offsetWidth) / 2 + _center_offset_x
|
||||
rootNode.lot.y = parseInt(__mapHeight / 2 - rootNode.el.offsetHeight / 2) + _center_offset_y
|
||||
} else {
|
||||
rootNode.lot.x = parseInt(__mapWidth * 0.3 - rootNode.el.offsetWidth) / 2 + _center_offset_x
|
||||
rootNode.lot.y = parseInt(__mapHeight / 2 - rootNode.el.offsetHeight / 2) + _center_offset_y
|
||||
}
|
||||
console.log('设置根节点位置:', rootNode.text, rootNode.x, rootNode.y, this.graphSetting.canvasSize.width, this.graphSetting.canvasSize.height, this.graphSetting.canvasOffset.x, this.graphSetting.canvasOffset.y)
|
||||
rootNode.x = rootNode.lot.x + __offsetX
|
||||
rootNode.y = rootNode.lot.y + __offsetY
|
||||
} else {
|
||||
console.log('固定位置的rootNode:', rootNode.text, rootNode.x, rootNode.y)
|
||||
if (rootNode.origin_x === undefined) {
|
||||
rootNode.origin_x = rootNode.x
|
||||
rootNode.origin_y = rootNode.y
|
||||
}
|
||||
rootNode.lot.x = rootNode.origin_x
|
||||
rootNode.lot.y = rootNode.origin_y
|
||||
rootNode.x = rootNode.lot.x + __offsetX
|
||||
rootNode.y = rootNode.lot.y + __offsetY
|
||||
console.log('固定位置的rootNode:', rootNode.text, rootNode.x, rootNode.y)
|
||||
}
|
||||
rootNode.lot.placed = true
|
||||
var dynamicSizeConfig = {
|
||||
__mapWidth,
|
||||
__mapHeight
|
||||
}
|
||||
this.placeRelativePosition(rootNode, analyticResult, dynamicSizeConfig)
|
||||
allNodes.forEach(thisNode => {
|
||||
// if (rootNode === thisNode) {
|
||||
// var _root_offset_x = this.config.root_offset_x || 0
|
||||
// thisNode.x = thisNode.x + _root_offset_x
|
||||
// return
|
||||
// }
|
||||
if (thisNode.fixed === true) {
|
||||
thisNode.lot.placed = true
|
||||
return
|
||||
}
|
||||
if (!SeeksGraphMath.isAllowShowNode(thisNode)) return
|
||||
// console.log(thisNode.text, thisNode.offset_x, thisNode.offset_y)
|
||||
var __offsetX = thisNode.offset_x || 0
|
||||
var __offsetY = thisNode.offset_y || 0
|
||||
thisNode.x = thisNode.offset_x + thisNode.lot.x + __offsetX
|
||||
thisNode.y = thisNode.offset_y + thisNode.lot.y + __offsetY
|
||||
thisNode.lot.placed = true
|
||||
})
|
||||
}
|
||||
this.placeRelativePosition = function(rootNode, analyticResult, dynamicSizeConfig) {
|
||||
if (this.config.from === 'left' || this.config.from === 'right') {
|
||||
const __min_per_height = this.config.min_per_height || 80
|
||||
const __max_per_height = this.config.max_per_height || 400
|
||||
const __min_per_width = this.config.min_per_width || 430
|
||||
const __max_per_width = this.config.max_per_width || 650
|
||||
let __per_width = parseInt((dynamicSizeConfig.__mapWidth - 10) / (analyticResult.max_deep + 2))
|
||||
if (__per_width < __min_per_width)__per_width = __min_per_width
|
||||
if (__per_width > __max_per_width)__per_width = __max_per_width
|
||||
let __per_height = parseInt(dynamicSizeConfig.__mapHeight / (analyticResult.max_strength + 1))
|
||||
if (__per_height < __min_per_height)__per_height = __min_per_height
|
||||
if (__per_height > __max_per_height)__per_height = __max_per_height
|
||||
this.allNodes.forEach(thisNode => {
|
||||
if (thisNode.fixed === true) return
|
||||
if (thisNode.lot.placed === true) return
|
||||
if (thisNode === rootNode) return
|
||||
// console.log('Place node:', thisNode.text, thisNode)
|
||||
// console.log('Place node lot:', thisNode.lot.subling.level, thisNode.lot.index_of_level, 'of', thisNode.lot.subling.all_size, thisNode.lot.subling.all_strength)
|
||||
if (this.config.from === 'right') {
|
||||
thisNode.lot.x = rootNode.lot.x - this.getLevelDistance(thisNode, thisNode.lot.subling.level, __per_width)
|
||||
} else {
|
||||
thisNode.lot.x = rootNode.lot.x + this.getLevelDistance(thisNode, thisNode.lot.subling.level, __per_width)
|
||||
}
|
||||
})
|
||||
this.allNodes.forEach(thisNode => {
|
||||
if (thisNode.fixed === true) return
|
||||
if (thisNode.lot.level !== 0) {
|
||||
thisNode.lot.y = rootNode.lot.y + __per_height * ((analyticResult.max_strength / -2) + thisNode.lot.strengthWithChilds_from + thisNode.lot.strengthWithChilds / 2)
|
||||
}
|
||||
})
|
||||
// this.allNodes.forEach(thisNode => {
|
||||
// if (thisNode.fixed === true) return
|
||||
// if (thisNode.lot.level === 1) {
|
||||
// thisNode.lot.y = __per_height * (thisNode.lot.strength_plus - thisNode.lot.strength + (thisNode.lot.strength - 1) / 2)
|
||||
// }
|
||||
// })
|
||||
// this.allNodes.forEach(thisNode => {
|
||||
// if (thisNode.fixed === true) return
|
||||
// if (thisNode.lot.level > 1) {
|
||||
// // thisNode.lot.y = __per_height * (thisNode.lot.strength_plus - thisNode.lot.strength + (thisNode.lot.strength - 1) / 2)
|
||||
// thisNode.lot.y = __per_height * (thisNode.lot.parent.lot.strength_plus - thisNode.lot.parent.lot.strength + thisNode.lot.index_of_parent)
|
||||
// // thisNode.text = thisNode.lot.parent.lot.strength_plus + '-' + thisNode.lot.parent.lot.strength + '+' + thisNode.lot.index_of_parent
|
||||
// }
|
||||
// })
|
||||
} else {
|
||||
const __min_per_height = this.config.min_per_height || 250
|
||||
const __max_per_height = this.config.max_per_height || 400
|
||||
const __min_per_width = this.config.min_per_width || 250
|
||||
const __max_per_width = this.config.max_per_width || 500
|
||||
var __per_width = parseInt((dynamicSizeConfig.__mapWidth - 10) / (analyticResult.max_strength + 2))
|
||||
if (__per_width < __min_per_width)__per_width = __min_per_width
|
||||
if (__per_width > __max_per_width)__per_width = __max_per_width
|
||||
var __per_height = parseInt((dynamicSizeConfig.__mapHeight - 10) / (analyticResult.max_deep + 2))
|
||||
if (__per_height < __min_per_height)__per_height = __min_per_height
|
||||
if (__per_height > __max_per_height)__per_height = __max_per_height
|
||||
this.allNodes.forEach(thisNode => {
|
||||
if (thisNode.fixed === true) return
|
||||
if (thisNode.lot.placed === true) return
|
||||
if (thisNode === rootNode) return
|
||||
// console.log('Place node:', thisNode.text, thisNode)
|
||||
// console.log('Place node lot:', thisNode.lot.subling.level, thisNode.lot.index_of_level, 'of', thisNode.lot.subling.all_size, thisNode.lot.subling.all_strength)
|
||||
if (this.config.from === 'bottom') {
|
||||
thisNode.lot.y = rootNode.lot.y - this.getLevelDistance(thisNode, thisNode.lot.subling.level, __per_height)
|
||||
} else {
|
||||
// console.log('Place node xxxx:', rootNode.lot.y, thisNode.lot.subling.level, __per_height)
|
||||
thisNode.lot.y = rootNode.lot.y + this.getLevelDistance(thisNode, thisNode.lot.subling.level, __per_height)
|
||||
}
|
||||
})
|
||||
this.allNodes.forEach(thisNode => {
|
||||
if (thisNode.fixed === true) return
|
||||
if (thisNode.lot.level !== 0) {
|
||||
// console.log('Place node xxxx:', thisNode.lot.strengthWithChilds_from, thisNode.lot.strengthWithChilds, __per_width)
|
||||
thisNode.lot.x = -58 + rootNode.lot.x + __per_width * ((analyticResult.max_strength / -2) + thisNode.lot.strengthWithChilds_from + thisNode.lot.strengthWithChilds / 2)
|
||||
// thisNode.lot.x = rootNode.lot.x
|
||||
}
|
||||
})
|
||||
// this.allNodes.forEach(thisNode => {
|
||||
// if (thisNode.fixed === true) return
|
||||
// if (thisNode.lot.level === 1) {
|
||||
// thisNode.lot.x = __per_width * (thisNode.lot.strength_plus - thisNode.lot.strength + thisNode.lot.strength / 2)
|
||||
// }
|
||||
// })
|
||||
// this.allNodes.forEach(thisNode => {
|
||||
// if (thisNode.fixed === true) return
|
||||
// if (thisNode.lot.level > 1) {
|
||||
// thisNode.lot.x = __per_width * (thisNode.lot.parent.lot.strength_plus - thisNode.lot.parent.lot.strength + thisNode.lot.index_of_parent)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
this.getLevelDistance = function(node, level, perSize) {
|
||||
if (this.config.levelDistanceArr && this.config.levelDistanceArr.length > 0) {
|
||||
var _distance = 0
|
||||
for (let i = 0; i < level; i++) {
|
||||
var _thisLevelDistance = this.config.levelDistanceArr[i] || 100
|
||||
_distance += _thisLevelDistance
|
||||
}
|
||||
return _distance
|
||||
} else {
|
||||
return level * perSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SeeksBidirectionalTreeLayouter
|
||||
@@ -0,0 +1,290 @@
|
||||
import SeeksGraphMath from '../SeeksGraphMath'
|
||||
|
||||
function SeeksCenterLayouter(layoutSetting, graphSetting) {
|
||||
this.graphSetting = graphSetting
|
||||
this.config = layoutSetting || {}
|
||||
this.rootNode = null
|
||||
this.allNodes = []
|
||||
this.__origin_nodes = []
|
||||
this.refresh = function() {
|
||||
if (window.SeeksGraphDebug) console.log('SeeksCenterLayouter:refresh')
|
||||
this.placeNodes(this.__origin_nodes, this.rootNode)
|
||||
}
|
||||
this.placeNodes = function(allNodes, rootNode) {
|
||||
if (window.SeeksGraphDebug) console.log('SeeksCenterLayouter:placeNodes')
|
||||
if (!rootNode) {
|
||||
console.log('root is null:', rootNode)
|
||||
return
|
||||
} else {
|
||||
if (window.SeeksGraphDebug) console.log('layout by root:', rootNode)
|
||||
}
|
||||
this.__origin_nodes = allNodes
|
||||
this.rootNode = rootNode
|
||||
allNodes.forEach(thisNode => {
|
||||
// thisNode.lot = { eached: false }
|
||||
thisNode.lot.eached = false
|
||||
thisNode.lot.notLeafNode = false
|
||||
thisNode.lot.childs = []
|
||||
// thisNode.lot.parent = undefined
|
||||
thisNode.lot.index_of_parent = 0
|
||||
thisNode.lot.strength = 0
|
||||
thisNode.lot.prevNode = undefined
|
||||
thisNode.lot.nextNode = undefined
|
||||
thisNode.lot.placed = false
|
||||
})
|
||||
this.allNodes = []
|
||||
var analyticResult = {
|
||||
max_deep: 1,
|
||||
max_length: 1
|
||||
}
|
||||
SeeksGraphMath.analysisNodes4Didirectional(this.allNodes, [this.rootNode], 0, analyticResult, 0)
|
||||
// console.log('analysisNodes:', analyticResult)
|
||||
// if (this.graphSetting.heightByContent) {
|
||||
// console.log('根据内容调整高度')
|
||||
// var __suitableHeight = analyticResult.max_deep * 2 * 300 + 500
|
||||
// this.graphSetting.viewSize.height = __suitableHeight
|
||||
// }
|
||||
if (window.SeeksGraphDebug) console.log('调整画布大小')
|
||||
// var __per_width = parseInt((__mapWidth - 10) / (analyticResult.max_deep + 2))
|
||||
// var __per_height = parseInt((__mapHeight - 10) / (analyticResult.max_length + 1))
|
||||
// console.log('per:', __per_width, __per_height)
|
||||
// var __level2_current_length = 0
|
||||
// this.allNodes.forEach(thisNode => {
|
||||
// if (thisNode.lot.subling.level === 1 && thisNode.lot.childs_size > 0) {
|
||||
// __level2_current_length += thisNode.lot.childs_size
|
||||
// var __thisNodeLength = __level2_current_length + parseInt((thisNode.lot.childs_size / 2).toFixed(0))
|
||||
// thisNode.lot.strength_plus = __level2_current_length
|
||||
// console.log('level2 parents:', thisNode.name, thisNode.lot.childs_size, { strength_plus: thisNode.lot.strength_plus, __thisNodeLength, strength: thisNode.lot.childs_size, __level2_current_length })
|
||||
// }
|
||||
// })
|
||||
// var __currentLevel = 0
|
||||
var __mapWidth = this.graphSetting.viewSize.width
|
||||
var __mapHeight = this.graphSetting.viewSize.height
|
||||
rootNode.lot.x = parseInt((__mapWidth - rootNode.el.offsetWidth) / 2)
|
||||
rootNode.lot.y = parseInt((__mapHeight - rootNode.el.offsetHeight) / 2)
|
||||
// this.rootNode.lot.x = 0
|
||||
// this.rootNode.lot.y = 0
|
||||
// if (this.rootNode.lot.y > 400) {
|
||||
// this.rootNode.lot.y = 400
|
||||
// }
|
||||
// console.log('[layout canvasOffset]', this.graphSetting.viewSize, this.graphSetting.canvasSize)
|
||||
this.placeRelativePosition(this.rootNode, analyticResult)
|
||||
this.allNodes.forEach(thisNode => {
|
||||
if (thisNode.fixed === true) return
|
||||
if (!SeeksGraphMath.isAllowShowNode(thisNode)) return
|
||||
var __offsetX = thisNode.offset_x || 0
|
||||
var __offsetY = thisNode.offset_y || 0
|
||||
thisNode.x = thisNode.lot.x + __offsetX
|
||||
thisNode.y = thisNode.lot.y + __offsetY
|
||||
thisNode.lot.placed = true
|
||||
})
|
||||
// var __graphIndex = 1
|
||||
// allNodes.forEach(thisNode => {
|
||||
// // thisNode.lot = { eached: false }
|
||||
// if (!SeeksGraphMath.isAllowShowNode(thisNode)) return
|
||||
// if (thisNode.lot.placed === false) {
|
||||
// this.allNodes = []
|
||||
// var analyticResult = {
|
||||
// max_deep: 1,
|
||||
// max_length: 1
|
||||
// }
|
||||
// SeeksGraphMath.analysisNodes(this.allNodes, [thisNode], 0, analyticResult, { prettyLevelPosition: this.graphSetting.prettyLevelPosition })
|
||||
// thisNode.lot.x = this.rootNode.lot.x
|
||||
// thisNode.lot.y = this.rootNode.lot.y + (__graphIndex++ * 1200)
|
||||
// this.graphSetting.canvasSize.height += 1200
|
||||
// this.placeRelativePosition(thisNode)
|
||||
// this.allNodes.forEach(thisNode => {
|
||||
// thisNode.x = thisNode.lot.x - __offsetX
|
||||
// thisNode.y = thisNode.lot.y - __offsetY
|
||||
// thisNode.lot.placed = true
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
console.log('Start Auto Layout.....')
|
||||
// this.autoLayout(true)
|
||||
// console.log('layout from root:', analyticResult.max_deep, analyticResult.max_length)
|
||||
// rootNode.x = (this.graphSetting.canvasSize.width - this.graphSetting.nodeSize.width) / 2
|
||||
// rootNode.y = (this.graphSetting.canvasSize.height - this.graphSetting.nodeSize.height) / 2
|
||||
// rootNode.placed = true
|
||||
// // rootNode.name = rootNode.x + ',' + rootNode.y
|
||||
// var newLevelNodes = []
|
||||
// newLevelNodes.push(rootNode)
|
||||
// this.setPlace(newLevelNodes, 0, rootNode)
|
||||
}
|
||||
this.placeRelativePosition = function(rootNode, analyticResult) {
|
||||
var distance_coefficient = this.config.distance_coefficient === undefined ? 1 : this.config.distance_coefficient
|
||||
var __leve1_min_r = parseInt(((this.graphSetting.viewSize.height + this.graphSetting.viewSize.width) / analyticResult.max_deep * 0.2)) * distance_coefficient
|
||||
if (window.SeeksGraphDebug) console.log('analyticResult:', analyticResult, __leve1_min_r, this.config.distance_coefficient)
|
||||
if (__leve1_min_r < 150 * distance_coefficient) __leve1_min_r = 150 * distance_coefficient
|
||||
var __level1_r = 0
|
||||
this.allNodes.forEach(thisNode => {
|
||||
if (thisNode.lot.subling.level === 1) {
|
||||
__level1_r = parseInt(thisNode.lot.subling.all_size * 50 / Math.PI / 2)
|
||||
if (__level1_r < __leve1_min_r)__level1_r = __leve1_min_r
|
||||
// if (__level1_r > 500)__level1_r = 500
|
||||
const _point = SeeksGraphMath.getOvalPoint(rootNode.lot.x, rootNode.lot.y, thisNode.lot.subling.level * __level1_r, thisNode.lot.strength_plus - (thisNode.lot.strength / 2), thisNode.lot.subling.all_strength)
|
||||
// const _point = SeeksGraphMath.getOvalPoint(this.rootNode.x, this.rootNode.y, thisNode.lot.subling.level * __level1_r, thisNode.lot.index_of_level, thisNode.lot.subling.all_size)
|
||||
thisNode.lot.x = _point.x
|
||||
thisNode.lot.y = _point.y
|
||||
}
|
||||
})
|
||||
var __level_r = parseInt(300 * distance_coefficient)
|
||||
this.allNodes.forEach(thisNode => {
|
||||
if (thisNode.lot.subling.level > 1) {
|
||||
var __area_start = thisNode.lot.parent.lot.strength_plus - thisNode.lot.parent.lot.strength
|
||||
var __area_end = thisNode.lot.parent.lot.strength_plus
|
||||
var __buff = (__area_end - __area_start) / (thisNode.lot.parent.lot.childs_size + 1) * (thisNode.lot.index_of_parent + 1)
|
||||
const _point = SeeksGraphMath.getOvalPoint(rootNode.lot.x, rootNode.lot.y, (thisNode.lot.subling.level - 1) * __level_r + __level1_r, __area_start + __buff, thisNode.lot.parent.lot.subling.all_strength)
|
||||
thisNode.lot.x = _point.x
|
||||
thisNode.lot.y = _point.y
|
||||
}
|
||||
})
|
||||
}
|
||||
this.layoutTimes = 0
|
||||
// var ___this = this
|
||||
this.autoLayout = function(forceLayout) {
|
||||
if (forceLayout) {
|
||||
this.layoutTimes = 0
|
||||
}
|
||||
console.log('this.layoutTimes:', this.layoutTimes)
|
||||
if (this.layoutTimes > 300) {
|
||||
this.graphSetting.autoLayouting = false
|
||||
return
|
||||
}
|
||||
this.layoutTimes++
|
||||
this.__origin_nodes.forEach(thisNode => {
|
||||
thisNode.Fx = 0
|
||||
thisNode.Fy = 0
|
||||
})
|
||||
var __by_node = true // parseInt(this.layoutTimes / 10) % 2 === 1
|
||||
var __by_line = true // parseInt(this.layoutTimes / 10) % 2 === 0
|
||||
if (__by_node) {
|
||||
for (const i in this.__origin_nodes) {
|
||||
var __node1 = this.__origin_nodes[i]
|
||||
// if (__node1.text === '宣洁')console.log('宣洁:', __node1.x, __node1.y)
|
||||
if (__node1.lot.placed === true) {
|
||||
// var __thisNode = this.__origin_nodes[i]
|
||||
// __thisNode.targetNodes.forEach(thisTN_level1 => {
|
||||
// this.addGravityByNode(__thisNode, thisTN_level1)
|
||||
// thisTN_level1.targetNodes.forEach(thisTN_level2 => {
|
||||
// this.addGravityByNode(__thisNode, thisTN_level2)
|
||||
// })
|
||||
// })
|
||||
// 循环点,综合点与其他所有点点斥力及方向
|
||||
for (var j in this.__origin_nodes) {
|
||||
var __node2 = this.__origin_nodes[j]
|
||||
if (__node2.lot.placed === true) {
|
||||
// 循环点,计算i点与j点点斥力及方向
|
||||
if (i !== j) {
|
||||
// if (this.allNodes[i].lot.level === this.allNodes[j].lot.level) {
|
||||
this.addGravityByNode(__node1, __node2)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (__by_line) {
|
||||
for (const i in this.__origin_nodes) {
|
||||
// 循环线,设置每个点承受点力及力点方向
|
||||
if (this.__origin_nodes[i].lot.parent) {
|
||||
this.addElasticByLine(this.__origin_nodes[i].lot.parent, this.__origin_nodes[i])
|
||||
// break
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (this.layoutTimes % 5 === 0) { // 为提高布局效率,计算五次后更新位置
|
||||
for (const i in this.__origin_nodes) {
|
||||
this.applyToNodePosition(this.__origin_nodes[i])
|
||||
}
|
||||
// }
|
||||
window.setTimeout(function() { this.autoLayout() }.bind(this), 30)
|
||||
}
|
||||
this.stop = function() {
|
||||
this.layoutTimes = 1000
|
||||
}
|
||||
this.addElasticByLine = function(node1, node2) {
|
||||
var length = Math.sqrt(Math.pow((node1.y - node2.y), 2) + Math.pow((node1.x - node2.x), 2))
|
||||
if (length > 1000) {
|
||||
length = 1000
|
||||
}
|
||||
var Kf = length < 30 ? 0 : ((length - 30) * 0.05)
|
||||
var Kf_1 = Kf
|
||||
var Kf_2 = Kf
|
||||
// var Kf_1 = Kf / node1.lot.childs.length
|
||||
// var Kf_2 = Kf / node2.lot.childs.length
|
||||
var _buff_x = (node1.x - node2.x) / length
|
||||
var _buff_y = (node1.y - node2.y) / length
|
||||
this.addFtoNode(node1, _buff_x * Kf_1 * -1, _buff_y * Kf_1 * -1, 1)
|
||||
this.addFtoNode(node2, _buff_x * Kf_2, _buff_y * Kf_2, 1)
|
||||
}
|
||||
this.addGravityByNode = function(node1, node2) {
|
||||
var length = Math.sqrt(Math.pow((node1.y - node2.y), 2) + Math.pow((node1.x - node2.x), 2))
|
||||
var zero_length = 300
|
||||
var Kf = length > zero_length ? 0 : ((zero_length - length) * 0.03)
|
||||
if (zero_length < 30) {
|
||||
Kf = Kf * 100
|
||||
}
|
||||
// if (length < 100)Kf = Kf * 2
|
||||
var _buff_x = (node1.x - node2.x) / length
|
||||
var _buff_y = (node1.y - node2.y) / length
|
||||
// if (_buff_x < 30)_buff_x = 1
|
||||
// if (_buff_y < 30)_buff_y = 1
|
||||
// console.log({ Kf, _buff_x, _buff_y, zero_length })
|
||||
this.addFtoNode(node1, _buff_x * Kf, _buff_y * Kf, 0)
|
||||
this.addFtoNode(node2, _buff_x * Kf * -1, _buff_y * Kf * -1, 0)
|
||||
}
|
||||
this.getNodeFWeight = function(node) {
|
||||
var level = node.lot.level
|
||||
if (level > 7)level = 7
|
||||
if (level < 0)level = 0
|
||||
return (8 - level) / 8
|
||||
}
|
||||
this.addFtoNode = function(node, x, y) {
|
||||
// console.log('Add F:', node.text, type, parseInt(x), parseInt(y))
|
||||
if (isNaN(x) || isNaN(y)) {
|
||||
return
|
||||
}
|
||||
x = x / node.lot.strength
|
||||
y = y / node.lot.strength
|
||||
if (x > 50)x = 50
|
||||
if (y > 50)y = 50
|
||||
if (x < -50)x = -50
|
||||
if (y < -50)y = -50
|
||||
// if (isNaN(node.Fx)) {
|
||||
// if (node.text === '宣洁')console.log('宣洁!!!NaN B buff x:', x, node.lot.strength, node)
|
||||
// }
|
||||
node.Fx += x
|
||||
node.Fy += y
|
||||
// if (isNaN(node.Fx)) {
|
||||
// if (node.text === '宣洁')console.log('宣洁!!!NaN A buff x:', x, node.lot.strength, node)
|
||||
// }
|
||||
}
|
||||
this.applyToNodePosition = function(node) {
|
||||
// if (!node.lot.childs || node.lot.childs.length === 0) {
|
||||
// return
|
||||
// }
|
||||
// if (node.lot.level === 0) {
|
||||
// return
|
||||
// }
|
||||
// console.log('F add:', node.name, node.Fx, node.Fy)
|
||||
const __buff_x = parseInt(node.Fx)
|
||||
const __buff_y = parseInt(node.Fy)
|
||||
// console.log('F add:2:', node.name, __buff_x, __buff_y)
|
||||
node.x = node.x + __buff_x
|
||||
node.y = node.y + __buff_y
|
||||
// if (isNaN(node.x)) {
|
||||
// if (node.text === '宣洁')console.log('!!!NaN x:', node.text, __buff_x, node.Fx, node)
|
||||
// }
|
||||
// node.name = __buff_x + ',' + __buff_y
|
||||
// if (node.id === '8') {
|
||||
// console.log(node.id, __buff_x, __buff_y)
|
||||
// // console.log(node.x, node.y)
|
||||
// }
|
||||
node.Fx = 0
|
||||
node.Fy = 0
|
||||
}
|
||||
}
|
||||
|
||||
export default SeeksCenterLayouter
|
||||
@@ -0,0 +1,307 @@
|
||||
import SeeksGraphMath from '../SeeksGraphMath'
|
||||
|
||||
function SeeksCenterLayouter(layoutSetting, setting) {
|
||||
this.setting = setting
|
||||
this.config = layoutSetting || {}
|
||||
this.__max_deep = 1
|
||||
this.__max_length = 1
|
||||
this.checkMaxDeepAndLength = function(thisLevelNodes, thisDeep) {
|
||||
if (thisLevelNodes.length > this.__max_length) {
|
||||
this.__max_length = thisLevelNodes.length
|
||||
}
|
||||
if (thisDeep > this.__max_deep) {
|
||||
this.__max_deep = thisDeep
|
||||
}
|
||||
var __thisLOT_subling = {
|
||||
level: thisDeep,
|
||||
all_size: thisLevelNodes.length,
|
||||
all_strength: 0
|
||||
}
|
||||
var newLevelNodes = []
|
||||
thisLevelNodes.forEach(thisNode => {
|
||||
if (!thisNode.lot)thisNode.lot = {}
|
||||
thisNode.lot.eached = true
|
||||
thisNode.lot.subling = __thisLOT_subling
|
||||
this.allNodes.push(thisNode)
|
||||
})
|
||||
var __thisLevel_index = 0
|
||||
var __prev_node
|
||||
thisLevelNodes.forEach(thisNode => {
|
||||
// console.log('Build node::', thisNode.name, thisNode.targetNodes.length)
|
||||
var __thisNode_child_size = 0
|
||||
if (thisNode.targetNodes) {
|
||||
thisNode.targetNodes.forEach(thisTarget => {
|
||||
console.log('child node::', thisTarget.type, thisTarget.lot.eached)
|
||||
if (!thisTarget.lot)thisTarget.lot = { eached: false }
|
||||
if (thisTarget.type === 'node' && thisTarget.targetNodes.length <= 1) {
|
||||
if (!thisTarget.lot.eached) {
|
||||
thisTarget.lot.parent = thisNode
|
||||
thisTarget.lot.index_of_p_childs = __thisNode_child_size
|
||||
thisTarget.lot.prevNode = __prev_node
|
||||
if (__prev_node)__prev_node.lot.nextNode = thisTarget
|
||||
__prev_node = thisTarget
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
thisNode.lot.eached = true
|
||||
newLevelNodes.push(thisTarget)
|
||||
__thisNode_child_size++
|
||||
}
|
||||
} else {
|
||||
thisTarget.lot.notLeafNode = true
|
||||
}
|
||||
})
|
||||
thisNode.targetNodes.forEach(thisTarget => {
|
||||
if (thisTarget.lot.notLeafNode) {
|
||||
if (!thisTarget.lot)thisTarget.lot = { eached: false }
|
||||
if (!thisTarget.lot.eached) {
|
||||
thisTarget.lot.parent = thisNode
|
||||
thisTarget.lot.index_of_p_childs = __thisNode_child_size
|
||||
thisTarget.lot.prevNode = __prev_node
|
||||
if (__prev_node)__prev_node.lot.nextNode = thisTarget
|
||||
__prev_node = thisTarget
|
||||
thisNode.lot.childs.push(thisTarget)
|
||||
thisNode.lot.eached = true
|
||||
newLevelNodes.push(thisTarget)
|
||||
__thisNode_child_size++
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
thisNode.lot.strength = __thisNode_child_size > 0 ? __thisNode_child_size : 1
|
||||
__thisLOT_subling.all_strength += thisNode.lot.strength
|
||||
thisNode.lot.strength_plus = __thisLOT_subling.all_strength
|
||||
thisNode.lot.level_index = __thisLevel_index
|
||||
thisNode.lot.childs_size = __thisNode_child_size
|
||||
__thisLevel_index++
|
||||
})
|
||||
// console.log('next level nodes:', newLevelNodes.length)
|
||||
if (newLevelNodes.length > 0) {
|
||||
// console.log('thisLevelNodes.length:', thisLevelNodes, thisLevelNodes.length)
|
||||
this.checkMaxDeepAndLength(newLevelNodes, thisDeep + 1)
|
||||
}
|
||||
}
|
||||
this.rootNode = null
|
||||
this.allNodes = []
|
||||
this.__origin_nodes = []
|
||||
this.refresh = function() {
|
||||
this.placeNodes(this.__origin_nodes, this.rootNode)
|
||||
}
|
||||
this.placeNodes = function(allNodes, rootNode) {
|
||||
if (!rootNode) {
|
||||
console.log('root is null:', rootNode)
|
||||
return
|
||||
} else {
|
||||
console.log('layout by root:', rootNode)
|
||||
}
|
||||
this.__origin_nodes = allNodes
|
||||
this.rootNode = rootNode
|
||||
allNodes.forEach(thisNode => {
|
||||
// thisNode.lot = { eached: false }
|
||||
thisNode.lot.eached = false
|
||||
thisNode.lot.notLeafNode = false
|
||||
thisNode.lot.childs = []
|
||||
thisNode.lot.parent = undefined
|
||||
thisNode.lot.index_of_p_childs = 0
|
||||
thisNode.lot.strength = 0
|
||||
thisNode.lot.prevNode = undefined
|
||||
thisNode.lot.nextNode = undefined
|
||||
})
|
||||
this.allNodes = []
|
||||
console.log('max before:', this.__max_deep, this.__max_length)
|
||||
this.checkMaxDeepAndLength([this.rootNode], 0)
|
||||
console.log('max after:', this.__max_deep, this.__max_length)
|
||||
// if (this.setting.heightByContent) {
|
||||
// console.log('根据内容调整高度')
|
||||
// var __suitableHeight = this.__max_length * 40 + 100
|
||||
// if (__suitableHeight > this.setting.viewSize.height) {
|
||||
// this.setting.viewSize.height = __suitableHeight
|
||||
// }
|
||||
// if (setting.viewSize.height > this.setting.canvasSize.height / 2) {
|
||||
// this.setting.canvasSize.height = this.setting.viewSize.height * 2
|
||||
// }
|
||||
// }
|
||||
this.setting.canvasSize.width = 4000
|
||||
this.setting.canvasSize.height = 4000
|
||||
if (this.setting.heightByContent) {
|
||||
console.log('根据数据调整视窗高度')
|
||||
this.setting.viewSize.height = 1600
|
||||
}
|
||||
this.setting.resetViewSize(this.setting)
|
||||
var __mapWidth = this.setting.viewSize.width
|
||||
var __mapHeight = this.setting.viewSize.height
|
||||
var __offsetX = this.setting.canvasOffset.x
|
||||
var __offsetY = this.setting.canvasOffset.y
|
||||
// var __per_width = parseInt((__mapWidth - 10) / (this.__max_deep + 2))
|
||||
// var __per_height = parseInt((__mapHeight - 10) / (this.__max_length + 1))
|
||||
// console.log('per:', __per_width, __per_height)
|
||||
// var __level2_current_length = 0
|
||||
// this.allNodes.forEach(thisNode => {
|
||||
// if (thisNode.lot.subling.level === 1 && thisNode.lot.childs_size > 0) {
|
||||
// __level2_current_length += thisNode.lot.childs_size
|
||||
// var __thisNodeLength = __level2_current_length + parseInt((thisNode.lot.childs_size / 2).toFixed(0))
|
||||
// thisNode.lot.strength_plus = __level2_current_length
|
||||
// console.log('level2 parents:', thisNode.name, thisNode.lot.childs_size, { strength_plus: thisNode.lot.strength_plus, __thisNodeLength, strength: thisNode.lot.childs_size, __level2_current_length })
|
||||
// }
|
||||
// })
|
||||
// var __currentLevel = 0
|
||||
var __center = {
|
||||
x: (__mapWidth) / 2 - __offsetX,
|
||||
y: (__mapHeight) / 2 - __offsetY
|
||||
}
|
||||
if (__center.y > 800 - __offsetY) {
|
||||
__center.y = 800 - __offsetY
|
||||
}
|
||||
var __all_size = this.allNodes.length
|
||||
var __circle_r = __all_size * 90 / Math.PI / 2
|
||||
if (__circle_r < 200)__circle_r = 200
|
||||
if (__circle_r > 800)__circle_r = 800
|
||||
this.allNodes.forEach((thisNode, _index) => {
|
||||
const _point = SeeksGraphMath.getOvalPoint(__center.x, __center.y, __circle_r, _index, __all_size)
|
||||
thisNode.x = _point.x
|
||||
thisNode.y = _point.y
|
||||
// console.log('Place node:', thisNode.name, thisNode.x, thisNode.y)
|
||||
// thisNode.name = (thisNode.lp_level_index + 1) + '/' + subling_size
|
||||
})
|
||||
// this.allNodes.forEach(thisNode => {
|
||||
// if (thisNode.lot.subling.level === 1 && thisNode.lot.childs_size > 0) {
|
||||
// var _c_first = thisNode.lot.childs[0]
|
||||
// var _c_last = thisNode.lot.childs[thisNode.lot.childs.length - 1]
|
||||
// var _new_y = parseInt((_c_first.y + _c_last.y) / 2)
|
||||
// thisNode.y = _new_y
|
||||
// }
|
||||
// })
|
||||
// this.adjustLevel2Y(__mapHeight)
|
||||
console.log('Start Auto Layout.....')
|
||||
// this.autoLayout(true)
|
||||
// console.log('layout from root:', this.__max_deep, this.__max_length)
|
||||
// rootNode.x = (this.setting.canvasSize.width - this.setting.nodeSize.width) / 2
|
||||
// rootNode.y = (this.setting.canvasSize.height - this.setting.nodeSize.height) / 2
|
||||
// rootNode.placed = true
|
||||
// // rootNode.name = rootNode.x + ',' + rootNode.y
|
||||
// var newLevelNodes = []
|
||||
// newLevelNodes.push(rootNode)
|
||||
// this.setPlace(newLevelNodes, 0, rootNode)
|
||||
}
|
||||
this.adjustLevel2Y = function(__mapHeight) {
|
||||
for (let i = 0; i < this.allNodes.length; i++) {
|
||||
var thisNode = this.allNodes[i]
|
||||
if (thisNode.lot.subling.level === 1 && thisNode.lot.childs_size === 0) {
|
||||
var __per_height = parseInt(__mapHeight / (thisNode.lot.subling.all_size + 1))
|
||||
if (__per_height > 70)__per_height = 70
|
||||
console.log(__per_height, __mapHeight, thisNode.lot.subling.all_size, thisNode.lot.subling.all_strength, thisNode.lot.strength)
|
||||
for (let j = 0; j < this.allNodes.length; j++) {
|
||||
var thisLevel2Node = this.allNodes[j]
|
||||
if (thisLevel2Node.lot.subling.level === 1 && thisLevel2Node !== thisNode) {
|
||||
var __y_diff = Math.abs(thisNode.y - thisLevel2Node.y)
|
||||
if (__y_diff < __per_height - 2) {
|
||||
console.log('__y_diff', thisNode.name, thisLevel2Node.name, __y_diff)
|
||||
// if (thisLevel2Node.lot.childs_size > 0 && i > 0) {
|
||||
// thisLevel2Node.y = this.allNodes[i - 1].y + __per_height
|
||||
// }
|
||||
thisNode.y = thisLevel2Node.y + __per_height
|
||||
i--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.layoutTimes = 0
|
||||
// var ___this = this
|
||||
this.autoLayout = function(forceLayout) {
|
||||
if (forceLayout) {
|
||||
this.layoutTimes = 0
|
||||
}
|
||||
console.log('this.layoutTimes:', this.layoutTimes)
|
||||
if (this.layoutTimes > 300) {
|
||||
setting.autoLayouting = false
|
||||
return
|
||||
}
|
||||
this.layoutTimes++
|
||||
this.allNodes.forEach(thisNode => {
|
||||
thisNode.Fx = 0
|
||||
thisNode.Fy = 0
|
||||
})
|
||||
var __by_node = true // parseInt(this.layoutTimes / 10) % 2 === 1
|
||||
var __by_line = true // parseInt(this.layoutTimes / 10) % 2 === 0
|
||||
if (__by_node) {
|
||||
for (const i in this.allNodes) {
|
||||
// 循环点,综合点与其他所有点点斥力及方向
|
||||
for (var j in this.allNodes) {
|
||||
// 循环点,计算i点与j点点斥力及方向
|
||||
if (i !== j) {
|
||||
// if (this.allNodes[i].lot.subling.level === this.allNodes[j].lot.subling.level) {
|
||||
this.addGravityByNode(this.allNodes[i], this.allNodes[j])
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (__by_line) {
|
||||
for (const i in this.allNodes) {
|
||||
// 循环线,设置每个点承受点力及力点方向
|
||||
if (this.allNodes[i].lot.parent) {
|
||||
this.addElasticByLine(this.allNodes[i].lot.parent, this.allNodes[i])
|
||||
// break
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (this.layoutTimes % 1 === 0) { // 为提高布局效率,计算五次后更新位置
|
||||
for (const i in this.allNodes) {
|
||||
this.applyToNodePosition(this.allNodes[i])
|
||||
}
|
||||
// }
|
||||
window.setTimeout(function() { this.autoLayout() }.bind(this), 30)
|
||||
}
|
||||
this.stop = function() {
|
||||
this.layoutTimes = 1000
|
||||
}
|
||||
this.addElasticByLine = function(n1, n2) {
|
||||
var length = Math.sqrt(Math.pow((n1.y - n2.y), 2) + Math.pow((n1.x - n2.x), 2))
|
||||
var Kf = length < 30 ? 0 : ((length - 30) * 0.01)
|
||||
this.addFtoNode(n1, (n1.x - n2.x) * Kf * -1, (n1.y - n2.y) * Kf * -1)
|
||||
this.addFtoNode(n2, (n2.x - n1.x) * Kf * -1, (n2.y - n1.y) * Kf * -1)
|
||||
}
|
||||
this.addGravityByNode = function(node1, node2) {
|
||||
var length = Math.sqrt(Math.pow((node1.y - node2.y), 2) + Math.pow((node1.x - node2.x), 2))
|
||||
var Kf = length > 300 ? 0 : ((300 - length) * 0.02)
|
||||
// if (length < 100)Kf = Kf * 2
|
||||
var _buff_x = node1.x - node2.x
|
||||
var _buff_y = node1.y - node2.y
|
||||
if (_buff_x === 0)_buff_x = 1
|
||||
if (_buff_y === 0)_buff_y = 1
|
||||
this.addFtoNode(node1, _buff_x * Kf, _buff_y * Kf)
|
||||
this.addFtoNode(node2, _buff_x * -1 * Kf, _buff_y * -1 * Kf)
|
||||
}
|
||||
this.addFtoNode = function(node, x, y) {
|
||||
node.Fx += x
|
||||
node.Fy += y
|
||||
}
|
||||
this.applyToNodePosition = function(node) {
|
||||
// if (!node.lot.childs || node.lot.childs.length === 0) {
|
||||
// return
|
||||
// }
|
||||
if (this.rootNode === node) {
|
||||
return
|
||||
}
|
||||
// console.log('F add:', node.name, node.Fx, node.Fy)
|
||||
if (node.Fx > 1000)node.Fx = 3000
|
||||
if (node.Fy > 1000)node.Fy = 3000
|
||||
if (node.Fx < -1000)node.Fx = -3000
|
||||
if (node.Fy < -1000)node.Fy = -3000
|
||||
const __buff_x = parseInt(node.Fx * 0.02)
|
||||
const __buff_y = parseInt(node.Fy * 0.02)
|
||||
// console.log('F add:2:', node.name, __buff_x, __buff_y)
|
||||
node.x = node.x + __buff_x
|
||||
node.y = node.y + __buff_y
|
||||
// node.name = __buff_x + ',' + __buff_y
|
||||
// if (node.id === '8') {
|
||||
// console.log(node.id, __buff_x, __buff_y)
|
||||
// // console.log(node.x, node.y)
|
||||
// }
|
||||
node.Fx = 0
|
||||
node.Fy = 0
|
||||
}
|
||||
}
|
||||
|
||||
export default SeeksCenterLayouter
|
||||
@@ -0,0 +1,43 @@
|
||||
import SeeksGraphMath from '../SeeksGraphMath'
|
||||
|
||||
function SeeksFixedLayouter(layoutSetting, graphSetting) {
|
||||
this.graphSetting = graphSetting
|
||||
this.config = layoutSetting || {}
|
||||
this.rootNode = null
|
||||
this.allNodes = []
|
||||
this.__origin_nodes = []
|
||||
this.refresh = function() {
|
||||
this.placeNodes(this.__origin_nodes, this.rootNode)
|
||||
}
|
||||
this.placeNodes = function(allNodes, rootNode) {
|
||||
if (!rootNode) {
|
||||
console.log('root is null:', rootNode)
|
||||
return
|
||||
} else {
|
||||
if (window.SeeksGraphDebug) console.log('layout by root:', rootNode)
|
||||
}
|
||||
this.__origin_nodes = allNodes
|
||||
this.rootNode = rootNode
|
||||
allNodes.forEach(thisNode => {
|
||||
// thisNode.lot = { eached: false }
|
||||
thisNode.lot.eached = false
|
||||
thisNode.lot.notLeafNode = false
|
||||
thisNode.lot.childs = []
|
||||
// thisNode.lot.parent = undefined
|
||||
thisNode.lot.index_of_parent = 0
|
||||
thisNode.lot.strength = 0
|
||||
thisNode.lot.prevNode = undefined
|
||||
thisNode.lot.nextNode = undefined
|
||||
thisNode.lot.placed = false
|
||||
})
|
||||
this.allNodes = []
|
||||
var analyticResult = {
|
||||
max_deep: 1,
|
||||
max_length: 1
|
||||
}
|
||||
SeeksGraphMath.analysisNodes4Didirectional(this.allNodes, [this.rootNode], 0, analyticResult, 0)
|
||||
if (window.SeeksGraphDebug) console.log('[layout canvasOffset]', this.graphSetting.viewSize, this.graphSetting.canvasSize)
|
||||
}
|
||||
}
|
||||
|
||||
export default SeeksFixedLayouter
|
||||
7
cmdb-ui/src/modules/cmdb/3rd/relation-graph/index.js
Normal file
7
cmdb-ui/src/modules/cmdb/3rd/relation-graph/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/* eslint-disable */
|
||||
import RelationGraph from './index.vue'
|
||||
RelationGraph.install = function(Vue) {
|
||||
Vue.component('relation-graph', RelationGraph);
|
||||
Vue.component('seeks-relation-graph', RelationGraph);
|
||||
};
|
||||
export default RelationGraph
|
||||
1321
cmdb-ui/src/modules/cmdb/3rd/relation-graph/index.vue
Normal file
1321
cmdb-ui/src/modules/cmdb/3rd/relation-graph/index.vue
Normal file
File diff suppressed because one or more lines are too long
73
cmdb-ui/src/modules/cmdb/api/CIRelation.js
Normal file
73
cmdb-ui/src/modules/cmdb/api/CIRelation.js
Normal file
@@ -0,0 +1,73 @@
|
||||
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 batchUpdateCIRelationChildren(ciIds, parents) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_relations/batch',
|
||||
method: 'POST',
|
||||
data: { ci_ids: ciIds, parents: parents }
|
||||
})
|
||||
}
|
||||
|
||||
// 批量添加父节点
|
||||
export function batchUpdateCIRelationParents(ciIds, children) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_relations/batch',
|
||||
method: 'POST',
|
||||
data: { ci_ids: ciIds, children: children }
|
||||
})
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
export function batchDeleteCIRelation(ciIds, parents) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_relations/batch',
|
||||
method: 'DELETE',
|
||||
data: { ci_ids: ciIds, parents: parents }
|
||||
})
|
||||
}
|
||||
|
||||
// 单个添加
|
||||
export function addCIRelationView(firstCiId, secondCiId) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
// 单个删除
|
||||
export function deleteCIRelationView(firstCiId, secondCiId) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_relations/${firstCiId}/${secondCiId}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
207
cmdb-ui/src/modules/cmdb/api/CIType.js
Normal file
207
cmdb-ui/src/modules/cmdb/api/CIType.js
Normal file
@@ -0,0 +1,207 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取 所有的 ci_types
|
||||
* @param parameter
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getCITypes(parameter) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_types',
|
||||
method: 'GET',
|
||||
params: parameter
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 某个 ci_types
|
||||
* @param CITypeName
|
||||
* @param parameter
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getCIType(CITypeName, parameter) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeName}`,
|
||||
method: 'GET',
|
||||
params: parameter
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 ci_type
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function createCIType(data) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_types',
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 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
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 ci_type
|
||||
* @param CITypeId
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function deleteCIType(CITypeId) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 某个 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
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存 某个 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
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 某个 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
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 某个 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
|
||||
})
|
||||
}
|
||||
|
||||
export function getUniqueConstraintList(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function addUniqueConstraint(type_id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint`,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateUniqueConstraint(type_id, id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteUniqueConstraint(type_id, id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function getTriggerList(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function addTrigger(type_id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers`,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTrigger(type_id, id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTrigger(type_id, id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
// CMDB的模型和实例的授权接口
|
||||
export function grantCiType(type_id, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/roles/${rid}/grant`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
// CMDB的模型和实例的删除授权接口
|
||||
export function revokeCiType(type_id, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/roles/${rid}/revoke`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
// CMDB的模型和实例的过滤的权限
|
||||
export function ciTypeFilterPermissions(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/filters/permissions`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
155
cmdb-ui/src/modules/cmdb/api/CITypeAttr.js
Normal file
155
cmdb-ui/src/modules/cmdb/api/CITypeAttr.js
Normal file
@@ -0,0 +1,155 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取 ci_type 的属性
|
||||
* @param CITypeName
|
||||
* @param parameter
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getCITypeAttributesByName(CITypeName, parameter) {
|
||||
return axios({
|
||||
|
||||
url: `/v0.1/ci_types/${CITypeName}/attributes`,
|
||||
method: 'get',
|
||||
params: parameter
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ci_type 的属性
|
||||
* @param CITypeId
|
||||
* @param parameter
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getCITypeAttributesById(CITypeId, parameter) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}/attributes`,
|
||||
method: 'get',
|
||||
params: parameter
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新属性
|
||||
* @param attrId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function updateAttributeById(attrId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/attributes/${attrId}`,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加属性
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function createAttribute(data) {
|
||||
return axios({
|
||||
url: `/v0.1/attributes`,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索属性/ 获取所有的属性
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function searchAttributes(params) {
|
||||
return axios({
|
||||
url: `/v0.1/attributes/s`,
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getCITypeAttributesByTypeIds(params) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/attributes`,
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除属性
|
||||
* @param attrId
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function deleteAttributesById(attrId) {
|
||||
return axios({
|
||||
url: `/v0.1/attributes/${attrId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定ci_type 属性
|
||||
* @param CITypeId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function createCITypeAttributes(CITypeId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}/attributes`,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新ci_type 属性
|
||||
* @param CITypeId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function updateCITypeAttributesById(CITypeId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}/attributes`,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除ci_type 属性
|
||||
* @param CITypeId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function deleteCITypeAttributesById(CITypeId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}/attributes`,
|
||||
method: 'delete',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function transferCITypeAttrIndex(CITypeId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}/attributes/transfer`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function transferCITypeGroupIndex(CITypeId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}/attribute_groups/transfer`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function canDefineComputed() {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/can_define_computed`,
|
||||
method: 'HEAD',
|
||||
})
|
||||
}
|
||||
63
cmdb-ui/src/modules/cmdb/api/CITypeRelation.js
Normal file
63
cmdb-ui/src/modules/cmdb/api/CITypeRelation.js
Normal file
@@ -0,0 +1,63 @@
|
||||
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 getRelationTypes(CITypeID, parameter) {
|
||||
return axios({
|
||||
url: '/v0.1/relation_types',
|
||||
method: 'get',
|
||||
params: parameter
|
||||
})
|
||||
}
|
||||
|
||||
export function createRelation(parentId, childrenId, relationTypeId, constraint) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
|
||||
method: 'post',
|
||||
data: { relation_type_id: relationTypeId, constraint }
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRelation(parentId, childrenId) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_type_relations/${parentId}/${childrenId}`,
|
||||
method: 'delete'
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
export function grantTypeRelation(first_type_id, second_type_id, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_type_relations/${first_type_id}/${second_type_id}/roles/${rid}/grant`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function revokeTypeRelation(first_type_id, second_type_id, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_type_relations/${first_type_id}/${second_type_id}/roles/${rid}/revoke`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
67
cmdb-ui/src/modules/cmdb/api/batch.js
Normal file
67
cmdb-ui/src/modules/cmdb/api/batch.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import XLSX from 'xlsx'
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function processFile(fileObj) {
|
||||
return new Promise(function (resolve) {
|
||||
const reader = new FileReader()
|
||||
reader.readAsBinaryString(fileObj)
|
||||
reader.onload = function (e) {
|
||||
const data = e.target.result
|
||||
const workbook = XLSX.read(data, { type: 'binary' })
|
||||
const sheet = workbook.Sheets[workbook.SheetNames[0]]
|
||||
const lt = XLSX.utils.sheet_to_json(sheet, { header: 1 })
|
||||
resolve(lt)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function uploadData(ciId, data) {
|
||||
data.ci_type = ciId
|
||||
data.exist_policy = 'replace'
|
||||
return axios({
|
||||
url: '/v0.1/ci',
|
||||
method: 'POST',
|
||||
data,
|
||||
isShowMessage: false
|
||||
})
|
||||
}
|
||||
|
||||
export function writeCsv(columns) {
|
||||
const { Parser } = require('json2csv')
|
||||
const opts = { columns }
|
||||
const p = new Parser(opts)
|
||||
return p.parse([])
|
||||
}
|
||||
|
||||
export function writeExcel(columns, name) {
|
||||
const worksheet = XLSX.utils.aoa_to_sheet([columns])
|
||||
const newWorkBoot = XLSX.utils.book_new()
|
||||
XLSX.utils.book_append_sheet(newWorkBoot, worksheet, name)
|
||||
const s = XLSX.write(newWorkBoot, { type: 'array' })
|
||||
console.log(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// 判断一个数组元素是否都为空的
|
||||
export function any(ArrayList) {
|
||||
let flag = false
|
||||
for (let i = 0; i < ArrayList.length; i++) {
|
||||
if (ArrayList[i]) {
|
||||
flag = true
|
||||
return flag
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 去除一个二维数组 底下为空的部分
|
||||
export function filterNull(twoDimArray) {
|
||||
console.log(twoDimArray)
|
||||
const newArray = []
|
||||
for (let i = 0; i < twoDimArray.length; i++) {
|
||||
if (any(twoDimArray[i])) {
|
||||
newArray.push(twoDimArray[i])
|
||||
}
|
||||
}
|
||||
return newArray
|
||||
}
|
||||
58
cmdb-ui/src/modules/cmdb/api/ci.js
Normal file
58
cmdb-ui/src/modules/cmdb/api/ci.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v0.1'
|
||||
|
||||
export function searchCI(params) {
|
||||
return axios({
|
||||
url: urlPrefix + `/ci/s`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function searchCI2(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'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取单个ci实例
|
||||
export function getCIById(ciId) {
|
||||
return axios({
|
||||
// url: urlPrefix + `/ci/${ciId}`,
|
||||
url: urlPrefix + `/ci/s?q=_id:${ciId}`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取自动发现占比
|
||||
export function getCIAdcStatistics() {
|
||||
return axios({
|
||||
url: urlPrefix + `/ci/adc/statistics`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
52
cmdb-ui/src/modules/cmdb/api/ciTypeGroup.js
Normal file
52
cmdb-ui/src/modules/cmdb/api/ciTypeGroup.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
const urlPrefix = '/v0.1'
|
||||
|
||||
export function getCITypeGroups(params) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/ci_types/groups`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function postCITypeGroup(data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/ci_types/groups`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function putCITypeGroupByGId(gid, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/ci_types/groups/${gid}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteCITypeGroup(gid, data) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/ci_types/groups/${gid}`,
|
||||
method: 'DELETE',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function getCITypeGroupsConfig(params) {
|
||||
return axios({
|
||||
url: `${urlPrefix}/ci_types/groups/config`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
// 更新模型配置分组的排序
|
||||
export const putCITypeGroups = (data) => {
|
||||
return axios({
|
||||
url: `${urlPrefix}/ci_types/groups/order`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
39
cmdb-ui/src/modules/cmdb/api/customDashboard.js
Normal file
39
cmdb-ui/src/modules/cmdb/api/customDashboard.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getCustomDashboard() {
|
||||
return axios({
|
||||
url: '/v0.1/custom_dashboard',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function postCustomDashboard(data) {
|
||||
return axios({
|
||||
url: '/v0.1/custom_dashboard',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function putCustomDashboard(id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/custom_dashboard/${id}`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteCustomDashboard(id) {
|
||||
return axios({
|
||||
url: `/v0.1/custom_dashboard/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function batchUpdateCustomDashboard(data) {
|
||||
return axios({
|
||||
url: `/v0.1/custom_dashboard/batch`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
120
cmdb-ui/src/modules/cmdb/api/discovery.js
Normal file
120
cmdb-ui/src/modules/cmdb/api/discovery.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getDiscovery() {
|
||||
return axios({
|
||||
url: `/v0.1/adr`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function postDiscovery(data) {
|
||||
return axios({
|
||||
url: `/v0.1/adr`,
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function putDiscovery(id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/adr/${id}`,
|
||||
method: 'PUT',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteDiscovery(id) {
|
||||
return axios({
|
||||
url: `/v0.1/adr/${id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
export function getHttpCategories(name) {
|
||||
return axios({
|
||||
url: `/v0.1/adr/http/${name}/categories`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function getHttpAttributes(name, params) {
|
||||
return axios({
|
||||
url: `/v0.1/adr/http/${name}/attributes`,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getSnmpAttributes(name) {
|
||||
return axios({
|
||||
url: `/v0.1/adr/snmp/${name}/attributes`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function getCITypeDiscovery(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/adt/ci_types/${type_id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
export function postCITypeDiscovery(type_id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/adt/ci_types/${type_id}`,
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function putCITypeDiscovery(adt_id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/adt/${adt_id}`,
|
||||
method: 'PUT',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteCITypeDiscovery(id) {
|
||||
return axios({
|
||||
url: `/v0.1/adt/${id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
export function getADCCiTypes(params) {
|
||||
return axios({
|
||||
url: `/v0.1/adc/ci_types`,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getADCCiTypesAttrs(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/adc/ci_types/${type_id}/attributes`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function updateADCAccept(adc_id) {
|
||||
return axios({
|
||||
url: `/v0.1/adc/${adc_id}/accept`,
|
||||
method: 'PUT'
|
||||
})
|
||||
}
|
||||
|
||||
export function getAdc(params) {
|
||||
return axios({
|
||||
url: `v0.1/adc`,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteAdc(adc_id) {
|
||||
return axios({
|
||||
url: `v0.1/adc/${adc_id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
40
cmdb-ui/src/modules/cmdb/api/history.js
Normal file
40
cmdb-ui/src/modules/cmdb/api/history.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getCIHistory (ciId) {
|
||||
return axios({
|
||||
url: `/v0.1/history/ci/${ciId}`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function getCIHistoryTable (params) {
|
||||
return axios({
|
||||
url: `/v0.1/history/records/attribute`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getRelationTable (params) {
|
||||
return axios({
|
||||
url: `/v0.1/history/records/relation`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getCITypesTable (params) {
|
||||
return axios({
|
||||
url: `/v0.1/history/ci_types`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getUsers (params) {
|
||||
return axios({
|
||||
url: `/v1/acl/users/employee`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
8
cmdb-ui/src/modules/cmdb/api/perm.js
Normal file
8
cmdb-ui/src/modules/cmdb/api/perm.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getWX() {
|
||||
return axios({
|
||||
url: '/v1/acl/users',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
116
cmdb-ui/src/modules/cmdb/api/preference.js
Normal file
116
cmdb-ui/src/modules/cmdb/api/preference.js
Normal file
@@ -0,0 +1,116 @@
|
||||
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 getPreference2(instance = true, tree = null) {
|
||||
return axios({
|
||||
url: '/v0.1/preference/ci_types2',
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
// 用户保存条件过滤选项
|
||||
export function getPreferenceSearch(payload) {
|
||||
// 参数有prv_id: 关系视图的id, ptv_id: 层级视图的id, type_id: 模型id
|
||||
return axios({
|
||||
url: `/v0.1/preference/search/option`,
|
||||
method: 'GET',
|
||||
params: payload
|
||||
})
|
||||
}
|
||||
|
||||
export function savePreferenceSearch(payload) {
|
||||
// 参数包括GET的参数 ,必须参数name,option option是个json
|
||||
return axios({
|
||||
url: `/v0.1/preference/search/option`,
|
||||
method: 'POST',
|
||||
data: payload
|
||||
})
|
||||
}
|
||||
|
||||
export function deletePreferenceSearch(id) {
|
||||
return axios({
|
||||
url: `/v0.1/preference/search/option/${id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
// 服务树授权
|
||||
export function grantRelationView(rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/preference/relation/view/roles/${rid}/grant`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 服务树权限回收
|
||||
export function revokeRelationView(rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/preference/relation/view/roles/${rid}/revoke`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
31
cmdb-ui/src/modules/cmdb/api/relationType.js
Normal file
31
cmdb-ui/src/modules/cmdb/api/relationType.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getRelationTypes () {
|
||||
return axios({
|
||||
url: '/v0.1/relation_types',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function addRelationType (payload) {
|
||||
return axios({
|
||||
url: `/v0.1/relation_types`,
|
||||
method: 'POST',
|
||||
data: payload
|
||||
})
|
||||
}
|
||||
|
||||
export function updateRelationType (rtId, payload) {
|
||||
return axios({
|
||||
url: `/v0.1/relation_types/${rtId}`,
|
||||
method: 'PUT',
|
||||
data: payload
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRelationType (rtId) {
|
||||
return axios({
|
||||
url: `/v0.1/relation_types/${rtId}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
8
cmdb-ui/src/modules/cmdb/api/statistics.js
Normal file
8
cmdb-ui/src/modules/cmdb/api/statistics.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getStatistics() {
|
||||
return axios({
|
||||
url: '/v0.1/statistics',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
19
cmdb-ui/src/modules/cmdb/api/system_config.js
Normal file
19
cmdb-ui/src/modules/cmdb/api/system_config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
// 保存布局
|
||||
export function saveSystemConfig(data) {
|
||||
return axios({
|
||||
url: '/v0.1/system_config',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取布局
|
||||
export function getSystemConfig(params) {
|
||||
return axios({
|
||||
url: '/v0.1/system_config',
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
BIN
cmdb-ui/src/modules/cmdb/assets/preference_background.png
Normal file
BIN
cmdb-ui/src/modules/cmdb/assets/preference_background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
cmdb-ui/src/modules/cmdb/assets/preference_card.png
Normal file
BIN
cmdb-ui/src/modules/cmdb/assets/preference_card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
wrapClassName="ci-json-editor"
|
||||
:closable="false"
|
||||
:maskClosable="false"
|
||||
@cancel="handleCancel"
|
||||
width="50%"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<vue-json-editor
|
||||
:style="{ '--custom-height': `${windowHeight - 300}px` }"
|
||||
v-model="jsonData"
|
||||
:showBtns="false"
|
||||
:mode="'code'"
|
||||
lang="zh"
|
||||
@has-error="onJsonError"
|
||||
@json-change="onJsonChange"
|
||||
/>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import vueJsonEditor from 'vue-json-editor'
|
||||
import { updateCI } from '@/modules/cmdb/api/ci'
|
||||
export default {
|
||||
name: 'JsonEditor',
|
||||
components: { vueJsonEditor },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
jsonData: {},
|
||||
row: {},
|
||||
column: {},
|
||||
default_value_json_right: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
open(column, row, jsonData) {
|
||||
this.visible = true
|
||||
if (row && row[column.property]) {
|
||||
this.jsonData = JSON.parse(row[column.property]) || {}
|
||||
} else {
|
||||
this.jsonData = {}
|
||||
}
|
||||
if (jsonData) {
|
||||
this.jsonData = jsonData
|
||||
}
|
||||
|
||||
this.row = row
|
||||
this.column = column
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
handleOk() {
|
||||
if (this.row && this.column) {
|
||||
updateCI(this.row.ci_id || this.row._id, {
|
||||
[`${this.column.property}`]: this.default_value_json_right ? this.jsonData : {},
|
||||
}).then(() => {
|
||||
this.$message.success('保存成功!')
|
||||
this.handleCancel()
|
||||
this.$emit('jsonEditorOk', this.row, this.column, this.default_value_json_right ? this.jsonData : {})
|
||||
})
|
||||
} else {
|
||||
this.$emit('jsonEditorOk', this.jsonData)
|
||||
this.handleCancel()
|
||||
}
|
||||
},
|
||||
onJsonChange(value) {
|
||||
this.default_value_json_right = true
|
||||
},
|
||||
onJsonError() {
|
||||
this.default_value_json_right = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ci-json-editor {
|
||||
.jsoneditor-outer {
|
||||
height: var(--custom-height) !important;
|
||||
border: 1px solid #2f54eb;
|
||||
}
|
||||
div.jsoneditor-menu {
|
||||
background-color: #2f54eb;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
298
cmdb-ui/src/modules/cmdb/components/attributesTransfer/index.vue
Normal file
298
cmdb-ui/src/modules/cmdb/components/attributesTransfer/index.vue
Normal file
@@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-transfer
|
||||
:dataSource="dataSource"
|
||||
:showSearch="true"
|
||||
:listStyle="{
|
||||
width: '200px',
|
||||
height: `${height}px`,
|
||||
}"
|
||||
:titles="['未选属性', '已选属性']"
|
||||
:render="(item) => item.title"
|
||||
:targetKeys="targetKeys"
|
||||
@change="handleChange"
|
||||
@selectChange="selectChange"
|
||||
:selectedKeys="selectedKeys"
|
||||
:filterOption="filterOption"
|
||||
class="cmdb-transfer"
|
||||
>
|
||||
<span slot="notFoundContent">暂无数据</span>
|
||||
<template slot="children" slot-scope="{ props: { direction, filteredItems } }">
|
||||
<div class="ant-transfer-list-content" v-if="direction === 'right'">
|
||||
<draggable :value="targetKeys" animation="300" @end="dragEnd" :disabled="!isSortable">
|
||||
<div
|
||||
@dblclick="changeSingleItem(item)"
|
||||
v-for="item in filteredItems"
|
||||
:key="item.key"
|
||||
:style="{ height: '38px' }"
|
||||
>
|
||||
<li
|
||||
:class="{
|
||||
'ant-transfer-list-content-item': true,
|
||||
'ant-transfer-list-content-item-selected': selectedKeys.includes(item.key),
|
||||
}"
|
||||
@click="setSelectedKeys(item)"
|
||||
>
|
||||
<OpsMoveIcon class="move-icon" />
|
||||
<div :class="`ant-transfer-list-content-item-text`" style="display: inline">
|
||||
{{ item.title }}
|
||||
<span
|
||||
:style="{ position: 'absolute', top: '15px', left: '34px', fontSize: '11px', color: '#a3a3a3' }"
|
||||
>{{ item.name }}</span
|
||||
>
|
||||
</div>
|
||||
<div v-if="isFixable" @click="(e) => changeFixed(e, item)" class="ant-transfer-list-lock-icon">
|
||||
<a-icon
|
||||
:type="fixedList.includes(item.key) ? 'lock' : 'unlock'"
|
||||
:theme="fixedList.includes(item.key) ? 'filled' : 'outlined'"
|
||||
/>
|
||||
</div>
|
||||
<div class="ant-transfer-list-icon" :style="{ left: '17px' }" @click="changeSingleItem(item)">
|
||||
<a-icon type="left" />
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
<div v-if="direction === 'left'" class="ant-transfer-list-content">
|
||||
<div
|
||||
@dblclick="changeSingleItem(item)"
|
||||
v-for="item in filteredItems"
|
||||
:key="item.key"
|
||||
:style="{ height: '38px' }"
|
||||
>
|
||||
<li
|
||||
:class="`ant-transfer-list-content-item ${
|
||||
selectedKeys.includes(item.key) ? 'ant-transfer-list-content-item-selected' : ''
|
||||
}`"
|
||||
@click="setSelectedKeys(item)"
|
||||
>
|
||||
<div class="ant-transfer-list-content-item-text" style="display: inline">
|
||||
{{ item.title }}
|
||||
<span
|
||||
:style="{ position: 'absolute', top: '15px', left: '34px', fontSize: '11px', color: '#a3a3a3' }"
|
||||
>{{ item.name }}</span
|
||||
>
|
||||
</div>
|
||||
<div @click="changeSingleItem(item)" :style="{ left: '4px' }" class="ant-transfer-list-icon">
|
||||
<a-icon type="right" />
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-transfer>
|
||||
<div v-if="hasFooter" :style="{ marginTop: '5px', height: '20px' }">
|
||||
<a-button :style="{ float: 'right' }" size="small" @click="handleSubmit" type="primary">确定</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import draggable from 'vuedraggable'
|
||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||
|
||||
export default {
|
||||
name: 'AttributesTransfer',
|
||||
components: { draggable, OpsMoveIcon },
|
||||
props: {
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
targetKeys: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
hasFooter: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isSortable: {
|
||||
// 右侧是否可排序
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isFixable: {
|
||||
// 右侧是否可固定
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
fixedList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 400,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedKeys: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectChange(sourceSelectedKeys, targetSelectedKeys) {
|
||||
const _selectedKeys = _.cloneDeep(this.selectedKeys)
|
||||
const list = [
|
||||
{ data: sourceSelectedKeys, name: 'source' },
|
||||
{ data: targetSelectedKeys, name: 'target' },
|
||||
]
|
||||
list.forEach((item) => {
|
||||
if (!item.data.__ob__) {
|
||||
if (item.data.length) {
|
||||
item.data.forEach((key) => {
|
||||
const idx = _selectedKeys.findIndex((selected) => selected === key)
|
||||
if (idx > -1) {
|
||||
} else {
|
||||
_selectedKeys.push(key)
|
||||
}
|
||||
})
|
||||
this.selectedKeys = _.cloneDeep(_selectedKeys)
|
||||
} else {
|
||||
let _list = []
|
||||
if (item.name === 'source') {
|
||||
_list = _selectedKeys.filter((key) => this.targetKeys.includes(key))
|
||||
} else {
|
||||
_list = _selectedKeys.filter((key) => !this.targetKeys.includes(key))
|
||||
}
|
||||
this.selectedKeys = _list
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
setSelectedKeys(item) {
|
||||
const idx = this.selectedKeys.findIndex((key) => key === item.key)
|
||||
if (idx > -1) {
|
||||
this.selectedKeys.splice(idx, 1)
|
||||
} else {
|
||||
this.selectedKeys.push(item.key)
|
||||
}
|
||||
},
|
||||
handleChange(targetKeys, direction, moveKeys) {
|
||||
const _selectedKeys = _.cloneDeep(this.selectedKeys)
|
||||
moveKeys.forEach((key) => {
|
||||
const idx = _selectedKeys.findIndex((selected) => selected === key)
|
||||
if (idx > -1) {
|
||||
_selectedKeys.splice(idx, 1)
|
||||
}
|
||||
})
|
||||
this.selectedKeys = _.cloneDeep(_selectedKeys)
|
||||
this.$emit('setTargetKeys', targetKeys)
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$emit('handleSubmit')
|
||||
},
|
||||
changeSingleItem(item) {
|
||||
this.$emit('changeSingleItem', item)
|
||||
},
|
||||
dragEnd(e) {
|
||||
const { newIndex, oldIndex } = e
|
||||
const _targetKeys = _.cloneDeep(this.targetKeys)
|
||||
const _item = _targetKeys.splice(oldIndex, 1)[0]
|
||||
_targetKeys.splice(newIndex, 0, _item)
|
||||
this.$emit('setTargetKeys', _targetKeys)
|
||||
},
|
||||
filterOption(inputValue, option) {
|
||||
return (
|
||||
option.title.toLowerCase().includes(inputValue.toLowerCase()) ||
|
||||
option.name.toLowerCase().includes(inputValue.toLowerCase())
|
||||
)
|
||||
},
|
||||
changeFixed(e, item) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
const _fixedList = _.cloneDeep(this.fixedList)
|
||||
const idx = _fixedList.findIndex((key) => key === item.key)
|
||||
if (idx > -1) {
|
||||
_fixedList.splice(idx, 1)
|
||||
} else {
|
||||
_fixedList.push(item.key)
|
||||
}
|
||||
this.$emit('setFixedList', _fixedList)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.cmdb-transfer {
|
||||
.ant-transfer-list {
|
||||
background-color: #f9fbff;
|
||||
border-color: #e4e7ed;
|
||||
.ant-transfer-list-header {
|
||||
background-color: #f9fbff;
|
||||
border-bottom: none;
|
||||
.ant-transfer-list-header-title {
|
||||
color: #custom_colors[color_1];
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.ant-transfer-list-body-search-wrapper {
|
||||
padding-top: 0;
|
||||
padding-bottom: 5px;
|
||||
.ant-transfer-list-search-action {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.ant-transfer-list-body-customize-wrapper {
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
max-height: calc(100% - 44px);
|
||||
.ant-transfer-list-content-item {
|
||||
transition: all 0.3s;
|
||||
border-left: 2px solid #f9fbff;
|
||||
padding: 0 12px 8px 25px;
|
||||
position: relative;
|
||||
.ant-transfer-list-icon {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
background-color: #fff;
|
||||
color: #custom_colors[color_1];
|
||||
border-radius: 4px;
|
||||
width: 12px;
|
||||
}
|
||||
.ant-transfer-list-lock-icon {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 4px;
|
||||
display: inline;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: #cacdd9;
|
||||
&:hover {
|
||||
color: #custom_colors[color_1];
|
||||
}
|
||||
}
|
||||
.move-icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 6px;
|
||||
display: none;
|
||||
width: 14px;
|
||||
height: 20px;
|
||||
}
|
||||
&:hover .ant-transfer-list-icon {
|
||||
display: inline;
|
||||
}
|
||||
&:hover .move-icon {
|
||||
display: inline !important;
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
.ant-transfer-list-content-item-selected {
|
||||
background-color: #custom_colors[color_2];
|
||||
border-color: #custom_colors[color_1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<a-modal :visible="visible" title="导出数据" @cancel="handleCancel" okText="导出" @ok="handleOk">
|
||||
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }">
|
||||
<a-form-item label="文件名">
|
||||
<a-input
|
||||
placeholder="请输入文件名"
|
||||
v-decorator="['filename', { rules: [{ required: true, message: '请输入文件名' }] }]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="保存类型">
|
||||
<a-select
|
||||
placeholder="请选择保存类型"
|
||||
v-decorator="['type', { rules: [{ required: true, message: '请选择保存类型' }], initialValue: 'xlsx' }]"
|
||||
>
|
||||
<a-select-option v-for="item in typeList" :key="item.id" :values="item.id">{{ item.label }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="选择字段">
|
||||
<div
|
||||
:style="{
|
||||
paddingLeft: '26px',
|
||||
backgroundColor: '#e9e9e9',
|
||||
borderTopLeftRadius: '5px',
|
||||
borderTopRightRadius: '5px',
|
||||
}"
|
||||
>
|
||||
<a-checkbox
|
||||
:indeterminate="indeterminate"
|
||||
:checked="checkAll"
|
||||
@change="onCheckAllChange"
|
||||
:style="{ marginRight: '10px' }"
|
||||
/>全选
|
||||
</div>
|
||||
<div
|
||||
:style="{
|
||||
height: '200px',
|
||||
overflow: 'auto',
|
||||
borderLeft: '1px solid #e9e9e9',
|
||||
borderBottom: '1px solid #e9e9e9',
|
||||
}"
|
||||
>
|
||||
<a-tree
|
||||
checkable
|
||||
defaultExpandAll
|
||||
:selectable="false"
|
||||
:auto-expand-parent="true"
|
||||
:tree-data="preferenceAttrList"
|
||||
:replaceFields="replaceFields"
|
||||
:checkedKeys="checkedKeys"
|
||||
@check="check"
|
||||
/>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from 'vuex'
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import Treeselect from '@riophae/vue-treeselect'
|
||||
export default {
|
||||
name: 'BatchDownload',
|
||||
components: { Treeselect },
|
||||
props: {
|
||||
replaceFields: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return { children: 'children', title: 'alias', key: 'name' }
|
||||
},
|
||||
},
|
||||
treeType: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const typeList = [
|
||||
{
|
||||
id: 'xlsx',
|
||||
label: 'Excel工作簿(*.xlsx)',
|
||||
},
|
||||
{
|
||||
id: 'csv',
|
||||
label: 'CSV(逗号分隔)(*.csv)',
|
||||
},
|
||||
{
|
||||
id: 'html',
|
||||
label: '网页(*.html)',
|
||||
},
|
||||
{
|
||||
id: 'xml',
|
||||
label: 'XML数据(*.xml)',
|
||||
},
|
||||
{
|
||||
id: 'txt',
|
||||
label: '文本文件(制表符分隔)(*.txt)',
|
||||
},
|
||||
]
|
||||
return {
|
||||
typeList,
|
||||
visible: false,
|
||||
form: this.$form.createForm(this),
|
||||
preferenceAttrList: [],
|
||||
checkedKeys: [],
|
||||
checkAll: false,
|
||||
indeterminate: false,
|
||||
defaultChecked: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('cmdbStore', ['SET_IS_TABLE_LOADING']),
|
||||
open({ preferenceAttrList }) {
|
||||
this.preferenceAttrList = preferenceAttrList
|
||||
this.visible = true
|
||||
this.$nextTick((res) => {
|
||||
this.form.setFieldsValue({
|
||||
filename: `cmdb-${moment().format('YYYYMMDDHHmmss')}`,
|
||||
})
|
||||
if (this.treeType === 'tree') {
|
||||
const _check = ['ci_type_alias']
|
||||
preferenceAttrList.forEach((colGroup) => {
|
||||
if (colGroup.children && colGroup.children.length) {
|
||||
_check.push(...colGroup.children.map((attr) => attr[`${this.replaceFields.key}`]))
|
||||
}
|
||||
})
|
||||
this.defaultChecked = _check
|
||||
this.checkedKeys = _check
|
||||
} else {
|
||||
this.checkedKeys = preferenceAttrList.map((attr) => attr[`${this.replaceFields.key}`])
|
||||
}
|
||||
this.checkAll = true
|
||||
this.indeterminate = false
|
||||
})
|
||||
},
|
||||
check(checkedKeys) {
|
||||
if (this.treeType === 'tree') {
|
||||
this.checkedKeys = checkedKeys.filter((item) => !item.startsWith('parent-'))
|
||||
} else {
|
||||
this.checkedKeys = checkedKeys
|
||||
}
|
||||
if (this.treeType === 'tree') {
|
||||
const isEqual = _.isEqual(this.checkedKeys.length, this.defaultChecked.length)
|
||||
this.checkAll = isEqual
|
||||
this.indeterminate = !!checkedKeys.length && !isEqual
|
||||
return
|
||||
}
|
||||
this.checkAll = this.checkedKeys.length === this.preferenceAttrList.length
|
||||
this.indeterminate = !!checkedKeys.length && checkedKeys.length < this.preferenceAttrList.length
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
handleOk() {
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
this.SET_IS_TABLE_LOADING(true)
|
||||
this.$nextTick(() => {
|
||||
this.$emit('batchDownload', { ...values, checkedKeys: this.checkedKeys })
|
||||
setTimeout(() => {
|
||||
this.SET_IS_TABLE_LOADING(false)
|
||||
this.handleCancel()
|
||||
}, 2000)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onCheckAllChange(e) {
|
||||
Object.assign(this, {
|
||||
checkedKeys: e.target.checked ? this.preferenceAttrList.map((attr) => attr[`${this.replaceFields.key}`]) : [],
|
||||
indeterminate: false,
|
||||
checkAll: e.target.checked,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
139
cmdb-ui/src/modules/cmdb/components/cmdbGrant/ciTypeGrant.vue
Normal file
139
cmdb-ui/src/modules/cmdb/components/cmdbGrant/ciTypeGrant.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div class="ci-type-grant">
|
||||
<vxe-table size="mini" stripe class="ops-stripe-table" :data="filterTableData" :max-height="`${tableHeight}px`">
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<ReadCheckbox
|
||||
v-if="['read'].includes(col.split('_')[0])"
|
||||
:value="row[col.split('_')[0]]"
|
||||
:valueKey="col"
|
||||
:rid="row.rid"
|
||||
@openReadGrantModal="() => openReadGrantModal(col, row)"
|
||||
/>
|
||||
<a-checkbox v-else-if="col === 'grant'" :checked="row[col]" @click="clickGrant(col, row)"></a-checkbox>
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-else v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<template #empty>
|
||||
<div v-if="loading()" style="height: 200px; line-height: 200px;color:#2F54EB">
|
||||
<a-icon type="loading" /> 加载中...
|
||||
</div>
|
||||
<div v-else>
|
||||
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
|
||||
<div>暂无数据</div>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">授权用户/部门</span>
|
||||
<!-- <span class="grant-button" @click="grantRole">授权角色</span> -->
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { permMap } from './constants.js'
|
||||
import { grantCiType, revokeCiType } from '../../api/CIType'
|
||||
import ReadCheckbox from './readCheckbox.vue'
|
||||
|
||||
export default {
|
||||
name: 'CiTypeGrant',
|
||||
components: { ReadCheckbox },
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'ci_type',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
filterTableData() {
|
||||
console.log(_.cloneDeep(this.tableData))
|
||||
const _tableData = this.tableData.filter((data) => {
|
||||
const _intersection = _.intersection(
|
||||
Object.keys(data),
|
||||
this.columns.map((col) => col.split('_')[0])
|
||||
)
|
||||
return _intersection && _intersection.length
|
||||
})
|
||||
return _.uniqBy(_tableData, (item) => item.rid)
|
||||
},
|
||||
columns() {
|
||||
if (this.grantType === 'ci_type') {
|
||||
return ['config', 'grant']
|
||||
}
|
||||
return ['read_attr', 'read_ci', 'create', 'update', 'delete']
|
||||
},
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
permMap,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleChange(e, col, row) {
|
||||
if (e.target.checked) {
|
||||
await grantCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
await revokeCiType(this.CITypeId, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
openReadGrantModal(col, row) {
|
||||
this.$emit('openReadGrantModal', col, row)
|
||||
},
|
||||
clickGrant(col, row, rowIndex) {
|
||||
if (!row[col]) {
|
||||
this.handleChange({ target: { checked: true } }, col, row)
|
||||
const _idx = this.tableData.findIndex((item) => item.rid === row.rid)
|
||||
this.$set(this.tableData, _idx, { ...this.tableData[_idx], grant: true })
|
||||
} else {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '警告',
|
||||
content: `确认删除 【${row.name}】 的 【授权】 权限?`,
|
||||
onOk() {
|
||||
that.handleChange({ target: { checked: false } }, col, row)
|
||||
const _idx = that.tableData.findIndex((item) => item.rid === row.rid)
|
||||
that.$set(that.tableData, _idx, { ...that.tableData[_idx], grant: false })
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-type-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
11
cmdb-ui/src/modules/cmdb/components/cmdbGrant/constants.js
Normal file
11
cmdb-ui/src/modules/cmdb/components/cmdbGrant/constants.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const permMap = {
|
||||
read: '查看',
|
||||
add: '新增',
|
||||
create: '新增',
|
||||
update: '修改',
|
||||
delete: '删除',
|
||||
config: '配置',
|
||||
grant: '授权',
|
||||
'read_attr': '查看字段',
|
||||
'read_ci': '查看实例'
|
||||
}
|
||||
319
cmdb-ui/src/modules/cmdb/components/cmdbGrant/grantComp.vue
Normal file
319
cmdb-ui/src/modules/cmdb/components/cmdbGrant/grantComp.vue
Normal file
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 104}px` }">
|
||||
<template v-if="cmdbGrantType.includes('ci_type')">
|
||||
<div class="cmdb-grant-title">模型权限</div>
|
||||
<CiTypeGrant
|
||||
:CITypeId="CITypeId"
|
||||
:tableData="tableData"
|
||||
grantType="ci_type"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
/>
|
||||
</template>
|
||||
<template
|
||||
v-if="
|
||||
cmdbGrantType.includes('ci_type,ci') || (cmdbGrantType.includes('ci') && !cmdbGrantType.includes('ci_type'))
|
||||
"
|
||||
>
|
||||
<div class="cmdb-grant-title">实例权限</div>
|
||||
<CiTypeGrant
|
||||
:CITypeId="CITypeId"
|
||||
:tableData="tableData"
|
||||
grantType="ci"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
@openReadGrantModal="openReadGrantModal"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="cmdbGrantType.includes('type_relation')">
|
||||
<div class="cmdb-grant-title">关系权限</div>
|
||||
<TypeRelationGrant
|
||||
:typeRelationIds="typeRelationIds"
|
||||
:tableData="tableData"
|
||||
grantType="type_relation"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="cmdbGrantType.includes('relation_view')">
|
||||
<div class="cmdb-grant-title">{{ resourceTypeName }}权限</div>
|
||||
<RelationViewGrant
|
||||
:resourceTypeName="resourceTypeName"
|
||||
:tableData="tableData"
|
||||
grantType="relation_view"
|
||||
@grantDepart="grantDepart"
|
||||
@grantRole="grantRole"
|
||||
@getTableData="getTableData"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<GrantModal ref="grantModal" @handleOk="handleOk" />
|
||||
<ReadGrantModal ref="readGrantModal" :CITypeId="CITypeId" @updateTableDataRead="updateTableDataRead" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import CiTypeGrant from './ciTypeGrant.vue'
|
||||
import TypeRelationGrant from './typeRelationGrant.vue'
|
||||
import { searchResource } from '@/modules/acl/api/resource'
|
||||
import { getResourcePerms } from '@/modules/acl/api/permission'
|
||||
import GrantModal from './grantModal.vue'
|
||||
import ReadGrantModal from './readGrantModal'
|
||||
import RelationViewGrant from './relationViewGrant.vue'
|
||||
import { getCITypeGroupById, ciTypeFilterPermissions } from '../../api/CIType'
|
||||
|
||||
export default {
|
||||
name: 'GrantComp',
|
||||
components: { CiTypeGrant, TypeRelationGrant, RelationViewGrant, GrantModal, ReadGrantModal },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
resourceTypeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
resourceType: {
|
||||
type: String,
|
||||
default: 'CIType',
|
||||
},
|
||||
app_id: {
|
||||
type: String,
|
||||
default: 'cmdb',
|
||||
},
|
||||
cmdbGrantType: {
|
||||
type: String,
|
||||
default: 'ci_type,ci',
|
||||
},
|
||||
typeRelationIds: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
isModal: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
inject: ['resource_type'],
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
grantType: '',
|
||||
resource_id: null,
|
||||
attrGroup: [],
|
||||
filerPerimissions: {},
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
allEmployees: (state) => state.user.allEmployees,
|
||||
allDepartments: (state) => state.user.allDepartments,
|
||||
}),
|
||||
child_resource_type() {
|
||||
return this.resource_type()
|
||||
},
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
attrGroup: () => {
|
||||
return this.attrGroup
|
||||
},
|
||||
filerPerimissions: () => {
|
||||
return this.filerPerimissions
|
||||
},
|
||||
loading: () => {
|
||||
return this.loading
|
||||
},
|
||||
isModal: this.isModal,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
resourceTypeName: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.init()
|
||||
},
|
||||
},
|
||||
CITypeId: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
if (this.CITypeId && this.cmdbGrantType.includes('ci')) {
|
||||
this.getFilterPermissions()
|
||||
this.getAttrGroup()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
getAttrGroup() {
|
||||
getCITypeGroupById(this.CITypeId, { need_other: true }).then((res) => {
|
||||
this.attrGroup = res
|
||||
})
|
||||
},
|
||||
getFilterPermissions() {
|
||||
ciTypeFilterPermissions(this.CITypeId).then((res) => {
|
||||
this.filerPerimissions = res
|
||||
})
|
||||
},
|
||||
async init() {
|
||||
const _find = this.child_resource_type.groups.find((item) => item.name === this.resourceType)
|
||||
const resource_type_id = _find?.id ?? 0
|
||||
const res = await searchResource({
|
||||
app_id: this.app_id,
|
||||
resource_type_id,
|
||||
page_size: 9999,
|
||||
})
|
||||
const _tempFind = res.resources.find((item) => item.name === this.resourceTypeName)
|
||||
console.log(this.resourceTypeName)
|
||||
this.resource_id = _tempFind?.id || 0
|
||||
this.getTableData()
|
||||
},
|
||||
async getTableData() {
|
||||
this.loading = true
|
||||
const _tableData = await getResourcePerms(this.resource_id, { need_users: 0 })
|
||||
const perms = []
|
||||
for (const key in _tableData) {
|
||||
const obj = {}
|
||||
obj.name = key
|
||||
_tableData[key].perms.forEach((perm) => {
|
||||
obj[`${perm.name}`] = true
|
||||
obj.rid = perm?.rid ?? null
|
||||
})
|
||||
perms.push(obj)
|
||||
}
|
||||
this.tableData = perms
|
||||
this.loading = false
|
||||
},
|
||||
// 授权common-setting中的部门 从中拿到roleid
|
||||
grantDepart(grantType) {
|
||||
this.$refs.grantModal.open('depart')
|
||||
this.grantType = grantType
|
||||
},
|
||||
// 授权common-setting中的角色 从中拿到roleid
|
||||
grantRole(grantType) {
|
||||
this.$refs.grantModal.open('role')
|
||||
this.grantType = grantType
|
||||
},
|
||||
handleOk(params) {
|
||||
const { grantType } = this
|
||||
console.log(params)
|
||||
const rids = [
|
||||
...params.department.map((rid) => {
|
||||
const _find = this.allDepartments.find((dep) => dep.acl_rid === rid)
|
||||
return { rid, name: _find?.department_name ?? rid }
|
||||
}),
|
||||
...params.user.map((rid) => {
|
||||
const _find = this.allEmployees.find((dep) => dep.acl_rid === rid)
|
||||
return { rid, name: _find?.nickname ?? rid }
|
||||
}),
|
||||
]
|
||||
if (grantType === 'ci_type') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
const _find = this.tableData.find((item) => item.rid === rid)
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
conifg: false,
|
||||
grant: false,
|
||||
..._find,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'ci') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
const _find = this.tableData.find((item) => item.rid === rid)
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
read_attr: false,
|
||||
read_ci: false,
|
||||
create: false,
|
||||
update: false,
|
||||
delete: false,
|
||||
..._find,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'type_relation') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
create: false,
|
||||
grant: false,
|
||||
delete: false,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (grantType === 'relation_view') {
|
||||
this.tableData.unshift(
|
||||
...rids.map(({ rid, name }) => {
|
||||
return {
|
||||
rid,
|
||||
name,
|
||||
read: false,
|
||||
grant: false,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
openReadGrantModal(col, row) {
|
||||
this.$refs.readGrantModal.open(col, row)
|
||||
},
|
||||
updateTableDataRead(row, hasRead) {
|
||||
const _idx = this.tableData.findIndex((item) => item.rid === row.rid)
|
||||
this.$set(this.tableData, _idx, { ...this.tableData[_idx], read: hasRead })
|
||||
this.getFilterPermissions()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-grant {
|
||||
position: relative;
|
||||
padding: 24px 24px 0 24px;
|
||||
overflow: auto;
|
||||
.cmdb-grant-title {
|
||||
border-left: 4px solid #custom_colors[color_1];
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
.cmdb-grant {
|
||||
.grant-button {
|
||||
padding: 6px 8px;
|
||||
color: #custom_colors[color_1];
|
||||
background-color: #custom_colors[color_2];
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin: 15px 0;
|
||||
display: inline-block;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
box-shadow: 2px 3px 4px #custom_colors[color_2];
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
49
cmdb-ui/src/modules/cmdb/components/cmdbGrant/grantModal.vue
Normal file
49
cmdb-ui/src/modules/cmdb/components/cmdbGrant/grantModal.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<a-modal :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel" destroyOnClose>
|
||||
<EmployeeTransfer
|
||||
:isDisabledAllCompany="true"
|
||||
v-if="type === 'depart'"
|
||||
uniqueKey="acl_rid"
|
||||
ref="employeeTransfer"
|
||||
:height="350"
|
||||
/>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmployeeTransfer from '@/components/EmployeeTransfer'
|
||||
export default {
|
||||
name: 'GrantModal',
|
||||
components: { EmployeeTransfer },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
type: 'depart',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
if (this.type === 'depart') {
|
||||
return '授权用户/部门'
|
||||
}
|
||||
return '授权角色'
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
open(type) {
|
||||
this.visible = true
|
||||
this.type = type
|
||||
},
|
||||
handleOk() {
|
||||
const params = this.$refs.employeeTransfer.getValues()
|
||||
this.handleCancel()
|
||||
this.$emit('handleOk', params)
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
57
cmdb-ui/src/modules/cmdb/components/cmdbGrant/index.vue
Normal file
57
cmdb-ui/src/modules/cmdb/components/cmdbGrant/index.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<a-modal width="800px" :visible="visible" @ok="handleOk" @cancel="handleCancel" :bodyStyle="{ padding: 0 }">
|
||||
<GrantComp
|
||||
:resourceType="resourceType"
|
||||
:app_id="app_id"
|
||||
:cmdbGrantType="cmdbGrantType"
|
||||
:resourceTypeName="resourceTypeName"
|
||||
:typeRelationIds="typeRelationIds"
|
||||
:CITypeId="CITypeId"
|
||||
:isModal="true"
|
||||
/>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GrantComp from './grantComp.vue'
|
||||
export default {
|
||||
name: 'CMDBGrant',
|
||||
components: { GrantComp },
|
||||
props: {
|
||||
resourceType: {
|
||||
type: String,
|
||||
default: 'CIType',
|
||||
},
|
||||
app_id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
resourceTypeName: '',
|
||||
typeRelationIds: [],
|
||||
cmdbGrantType: '',
|
||||
CITypeId: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open({ name, typeRelationIds = [], cmdbGrantType, CITypeId }) {
|
||||
this.visible = true
|
||||
this.resourceTypeName = name
|
||||
this.typeRelationIds = typeRelationIds
|
||||
this.cmdbGrantType = cmdbGrantType
|
||||
this.CITypeId = CITypeId
|
||||
},
|
||||
handleOk() {
|
||||
this.handleCancel()
|
||||
},
|
||||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div :class="{ 'read-checkbox': true, 'ant-checkbox-wrapper': isHalfChecked }" @click="openReadGrantModal">
|
||||
<a-tooltip
|
||||
v-if="value && isHalfChecked"
|
||||
:title="valueKey === 'read_ci' ? filerPerimissions[this.rid].name || '' : ''"
|
||||
>
|
||||
<div v-if="value && isHalfChecked" :class="{ 'read-checkbox-half-checked': true, 'ant-checkbox': true }"></div>
|
||||
</a-tooltip>
|
||||
<a-checkbox v-else :checked="value" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ReadCheckbox',
|
||||
inject: {
|
||||
provide_filerPerimissions: {
|
||||
from: 'filerPerimissions',
|
||||
},
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'read_attr',
|
||||
},
|
||||
rid: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
filerPerimissions() {
|
||||
return this.provide_filerPerimissions()
|
||||
},
|
||||
filterKey() {
|
||||
if (this.valueKey === 'read_attr') {
|
||||
return 'attr_filter'
|
||||
}
|
||||
return 'ci_filter'
|
||||
},
|
||||
isHalfChecked() {
|
||||
if (this.filerPerimissions[this.rid]) {
|
||||
const _tempValue = this.filerPerimissions[this.rid][this.filterKey]
|
||||
return !!(_tempValue && _tempValue.length)
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openReadGrantModal() {
|
||||
this.$emit('openReadGrantModal')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.read-checkbox {
|
||||
.read-checkbox-half-checked {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
// background-color: #custom_colors[color_1];
|
||||
border-radius: 2px;
|
||||
border: 14px solid transparent;
|
||||
border-left-color: #custom_colors[color_1];
|
||||
transform: rotate(225deg);
|
||||
top: -16px;
|
||||
left: -17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
205
cmdb-ui/src/modules/cmdb/components/cmdbGrant/readGrantModal.vue
Normal file
205
cmdb-ui/src/modules/cmdb/components/cmdbGrant/readGrantModal.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<a-modal :width="680" :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel">
|
||||
<CustomRadio
|
||||
:radioList="[
|
||||
{ value: 1, label: '全部' },
|
||||
{ value: 2, label: '自定义', layout: 'vertical' },
|
||||
{ value: 3, label: '无' },
|
||||
]"
|
||||
v-model="radioValue"
|
||||
>
|
||||
<template slot="extra_2" v-if="radioValue === 2">
|
||||
<treeselect
|
||||
v-if="colType === 'read_attr'"
|
||||
v-model="selectedAttr"
|
||||
:multiple="true"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="attrGroup"
|
||||
placeholder="请选择属性字段"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:limit="10"
|
||||
:limitText="(count) => `+ ${count}`"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name || -1,
|
||||
label: node.alias || node.name || '其他',
|
||||
title: node.alias || node.name || '其他',
|
||||
children: node.attributes,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
zIndex="1050"
|
||||
>
|
||||
</treeselect>
|
||||
<a-form-model
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
v-if="colType === 'read_ci'"
|
||||
:labelCol="{ span: 2 }"
|
||||
:wrapperCol="{ span: 10 }"
|
||||
ref="form"
|
||||
>
|
||||
<a-form-model-item label="名称" prop="name">
|
||||
<a-input v-model="form.name" />
|
||||
</a-form-model-item>
|
||||
<FilterComp
|
||||
ref="filterComp"
|
||||
:isDropdown="false"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList"
|
||||
@setExpFromFilter="setExpFromFilter"
|
||||
:expression="expression"
|
||||
/>
|
||||
</a-form-model>
|
||||
</template>
|
||||
</CustomRadio>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { grantCiType, revokeCiType } from '../../api/CIType'
|
||||
import { getCITypeAttributesByTypeIds } from '../../api/CITypeAttr'
|
||||
import FilterComp from '@/components/CMDBFilterComp'
|
||||
|
||||
export default {
|
||||
name: 'ReadGrantModal',
|
||||
components: { FilterComp },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
inject: {
|
||||
provide_attrGroup: {
|
||||
from: 'attrGroup',
|
||||
},
|
||||
provide_filerPerimissions: {
|
||||
from: 'filerPerimissions',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
colType: '',
|
||||
row: {},
|
||||
radioValue: 1,
|
||||
radioStyle: {
|
||||
display: 'block',
|
||||
height: '30px',
|
||||
lineHeight: '30px',
|
||||
},
|
||||
selectedAttr: [],
|
||||
ruleList: [],
|
||||
canSearchPreferenceAttrList: [],
|
||||
expression: '',
|
||||
form: {
|
||||
name: '',
|
||||
},
|
||||
rules: {
|
||||
name: [{ required: true, message: '请输入自定义筛选条件名' }],
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
if (this.colType === 'read_attr') {
|
||||
return '字段权限'
|
||||
}
|
||||
return '实例权限'
|
||||
},
|
||||
attrGroup() {
|
||||
return this.provide_attrGroup()
|
||||
},
|
||||
filerPerimissions() {
|
||||
return this.provide_filerPerimissions()
|
||||
},
|
||||
filterKey() {
|
||||
if (this.colType === 'read_attr') {
|
||||
return 'attr_filter'
|
||||
}
|
||||
return 'ci_filter'
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async open(colType, row) {
|
||||
this.visible = true
|
||||
this.colType = colType
|
||||
this.row = row
|
||||
if (this.colType === 'read_ci') {
|
||||
await getCITypeAttributesByTypeIds({ type_ids: this.CITypeId }).then((res) => {
|
||||
this.canSearchPreferenceAttrList = res.attributes.filter((item) => item.value_type !== '6')
|
||||
})
|
||||
}
|
||||
if (this.filerPerimissions[row.rid]) {
|
||||
const _tempValue = this.filerPerimissions[row.rid][this.filterKey]
|
||||
if (_tempValue && _tempValue.length) {
|
||||
this.radioValue = 2
|
||||
if (this.colType === 'read_attr') {
|
||||
this.selectedAttr = _tempValue
|
||||
} else {
|
||||
this.expression = `q=${_tempValue}`
|
||||
this.form = {
|
||||
name: this.filerPerimissions[row.rid].name || '',
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.filterComp.visibleChange(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.form = {
|
||||
name: '',
|
||||
}
|
||||
}
|
||||
},
|
||||
async handleOk() {
|
||||
if (this.radioValue === 1) {
|
||||
await grantCiType(this.CITypeId, this.row.rid, {
|
||||
perms: ['read'],
|
||||
attr_filter: this.colType === 'read_attr' ? [] : undefined,
|
||||
ci_filter: this.colType === 'read_ci' ? '' : undefined,
|
||||
})
|
||||
} else if (this.radioValue === 2) {
|
||||
if (this.colType === 'read_ci') {
|
||||
this.$refs.filterComp.handleSubmit()
|
||||
}
|
||||
await grantCiType(this.CITypeId, this.row.rid, {
|
||||
perms: ['read'],
|
||||
attr_filter: this.colType === 'read_attr' ? this.selectedAttr : undefined,
|
||||
ci_filter: this.colType === 'read_ci' ? this.expression.slice(2) : undefined,
|
||||
name: this.colType === 'read_ci' ? this.form.name : undefined,
|
||||
})
|
||||
} else {
|
||||
const _tempValue = this.filerPerimissions?.[this.row.rid]?.[this.filterKey]
|
||||
await revokeCiType(this.CITypeId, this.row.rid, {
|
||||
perms: ['read'],
|
||||
attr_filter: this.colType === 'read_attr' ? _tempValue : undefined,
|
||||
ci_filter: this.colType === 'read_ci' ? _tempValue : undefined,
|
||||
})
|
||||
}
|
||||
this.$emit('updateTableDataRead', this.row, this.radioValue === 1 || this.radioValue === 2)
|
||||
this.handleCancel()
|
||||
},
|
||||
handleCancel() {
|
||||
this.radioValue = 1
|
||||
this.selectedAttr = []
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.resetFields()
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
setExpFromFilter(filterExp) {
|
||||
let expression = ''
|
||||
if (filterExp) {
|
||||
expression = `q=${filterExp}`
|
||||
}
|
||||
this.expression = expression
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="ci-relation-grant">
|
||||
<vxe-table size="mini" stripe class="ops-stripe-table" :data="tableData" :max-height="`${tableHeight}px`">
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">授权用户/部门</span>
|
||||
<!-- <span class="grant-button" @click="grantRole">授权角色</span> -->
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantRelationView, revokeRelationView } from '../../api/preference.js'
|
||||
export default {
|
||||
name: 'RelationViewGrant',
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
resourceTypeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'relation_view',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
permMap,
|
||||
columns: ['read', 'grant'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
handleChange(e, col, row) {
|
||||
if (e.target.checked) {
|
||||
grantRelationView(row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
revokeRelationView(row.rid, { perms: [col], name: this.resourceTypeName }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-relation-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="ci-relation-grant">
|
||||
<vxe-table size="mini" stripe class="ops-stripe-table" :data="tableData" :max-height="`${tableHeight}px`">
|
||||
<vxe-column field="name"></vxe-column>
|
||||
<vxe-column v-for="col in columns" :key="col" :field="col" :title="permMap[col]">
|
||||
<template #default="{row}">
|
||||
<a-checkbox @change="(e) => handleChange(e, col, row)" v-model="row[col]"></a-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<a-space>
|
||||
<span class="grant-button" @click="grantDepart">授权用户/部门</span>
|
||||
<!-- <span class="grant-button" @click="grantRole">授权角色</span> -->
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { permMap } from './constants.js'
|
||||
import { grantTypeRelation, revokeTypeRelation } from '../../api/CITypeRelation.js'
|
||||
export default {
|
||||
name: 'TypeRelationGrant',
|
||||
inject: ['loading', 'isModal'],
|
||||
props: {
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
grantType: {
|
||||
type: String,
|
||||
default: 'type_relation',
|
||||
},
|
||||
typeRelationIds: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
permMap,
|
||||
columns: ['create', 'grant', 'delete'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
tableHeight() {
|
||||
if (this.isModal) {
|
||||
return (this.windowHeight - 104) / 2
|
||||
}
|
||||
return (this.windowHeight - 104) / 2 - 116
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
grantDepart() {
|
||||
this.$emit('grantDepart', this.grantType)
|
||||
},
|
||||
grantRole() {
|
||||
this.$emit('grantRole', this.grantType)
|
||||
},
|
||||
handleChange(e, col, row) {
|
||||
const first = this.typeRelationIds[0]
|
||||
const second = this.typeRelationIds[1]
|
||||
if (e.target.checked) {
|
||||
grantTypeRelation(first, second, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
} else {
|
||||
revokeTypeRelation(first, second, row.rid, { perms: [col] }).catch(() => {
|
||||
this.$emit('getTableData')
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-relation-grant {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
565
cmdb-ui/src/modules/cmdb/components/colorPicker/index.vue
Normal file
565
cmdb-ui/src/modules/cmdb/components/colorPicker/index.vue
Normal file
@@ -0,0 +1,565 @@
|
||||
<template>
|
||||
<div class="color-picker">
|
||||
<!-- 选中的颜色 -->
|
||||
<!-- <div class="color-button">
|
||||
<div class="back-ground">
|
||||
<div class="contain" :style="{ backgroundColor: realShowColor }" @click="isShowDropDown">
|
||||
<a-icon type="down" />
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- 颜色选择器 -->
|
||||
<div class="color-dropdown">
|
||||
<div class="color-dropdown-picker">
|
||||
<!-- 颜色面板 -->
|
||||
<div
|
||||
ref="colorPannel"
|
||||
class="color-pannel-box"
|
||||
@click="pannelMosueClick($event)"
|
||||
:style="{ backgroundColor: colorPannel.backgroundColor }"
|
||||
>
|
||||
<div
|
||||
:style="{ top: colorPannel.top + 'px', left: colorPannel.left + 'px' }"
|
||||
class="color-select-circle"
|
||||
@mousedown="pannelMosueHandler($event)"
|
||||
></div>
|
||||
</div>
|
||||
<!-- 色相柱 -->
|
||||
<div ref="colorBar" class="color-slider-box">
|
||||
<div class="color-slider"></div>
|
||||
<div class="color-thumb" :style="{ top: colorBar.top + 'px' }" @mousedown="thumbMouseHandler($event)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 透明条 -->
|
||||
<!-- <div v-if="showAlpha" ref="alphaBar" class="color-alpha">
|
||||
<div
|
||||
class="color-alpha-bar"
|
||||
:style="{
|
||||
background: `linear-gradient(to right, ${alphaColorBar.barColor}, ${rgbToRgba(alphaColorBar.barColor, 0)})`,
|
||||
}"
|
||||
></div>
|
||||
<div
|
||||
class="color-alpha-thumb"
|
||||
:style="{ left: alphaColorBar.thumbLeft + 'px' }"
|
||||
@mousedown="alphaBarMouseHandler($event)"
|
||||
></div>
|
||||
</div> -->
|
||||
<div class="color-input">
|
||||
<a-input
|
||||
size="small"
|
||||
v-model="realShowColor"
|
||||
class="color-input-box"
|
||||
type="text"
|
||||
style="width:130px"
|
||||
@pressEnter="changeInputColor"
|
||||
/>
|
||||
<!-- <button @click="submitColor">确定</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ColorPicker',
|
||||
props: {
|
||||
// 是否开启透明度
|
||||
// showAlpha: {
|
||||
// type: Boolean,
|
||||
// default() {
|
||||
// return true
|
||||
// },
|
||||
// },
|
||||
// 初始化颜色,使用该组件时传入的默认颜色:支持hex、rgb格式
|
||||
initColor: {
|
||||
type: String,
|
||||
default() {
|
||||
return '#f00'
|
||||
},
|
||||
},
|
||||
// input中展示的颜色格式: hex、rgb
|
||||
colorFormat: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'hex'
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
colorConfig: {
|
||||
h: 360,
|
||||
s: 100,
|
||||
v: 100,
|
||||
alpha: 1,
|
||||
value: '',
|
||||
// 底色
|
||||
basicColor: '',
|
||||
},
|
||||
alphaColorBar: {
|
||||
barColor: 'rgb(255, 0, 0)',
|
||||
thumbLeft: 0,
|
||||
width: 0,
|
||||
},
|
||||
colorBar: {
|
||||
top: 0,
|
||||
height: 0,
|
||||
},
|
||||
colorPannel: {
|
||||
top: 0,
|
||||
left: 300,
|
||||
backgroundColor: '#f00',
|
||||
height: 0,
|
||||
width: 0,
|
||||
},
|
||||
// 默认红色
|
||||
realShowColor: '#f00',
|
||||
isShow: true,
|
||||
isApply: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initShowColor(this.initColor)
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
initShowColor(color) {
|
||||
// 初始化hsv颜色、格式化的颜色值
|
||||
let hsvObj, initRgb
|
||||
if (color.indexOf('#') !== -1) {
|
||||
initRgb = this.hexToRGB(color)
|
||||
hsvObj = this.rgbToHSV(initRgb)
|
||||
} else if (color.indexOf('rgb') !== -1) {
|
||||
hsvObj = this.rgbToHSV(color)
|
||||
} else {
|
||||
throw new Error('初始化颜色格式错误,使用#fff或rgb格式')
|
||||
// this.$message.error('颜色格式错误,使用16进制格式')
|
||||
}
|
||||
if (hsvObj) {
|
||||
this.colorConfig.h = hsvObj.h
|
||||
this.colorConfig.s = hsvObj.s
|
||||
this.colorConfig.v = hsvObj.v
|
||||
}
|
||||
// 获取容器高宽
|
||||
this.colorBar.height = this.$refs.colorBar.getBoundingClientRect().height
|
||||
this.colorPannel.height = this.$refs.colorPannel.getBoundingClientRect().height
|
||||
this.colorPannel.width = this.$refs.colorPannel.getBoundingClientRect().width
|
||||
if (this.showAlpha) {
|
||||
this.alphaColorBar.width = this.$refs.alphaBar.getBoundingClientRect().width
|
||||
// 根据alpha获取滑块位置
|
||||
this.alphaToPosition(this.colorConfig.alpha, this.alphaColorBar.width)
|
||||
this.alphaColorBar.barColor = initRgb || color
|
||||
}
|
||||
// 根据hsv获取位置
|
||||
this.colorPannel.backgroundColor = this.hueToRGB(this.colorConfig.h)
|
||||
this.hsvToPosition(this.colorConfig.s, this.colorConfig.v, this.colorPannel.width, this.colorPannel.height)
|
||||
this.hueToPosition(this.colorConfig.h, this.colorBar.height)
|
||||
|
||||
// 根据colorFormat和showAlpha格式化颜色
|
||||
this.colorForamtTransform()
|
||||
|
||||
this.realShowColor = this.colorConfig.value || this.initColor
|
||||
},
|
||||
// isShowDropDown() {
|
||||
// this.isShow = !this.isShow
|
||||
// },
|
||||
submitColor() {
|
||||
// 如果颜色为rgba形式将转换为rgb。
|
||||
let initColor
|
||||
if (this.realShowColor.indexOf('rgba') !== -1) {
|
||||
initColor = this.realShowColor.replace(/,\d{1,3}(?=\))/, '')
|
||||
// 获取输入的alpha
|
||||
this.colorConfig.alpha = parseFloat(this.realShowColor.split(',')[3].replace(')', ''))
|
||||
this.colorConfig.alpha = Math.max(0, this.colorConfig.alpha)
|
||||
this.colorConfig.alpha = Math.min(this.colorConfig.alpha, 1)
|
||||
} else {
|
||||
initColor = this.realShowColor
|
||||
}
|
||||
this.initShowColor(initColor)
|
||||
this.isShow = false
|
||||
},
|
||||
// 色相柱的拖拽事件
|
||||
thumbMouseHandler(e) {
|
||||
if (e.type === 'mousedown') {
|
||||
document.body.addEventListener('mousemove', this.thumbMouseHandler)
|
||||
document.body.addEventListener('mouseup', this.thumbMouseHandler)
|
||||
} else if (e.type === 'mousemove') {
|
||||
const elemInfo = this.$refs.colorBar.getBoundingClientRect()
|
||||
|
||||
this.colorBar.top = e.clientY - elemInfo.top
|
||||
this.colorBar.top = Math.max(0, this.colorBar.top)
|
||||
this.colorBar.top = Math.min(this.colorBar.top, elemInfo.height)
|
||||
this.colorConfig.h = ((parseInt(this.colorBar.top) / elemInfo.height) * 360 * 100) / 100
|
||||
// 色相[0,360)
|
||||
if (this.colorConfig.h === 360) {
|
||||
this.colorConfig.h = 0
|
||||
}
|
||||
|
||||
// 获取颜色面板背景色
|
||||
this.colorPannel.backgroundColor = this.hueToRGB(this.colorConfig.h)
|
||||
this.colorForamtTransform()
|
||||
this.alphaColorBar.barColor = this.colorConfig.basicColor
|
||||
this.realShowColor = this.colorConfig.value
|
||||
} else if (e.type === 'mouseup') {
|
||||
// 当释放鼠标键时,删除鼠标移动事件和删除鼠标释放事件
|
||||
document.body.removeEventListener('mousemove', this.thumbMouseHandler)
|
||||
document.body.removeEventListener('mouseup', this.thumbMouseHandler)
|
||||
}
|
||||
this.$emit('changColorPicker', this.realShowColor)
|
||||
},
|
||||
// 颜色面板点击事件
|
||||
pannelMosueClick(e) {
|
||||
const elemInfo = this.$refs.colorPannel.getBoundingClientRect()
|
||||
// 在颜色面板容器范围内移动
|
||||
this.colorPannel.top = e.clientY - elemInfo.top
|
||||
this.colorPannel.left = e.clientX - elemInfo.left
|
||||
// 使取色圈移动更加顺滑且不超过取色面板容器范围
|
||||
this.colorPannel.left = Math.max(0, this.colorPannel.left)
|
||||
this.colorPannel.left = Math.min(this.colorPannel.left, elemInfo.width)
|
||||
this.colorPannel.top = Math.max(0, this.colorPannel.top)
|
||||
this.colorPannel.top = Math.min(this.colorPannel.top, elemInfo.height)
|
||||
|
||||
// 计算饱和度(0 -> 100)和亮度 (0 -> 100)
|
||||
this.colorConfig.s = (parseInt(this.colorPannel.left) / elemInfo.width) * 100
|
||||
this.colorConfig.v = (1 - parseInt(this.colorPannel.top) / elemInfo.height) * 100
|
||||
this.colorForamtTransform()
|
||||
// 将hsv转换为rgb
|
||||
this.alphaColorBar.barColor = this.colorConfig.basicColor
|
||||
this.realShowColor = this.colorConfig.value
|
||||
this.$emit('changColorPicker', this.realShowColor)
|
||||
},
|
||||
// 颜色面板的拖拽事件
|
||||
pannelMosueHandler(e) {
|
||||
if (e.type === 'mousedown') {
|
||||
document.body.addEventListener('mousemove', this.pannelMosueHandler)
|
||||
document.body.addEventListener('mouseup', this.pannelMosueHandler)
|
||||
} else if (e.type === 'mousemove') {
|
||||
const elemInfo = this.$refs.colorPannel.getBoundingClientRect()
|
||||
// 在颜色面板容器范围内移动
|
||||
this.colorPannel.top = e.clientY - elemInfo.top
|
||||
this.colorPannel.left = e.clientX - elemInfo.left
|
||||
// 使取色圈移动更加顺滑且不超过取色面板容器范围
|
||||
this.colorPannel.left = Math.max(0, this.colorPannel.left)
|
||||
this.colorPannel.left = Math.min(this.colorPannel.left, elemInfo.width)
|
||||
this.colorPannel.top = Math.max(0, this.colorPannel.top)
|
||||
this.colorPannel.top = Math.min(this.colorPannel.top, elemInfo.height)
|
||||
|
||||
// 计算饱和度(0 -> 100)和亮度 (0 -> 100)
|
||||
this.colorConfig.s = (parseInt(this.colorPannel.left) / elemInfo.width) * 100
|
||||
this.colorConfig.v = (1 - parseInt(this.colorPannel.top) / elemInfo.height) * 100
|
||||
this.colorForamtTransform()
|
||||
// 将hsv转换为rgb
|
||||
this.alphaColorBar.barColor = this.colorConfig.basicColor
|
||||
this.realShowColor = this.colorConfig.value
|
||||
} else if (e.type === 'mouseup') {
|
||||
// 当释放鼠标键时,删除鼠标移动事件和删除鼠标释放事件
|
||||
document.body.removeEventListener('mousemove', this.pannelMosueHandler)
|
||||
document.body.removeEventListener('mouseup', this.pannelMosueHandler)
|
||||
}
|
||||
this.$emit('changColorPicker', this.realShowColor)
|
||||
},
|
||||
// 透明柱的拖拽事件
|
||||
// alphaBarMouseHandler(e) {
|
||||
// if (e.type === 'mousedown') {
|
||||
// document.body.addEventListener('mousemove', this.alphaBarMouseHandler)
|
||||
// document.body.addEventListener('mouseup', this.alphaBarMouseHandler)
|
||||
// } else if (e.type === 'mousemove') {
|
||||
// const elemInfo = this.$refs.alphaBar.getBoundingClientRect()
|
||||
// this.alphaColorBar.thumbLeft = e.clientX - elemInfo.left
|
||||
// this.alphaColorBar.thumbLeft = Math.max(0, this.alphaColorBar.thumbLeft)
|
||||
// this.alphaColorBar.thumbLeft = Math.min(this.alphaColorBar.thumbLeft, elemInfo.width)
|
||||
// // 获取颜色透明度0 -> 1
|
||||
// this.colorConfig.alpha = (1 - this.alphaColorBar.thumbLeft / elemInfo.width).toFixed(2)
|
||||
// this.colorForamtTransform()
|
||||
// this.realShowColor = this.colorConfig.value
|
||||
// } else if (e.type === 'mouseup') {
|
||||
// // 当释放鼠标键时,删除鼠标移动事件和删除鼠标释放事件
|
||||
// document.body.removeEventListener('mousemove', this.alphaBarMouseHandler)
|
||||
// document.body.removeEventListener('mouseup', this.alphaBarMouseHandler)
|
||||
// }
|
||||
// },
|
||||
// 颜色格式
|
||||
colorForamtTransform() {
|
||||
if (this.showAlpha) {
|
||||
// 如果开启透明度,那么颜色一定为rgba格式
|
||||
this.colorConfig.basicColor = this.hsvToRGB(this.colorConfig.h, this.colorConfig.s, this.colorConfig.v)
|
||||
this.colorConfig.value = this.rgbToRgba(this.colorConfig.basicColor, this.colorConfig.alpha)
|
||||
} else {
|
||||
if (this.colorFormat === 'hex') {
|
||||
this.colorConfig.basicColor = this.hsvToRGB(this.colorConfig.h, this.colorConfig.s, this.colorConfig.v)
|
||||
this.colorConfig.value = this.rgbToHex(this.colorConfig.basicColor)
|
||||
}
|
||||
if (this.colorFormat === 'rgb') {
|
||||
this.colorConfig.basicColor = this.hsvToRGB(this.colorConfig.h, this.colorConfig.s, this.colorConfig.v)
|
||||
this.colorConfig.value = this.colorConfig.basicColor
|
||||
}
|
||||
}
|
||||
},
|
||||
// 从hue to rgb
|
||||
hueToRGB(h) {
|
||||
if (h === 360) {
|
||||
h = 0
|
||||
}
|
||||
const doHandle = (num) => {
|
||||
if (num > 255) {
|
||||
return 255
|
||||
} else if (num < 0) {
|
||||
return 0
|
||||
} else {
|
||||
return Math.round(num)
|
||||
}
|
||||
}
|
||||
|
||||
const hueRGB = (h / 60) * 255
|
||||
const r = doHandle(Math.abs(hueRGB - 765) - 255)
|
||||
const g = doHandle(510 - Math.abs(hueRGB - 510))
|
||||
const b = doHandle(510 - Math.abs(hueRGB - 1020))
|
||||
return 'rgb(' + r + ',' + g + ',' + b + ')'
|
||||
},
|
||||
// 从HSV(色相、饱和度、亮度) to rgb
|
||||
hsvToRGB(h, s, v) {
|
||||
s = s / 100
|
||||
v = v / 100
|
||||
let r = 0
|
||||
let g = 0
|
||||
let b = 0
|
||||
const i = Math.floor(h / 60)
|
||||
const f = h / 60 - i
|
||||
const p = v * (1 - s)
|
||||
const q = v * (1 - f * s)
|
||||
const t = v * (1 - (1 - f) * s)
|
||||
switch (i) {
|
||||
case 0:
|
||||
r = v
|
||||
g = t
|
||||
b = p
|
||||
break
|
||||
case 1:
|
||||
r = q
|
||||
g = v
|
||||
b = p
|
||||
break
|
||||
case 2:
|
||||
r = p
|
||||
g = v
|
||||
b = t
|
||||
break
|
||||
case 3:
|
||||
r = p
|
||||
g = q
|
||||
b = v
|
||||
break
|
||||
case 4:
|
||||
r = t
|
||||
g = p
|
||||
b = v
|
||||
break
|
||||
case 5:
|
||||
r = v
|
||||
g = p
|
||||
b = q
|
||||
break
|
||||
}
|
||||
|
||||
return `rgb(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)})`
|
||||
},
|
||||
// rgb to hsv
|
||||
rgbToHSV(rgbStr) {
|
||||
let { r, g, b } = this.getRGB(rgbStr)
|
||||
r = parseFloat(parseFloat(r / 255).toFixed(4))
|
||||
g = parseFloat(parseFloat(g / 255).toFixed(4))
|
||||
b = parseFloat(parseFloat(b / 255).toFixed(4))
|
||||
const max = Math.max(r, g, b)
|
||||
const min = Math.min(r, g, b)
|
||||
let h
|
||||
const v = max
|
||||
|
||||
const d = max - min
|
||||
const s = max === 0 ? 0 : d / max
|
||||
|
||||
if (max === min) {
|
||||
h = 0 // achromatic
|
||||
} else {
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0)
|
||||
break
|
||||
case g:
|
||||
h = (b - r) / d + 2
|
||||
break
|
||||
case b:
|
||||
h = (r - g) / d + 4
|
||||
break
|
||||
}
|
||||
h /= 6
|
||||
}
|
||||
return { h: h * 360, s: s * 100, v: v * 100 }
|
||||
},
|
||||
// 根据hsv获取取色圈位置
|
||||
hsvToPosition(s, v, width, height) {
|
||||
this.colorPannel.top = height - (v * height) / 100
|
||||
this.colorPannel.left = (s * width) / 100
|
||||
},
|
||||
hueToPosition(h, height) {
|
||||
this.colorBar.top = (h * height) / 360
|
||||
},
|
||||
alphaToPosition(alpha, width) {
|
||||
this.alphaColorBar.thumbLeft = (1 - alpha) * width
|
||||
},
|
||||
// 拆解rgb为r,g,b
|
||||
getRGB(rgbStr) {
|
||||
const matchArr = rgbStr.match(/\(.+?\)/g)[0].match(/\w+/g)
|
||||
const r = parseInt(matchArr[0])
|
||||
const g = parseInt(matchArr[1])
|
||||
const b = parseInt(matchArr[2])
|
||||
return { r, g, b }
|
||||
},
|
||||
// rgb 转 16进制
|
||||
rgbToHex(rgbStr) {
|
||||
// 拆解rgb为[255,255,255]形式。
|
||||
const { r, g, b } = this.getRGB(rgbStr)
|
||||
return `#${this.zeroFill(r.toString(16))}${this.zeroFill(g.toString(16))}${this.zeroFill(b.toString(16))}`
|
||||
},
|
||||
rgbToRgba(rgbStr, alpha) {
|
||||
return rgbStr.replace(')', `,${alpha})`)
|
||||
},
|
||||
hexToRGB(hexStr) {
|
||||
if (hexStr.length === 4) {
|
||||
const hexArr = hexStr.match(/\w{1}/g)
|
||||
return `rgb(${parseInt(hexArr[0] + hexArr[0], 16)},${parseInt(hexArr[1] + hexArr[1], 16)},${parseInt(
|
||||
hexArr[2] + hexArr[2],
|
||||
16
|
||||
)})`
|
||||
}
|
||||
if (hexStr.length === 7) {
|
||||
const hexArr = hexStr.match(/\w{2}/g)
|
||||
return `rgb(${parseInt(hexArr[0], 16)},${parseInt(hexArr[1], 16)},${parseInt(hexArr[2], 16)})`
|
||||
}
|
||||
},
|
||||
// 补零
|
||||
zeroFill(val) {
|
||||
return val.length > 1 ? val : '0' + val
|
||||
},
|
||||
changeInputColor(e) {
|
||||
this.initShowColor(e.target.value)
|
||||
this.$emit('changColorPicker', e.target.value)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.color-picker {
|
||||
width: 150px;
|
||||
margin: auto;
|
||||
height: 100px;
|
||||
margin-top: 0px;
|
||||
position: relative;
|
||||
|
||||
// .color-button {
|
||||
// height: 36px;
|
||||
// width: 36px;
|
||||
// border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
// border-radius: 4px;
|
||||
// .back-ground {
|
||||
// height: 26px;
|
||||
// width: 26px;
|
||||
// margin: 4px;
|
||||
// background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
|
||||
// .contain {
|
||||
// border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
// border-radius: 2px;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
.color-dropdown {
|
||||
margin: auto;
|
||||
width: 140px;
|
||||
height: 92px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
// background-color: rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
.color-dropdown-picker {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-around;
|
||||
.color-pannel-box {
|
||||
position: relative;
|
||||
width: 110px;
|
||||
height: 64px;
|
||||
background: linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent);
|
||||
.color-select-circle {
|
||||
position: absolute;
|
||||
transform: translate(-4px, -4px);
|
||||
border: 1px solid #fff;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
.color-slider-box {
|
||||
cursor: pointer;
|
||||
width: 10px;
|
||||
|
||||
position: relative;
|
||||
.color-slider {
|
||||
background: linear-gradient(180deg, #f00, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00);
|
||||
width: 10px;
|
||||
height: 64px;
|
||||
}
|
||||
.color-thumb {
|
||||
width: 10px;
|
||||
height: 7px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
transform: translate(0, -3px);
|
||||
border-radius: 2px;
|
||||
background-color: rgb(10, 10, 10);
|
||||
border: 3px solid #fff;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.color-alpha {
|
||||
position: relative;
|
||||
height: 12px;
|
||||
box-shadow: 2px 2px 2px 2px 2px rgba(0, 0, 0, 0.1);
|
||||
margin-left: 10px;
|
||||
width: 300px;
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
|
||||
.color-alpha-bar {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.color-alpha-thumb {
|
||||
width: 7px;
|
||||
height: 18px;
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
transform: translate(-3px, 0);
|
||||
border-radius: 2px;
|
||||
background-color: rgb(10, 10, 10);
|
||||
border: 3px solid #fff;
|
||||
}
|
||||
}
|
||||
.color-input {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
padding: 3px 6px;
|
||||
justify-content: space-between;
|
||||
button {
|
||||
color: #000;
|
||||
}
|
||||
.color-input-box {
|
||||
color: #000;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user