添加项目权限

This commit is contained in:
limengnan 2026-04-02 17:33:02 +08:00
parent da87415599
commit eaf1910591
6 changed files with 747 additions and 18 deletions

View File

@ -1755,6 +1755,14 @@ const boundaryClick = () => {
}
</style>
<style>
.antvx6-content #graph-container {
flex: 1;
position: relative;
background-color: #fafafa;
overflow: hidden;
width: 100% !important;
height: calc(100%) !important;
}
.x6-widget-stencil-content {
top: 0px !important;
}

View File

@ -0,0 +1,696 @@
<!-- AntV G6 Graph Component -->
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRoute,useRouter } from 'vue-router';
import {
Graph,
Shape,
} from '@antv/x6'
// @ts-ignore
import { projectsById} from "@/api/business/project";
const emit = defineEmits([ 'closeAntvx6']);
const props = defineProps({
projectId: {
required: false,
type: String,
default: ''
},
})
const projectInfo:any = ref({}) //
//
let graph: Graph
onMounted(() => {
// #region
graph = new Graph({
container: document.getElementById('graph-container') as HTMLElement,
grid: true,
mousewheel: {
enabled: false,
zoomAtMousePosition: true,
modifiers: 'ctrl',
minScale: 0.5,
maxScale: 3,
},
connecting: {
router: 'manhattan',
connector: {
name: 'rounded',
args: {
radius: 8,
},
},
anchor: 'center',
connectionPoint: 'anchor',
allowBlank: false,
snap: {
radius: 20,
},
createEdge() {
return new Shape.Edge({
attrs: {
line: {
stroke: '#A2B1C3',
strokeWidth: 2,
strokeDasharray: 0,
targetMarker: {
name: 'block',
width: 12,
height: 8,
},
sourceMarker: null,
},
},
zIndex: 0,
})
},
validateConnection({ targetMagnet }) {
return !!targetMagnet
},
},
highlighting: {
magnetAdsorbed: {
name: 'stroke',
args: {
attrs: {
fill: '#5F95FF',
stroke: '#5F95FF',
},
},
},
},
interacting: function (cellView) {
// 'rect'
return { nodeMovable: false }
//
return true
}
// interacting: {
// nodeMovable: false, //
// },
})
const ports = {
groups: {
top: {
position: 'top',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
right: {
position: 'right',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
left: {
position: 'left',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
},
items: [
{
group: 'top',
},
{
group: 'right',
},
{
group: 'bottom',
},
{
group: 'left',
},
],
}
Graph.registerNode(
'custom-image',
{
inherit: 'rect',
width: 130,
height: 100,
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'image',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
stroke: 'transparent',
fill: '#f8f8f8',
},
image: {
width: 62,
height:84,
refX: 35,
refY: 0,
},
label: {
refX: 45,
refY: 80,
textAnchor: 'start',
textVerticalAnchor: 'top',
fontSize: 14,
fill: '#000',
},
},
ports: { ...ports },
},
true,
)
Graph.registerNode(
'image-node',
{
inherit: 'rect',
width: 120,
height: 60,
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'image',
selector: 'img',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
stroke: 'transparent',
strokeWidth: 1,
fill: 'transparent',
rx: 6,
ry: 6,
},
img: {
'xlink:href': 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
width: 24,
height: 24,
x: 0,
y: 0,
},
label: {
text: 'Image Node',
fill: '#333',
fontSize: 12,
refX: 0.5,
refY: 0.5,
textAnchor: 'middle',
dy: 10,
},
'.': {
class: 'custom-image-node',
},
},
},
true,
)
Graph.registerNode(
'rect-text',
{
inherit: 'rect',
width: 120,
height: 60,
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'image',
selector: 'img',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
stroke: 'transparent',
strokeWidth: 1,
fill: 'transparent',
rx: 6,
ry: 6,
},
label: {
text: '',
fill: '#333',
fontSize: 12,
refX: 0.5,
refY: 0.5,
textAnchor: 'middle',
dy: 10,
},
'.': {
class: 'custom-image-node',
},
},
},
true,
)
projectsById({projectId:props.projectId}).then((res:any) => {
if(res.topology != null && res.topology != ''){
projectInfo.value = res
if (!graph || !projectInfo.value || !projectInfo.value.topology) return;
graph.clearCells();
const topology:any = JSON.parse(projectInfo.value.topology)
if(!topology.designData)return
graph.fromJSON(topology.designData);
}
})
})
const left = ref(0)
const top = ref(0)
const isMenuShow = ref(false)
function bigClick(){
graph.zoom(0.1)
}
function smallClick(){
graph.zoom(-0.1)
}
function closeAntvx6() {
emit('closeAntvx6')
}
</script>
<template>
<div class="antvx6-layout readonlyx6-layout">
<div class="antvx6-header">
<div class="header-left-box">
<div class="return-icon-box" @click="closeAntvx6" title="返回工作台">
<img src="@/assets/x6/return.png" alt="图标" style="cursor: pointer;">
</div>
<div class="project-name">{{ projectInfo.name }}</div>
</div>
<div class="header-content-box">
<div class="operation-icon-box" @click="bigClick">
<img src="@/assets/x6/magnify.png">
<div class="operation-icon-text">放大</div>
</div>
<div class="operation-icon-box" @click="smallClick">
<img src="@/assets/x6/reduce.png">
<div class="operation-icon-text">缩小</div>
</div>
</div>
<div class="header-left-box"></div>
</div>
<div id="graph-container" style="width: 100%; height: calc(100% - 60px);">
<div class="context-menu" v-if="isMenuShow" :style="{left: left +'px', top: top+'px'}">
</div>
</div>
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.antvx6-layout{
/* position: relative;
z-index: 10;
display: flex;
flex-direction: column; */
width:100%;
/* font-family: 'Segoe UI', sans-serif; */
}
.readonlyx6-layout{
height: calc(100vh);
}
.toolbar {
padding: 10px;
background-color: #f5f5f5;
border-bottom: 1px solid #e0e0e0;
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.style-group {
display: flex;
flex-direction: column;
gap: 8px;
margin-right: 20px;
}
.style-group h4 {
margin: 0 0 5px 0;
font-size: 14px;
color: #333;
font-weight: 600;
}
.toolbar button {
padding: 8px 16px;
border: 1px solid #d9d9d9;
border-radius: 4px;
background-color: #fff;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
white-space: nowrap;
}
.toolbar button:hover {
border-color: #1890ff;
color: #1890ff;
}
.toolbar button:active {
background-color: #f0f0f0;
}
/* Sidebar Styles */
.sidebar {
width: 200px;
background-color: #ffffff;
border-right: 1px solid #e0e0e0;
overflow-y: auto;
padding: 16px;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
}
.sidebar-section {
margin-bottom: 24px;
}
.sidebar-section h3 {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.device-item {
display: flex;
align-items: center;
padding: 10px;
margin-bottom: 8px;
background-color: #fafafa;
border: 1px solid #e0e0e0;
border-radius: 4px;
cursor: grab;
transition: all 0.2s ease;
}
.device-item:hover {
background-color: #f0f5ff;
border-color: #1890ff;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2);
}
.device-item:active {
cursor: grabbing;
}
.device-icon {
font-size: 24px;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
}
.device-name {
font-size: 13px;
color: #333;
font-weight: 500;
}
/* Graph Container Styles */
#graph-container {
flex: 1;
position: relative;
background-color: #fafafa;
overflow: hidden;
width: 100% !important;
height: calc(100% - 60px) !important;
}
/* Drag and drop visual feedback */
.graph-container.drag-over {
background-color: #e6f7ff;
border: 2px dashed #1890ff;
}
.context-menu {
position: absolute;
z-index: 100;
background-color: #fff;
border: 1px solid #dfe3e8;
border-radius: 4px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.15);
width: 240px;
height: 40px;
display: flex;
justify-content: space-around;
align-items: center;
}
</style>
<style>
.antvx6-header {
width: 100%;
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
background-color: #ffffff;
border-bottom: 1px solid #e0e0e0;
}
.header-left-box {
width: 400px;
height: 60px;
display: flex;
align-items: center;
}
.header-content-box {
display: flex;
align-items: center;
}
.return-icon-box {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-right: 10px;
}
.return-icon-box:hover {
background-color: #eeeeee;
border-radius: 4px;
}
.antvx6-header .project-name {
font-family: 'Arial Negreta', 'Arial Normal', 'Arial';
font-weight: 700;
font-style: normal;
font-size: 16px;
color: #333333;
margin-right: 10px;
margin-left: 10px;
}
.antvx6-header .operation-icon-box {
width: 46px;
height: 46px;
display: flex;
align-content: center;
justify-content: center;
flex-wrap: wrap;
cursor: pointer;
margin-right: 20px;
font-family: '微软雅黑';
font-weight: 400;
font-style: normal;
font-size: 12px;
color: #4B4B4B;
padding-top: 5px;
}
.antvx6-header .operation-icon-box:hover {
background-color: #eeeeee;
border-radius: 4px;
}
.operation-icon-text {
width: 100%;
text-align: center;
padding-top: 5px;
}
.line-style-box{
position: absolute;
right: 0;
height: 100%;
/* width: 100px; */
/* background-color: #d9d9d9; */
z-index: 10;
display: flex;
align-items: center;
}
.expansionandcontraction-box{
width:15px;
height: 64px;
background-color: #ffffff;
border: 1px solid #cfcfcf;
border-radius: 10px 0 0 10px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.style-content-box{
width: 300px;
height: calc(100vh - 60px);
border: 1px solid #cfcfcf;
border-top: none;
border-right: none;
background: #fff;
}
.style-content-box-title{
width: 100%;
height: 40px;
line-height: 40px;
text-align: left;
font-size: 14px;
font-family: 'Arial Negreta', 'Arial Normal', 'Arial';
font-weight: 700;
font-style: normal;
font-size: 14px;
color: #282828;
background-color: rgba(255, 255, 255, 1);
box-sizing: border-box;
border-bottom:1px solid rgba(238, 238, 238, 1);
padding-left: 15px;
}
</style>
<style>
.el-dialog {
padding: 0 !important;
border-radius: 10px !important;
border: 1px solid #363636 !important;
}
.el-dialog .el-dialog__header {
display: flex;
display: -webkit-flex;
justify-content: flex-start;
-webkit-justify-content: flex-start;
align-items: center;
-webkit-align-items: center;
padding: 10px 20px;
background-color: #f1f3f8 !important;
font-family: 'Arial Negreta', 'Arial Normal', 'Arial', sans-serif;
font-weight: 700;
font-style: normal;
font-size: 16px;
color: #1B1B1B;
text-align: left;
border-radius: 10px 10px 0 0;
height: 50px;
}
.el-dialog .el-dialog__close {
font-size: 22px;
color: rgb(80, 80, 80);
}
</style>
<style >
.custom-image-node {
cursor: pointer;
}
</style>

View File

@ -137,7 +137,6 @@ function addAttrText(item:any,index:any){
}
},
})
debugger
graph.addNode({
shape: 'image-node',
x: item.position.x + 135 ,

View File

@ -18,7 +18,7 @@ export const useUserStore = defineStore('user', () => {
const roles = ref<Array<string>>([]); // 用户角色编码集合 → 判断路由权限
const perms = ref<Array<string>>([]); // 用户权限编码集合 → 判断按钮权限
const badgeval = ref('')
const username = ref('')
// actions
// 登录
@ -50,6 +50,7 @@ export const useUserStore = defineStore('user', () => {
}
nickname.value = data.userInfo.nickname;
userId.value = data.userInfo.id;
username.value = data.userInfo.username;
avatar.value = data.userInfo.avatar;
roles.value = data.roles;
perms.value = data.permissions;
@ -81,6 +82,7 @@ export const useUserStore = defineStore('user', () => {
removeToken();
Token.value = '';
nickname.value = '';
username.value = '';
userId.value = '';
avatar.value = '';
roles.value = [];
@ -89,6 +91,7 @@ export const useUserStore = defineStore('user', () => {
return {
Token,
nickname,
username,
userId,
avatar,
roles,

View File

@ -11,6 +11,14 @@ import { searchProjectsLsit,addProjects,updateProjects,deleteProjects,deleteBatc
import Page from '@/components/Pagination/page.vue'
import ScenarioModel from '@/views/component/scenario/index.vue'
import Antvx6 from '@/components/antvx6/index.vue'
import Readonlyx6 from '@/components/antvx6/readonlyx6.vue'
import { useUserStore } from '@/store/modules/user';
const userStore = useUserStore();
const username = ref(userStore.username)
const apiUrl = import.meta.env.VITE_APP_BASE_API; //
const isShowAntvx6 = ref(false); //
const isScenario = ref(false) //
@ -217,7 +225,8 @@ function closeAntvx6() {
<a :href=" apiUrl + '/projects/exportAllExports'" target="_blank" rel="noopener noreferrer">
<el-button type="primary">导出</el-button> </a>
<el-button type="primary"
:disabled="multipleSelection.length <= 0" @click="delClick" style="margin-left: 10px">删除</el-button>
:disabled="multipleSelection.length <= 0" @click="delClick" style="margin-left: 10px"
v-if="username == 'admin'">删除</el-button>
</div>
</div>
<el-table v-loading="loading" :data="tableData" style="width: 100%; height: calc(100vh - 260px);margin-bottom: 10px;" border
@ -246,12 +255,12 @@ function closeAntvx6() {
@click="simulationClick(scope.row)"
style="cursor: pointer; ">
<a :href=" apiUrl + '/projects/' + scope.row.projectId + '/exportProject'" target="_blank" rel="noopener noreferrer">
<img src="@/assets/table/export.png" alt="" title="导出项目工程"
<img src="@/assets/table/export.png" alt="" title="导出项目工程"
style="cursor: pointer; ">
</a>
<img src="@/assets/table/del.png" alt="" title="删除"
@click="delAloneClick(scope.row)" style="cursor: pointer; ">
<img src="@/assets/table/del.png" alt="" title="删除"
@click="delAloneClick(scope.row)" style="cursor: pointer;" v-if="username == scope.row.creator">
</span>
</template>
</el-table-column>
@ -274,7 +283,8 @@ function closeAntvx6() {
<span class="dialog-footer"
style="display: flex;display: -webkit-flex; justify-content: flex-end;-webkit-justify-content: flex-end;">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="confirmClick(infoForm)"> </el-button>
<el-button type="primary" @click="confirmClick(infoForm)" v-if="username == 'admin' || 'PUBLIC' == info.visibility
|| title == '新增项目'"> </el-button>
</span>
</el-form>
</el-dialog>
@ -285,8 +295,10 @@ function closeAntvx6() {
<ScenarioModel v-if="isScenario" :projectInfo="info" ref="Scenario" />
</el-dialog>
<div v-if="isShowAntvx6" style="position: fixed;top: 0px;left: 0px; width: 100vw;height: 100vh;background-color: #fff;z-index: 100;">
<Antvx6 ref="antvx6" v-if="isShowAntvx6" :projectInfo="info" @closeAntvx6="closeAntvx6"/>
<div v-if="isShowAntvx6" class="antvx6-content" style="position: fixed;top: 0px;left: 0px; width: 100vw;height: 100vh;background-color: #fff;z-index: 100;">
<Antvx6 ref="antvx6" v-if="isShowAntvx6 && (username == 'admin' || 'PUBLIC' == info.visibility)" :projectInfo="info" @closeAntvx6="closeAntvx6"/>
<Readonlyx6 ref="readonlyx6" v-if="isShowAntvx6 && ('READONLY' == info.visibility)" :projectId="info.projectId" @closeAntvx6="closeAntvx6"/>
</div>
</div>
</template>

View File

@ -20,6 +20,11 @@ import TableModels from '@/components/antvx6/tableModel.vue';
import EchartsModels from '@/components/antvx6/echartsModel.vue';
import Viewx6 from '@/components/antvx6/viewx6.vue';
import { useUserStore } from '@/store/modules/user';
const userStore = useUserStore();
const username = ref(userStore.username)
const webUrl = import.meta.env.VITE_APP_BASE_HTTP; //
const algorithmTypeData: any = ref([]); //
const stepsActive = ref(0); //
@ -431,9 +436,10 @@ function initDeviceData(){
<el-button type="primary" style="margin-left: 10px" @click="gettableData">搜索</el-button>
</div>
<div>
<el-button type="primary" @click="addClick">
<el-button type="primary" @click="addClick" v-if="props.projectInfo.visibility == 'PUBLIC' || username == props.projectInfo.creator || props.projectInfo.creator == 'admin'">
新增</el-button>
<el-button :type="multipleSelection.length > 0 ? 'primary' : ''"
v-if="props.projectInfo.visibility == 'PUBLIC' || username == props.projectInfo.creator || props.projectInfo.creator == 'admin'"
:disabled="multipleSelection.length <= 0" @click="delClick">删除</el-button>
</div>
</div>
@ -463,26 +469,31 @@ function initDeviceData(){
<span
style="display: flex;display: -webkit-flex; justify-content: space-around;-webkit-justify-content: space-around; ">
<img src="@/assets/table/edit.png" alt="" title="修改" v-if="scope.row.status == 0"
<img src="@/assets/table/edit.png" alt="" title="修改"
v-if="scope.row.status == 0 && (props.projectInfo.visibility == 'PUBLIC' || username == props.projectInfo.creator || props.projectInfo.creator == 'admin')"
@click="editClick(scope.row,'修改')" style="cursor: pointer; ">
<img src="@/assets/table/view.png" alt="" title="查看" v-if="scope.row.status != 0"
@click="editClick(scope.row,'查看')" style="cursor: pointer; ">
<img src="@/assets/table/view.png" alt="" title="查看" v-else @click="editClick(scope.row,'查看')" style="cursor: pointer; ">
<img src="@/assets/table/shifa.png" alt="" title="始发事件" v-if="scope.row.status == 0"
<img src="@/assets/table/shifa.png" alt="" title="始发事件"
v-if="scope.row.status == 0 && (props.projectInfo.visibility == 'PUBLIC' || username == props.projectInfo.creator || props.projectInfo.creator == 'admin')"
@click="departureClick(scope.row,'初始条件设置')" style="cursor: pointer; ">
<img src="@/assets/table/see.png" alt="" title="查看始发事件" v-if="scope.row.status != 0"
<img src="@/assets/table/see.png" alt="" title="查看始发事件" v-else
@click="departureClick(scope.row,'查看初始条件')" style="cursor: pointer; ">
<img src="@/assets/table/moni.png" alt="" title="模拟计算" v-if="scope.row.status == 0|| scope.row.status == 1"
<img src="@/assets/table/moni.png" alt="" title="模拟计算"
v-if="(scope.row.status == 0|| scope.row.status == 1) && (props.projectInfo.visibility == 'PUBLIC' || username == props.projectInfo.creator || props.projectInfo.creator == 'admin')"
@click="confirmationAnalysis(scope.row)" style="cursor: pointer; ">
<img src="@/assets/table/moni_disabled.png" alt="" title="查看始发事件" v-if="scope.row.status != 0 &&scope.row.status != 1">
<img src="@/assets/table/moni_disabled.png" alt="" title="查看始发事件" v-else>
<img src="@/assets/table/result.png" alt="" title="计算结果" v-if="scope.row.status == 2"
@click="resultClick(scope.row)" style="cursor: pointer; ">
<img src="@/assets/table/result_disabled.png" alt="" title="计算结果" v-if="scope.row.status != 2">
<img src="@/assets/table/del.png" alt="" title="删除" v-if="scope.row.status == 0"
<img src="@/assets/table/del.png" alt="" title="删除"
v-if="scope.row.status == 0 && (props.projectInfo.visibility == 'PUBLIC' || username == props.projectInfo.creator || props.projectInfo.creator == 'admin')"
@click="delAloneClick(scope.row)" style="cursor: pointer; ">
<img v-else src="@/assets/table/del_disabled.png" alt="" title="删除"
>