融合服务 赋能场景 后台管理增删改查

This commit is contained in:
guoyue 2022-07-28 10:15:49 +08:00
parent a5e5c35da8
commit 480d887b6d
6 changed files with 350 additions and 70 deletions

View File

@ -31,12 +31,21 @@
<el-input type="textarea" :rows="3" class="input-box" v-model="dataForm.description" <el-input type="textarea" :rows="3" class="input-box" v-model="dataForm.description"
placeholder="请输入融合服务描述"></el-input> placeholder="请输入融合服务描述"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="支撑场景" prop="applicationArea"> <el-form-item label="应用领域" prop="applicationArea">
<el-select v-model="dataForm.applicationArea" placeholder="请选择支撑场景" filterable> <el-select v-model="dataForm.applicationArea" placeholder="请选择应用领域" filterable>
<el-option v-for="item in areaList" :key="item.value" :label="item.label" :value="item.value"> <el-option v-for="item in areaList" :key="item.value" :label="item.label" :value="item.value">
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="图片">
<el-upload ref="editUpload" class="upload-demo" :action="fileUploadUrl"
:on-success="eidtHandleAvatarSuccess" :before-upload="editBeforeAvatarUpload" :limit="1" :file-list="[]"
:on-remove="editUploadRemoveFile" :on-exceed="handleExceed" list-type="picture">
<el-button size="small" type="primary" class="button-new">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传图片文件</div>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
</el-upload>
</el-form-item>
</div> </div>
<!-- 组合能力 --> <!-- 组合能力 -->
@ -44,8 +53,10 @@
<div class="title"> <div class="title">
组合能力 组合能力
</div> </div>
<combine-ability v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="基础设施" ref="jcssDom" <!-- <combine-ability v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="基础设施" ref="jcssDom"
:getDataParams="getListParams['基础设施']"></combine-ability> :getDataParams="getListParams['基础设施']"></combine-ability> -->
<InfrastructureModal v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="基础设施"
ref="jcssDom"></InfrastructureModal>
<combine-ability v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="数据资源" ref="sjzyDom" <combine-ability v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="数据资源" ref="sjzyDom"
:getDataParams="getListParams['数据资源']"></combine-ability> :getDataParams="getListParams['数据资源']"></combine-ability>
<combine-ability v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="组件服务" ref="zjfwDom" <combine-ability v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="组件服务" ref="zjfwDom"
@ -74,21 +85,28 @@ import CombineAbility from './components/combine-ability.vue';
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import { tableColumns } from './IntegratedServices.vue'; import { tableColumns } from './IntegratedServices.vue';
import { getFuseResourceList, getListParams, modalTypeText } from "./assignedScene/add-update-scene.vue"; import { getFuseResourceList, getListParams, modalTypeText } from "./assignedScene/add-update-scene.vue";
import InfrastructureModal from './assignedScene/components/infrastructure-modal.vue'
export default { export default {
components: { components: {
CommonQuestion, CommonQuestion,
IntegratedCombineAbility, IntegratedCombineAbility,
CombineAbility, CombineAbility,
InfrastructureModal,
}, },
data() { data() {
return { return {
fileUploadUrl: `${window.SITE_CONFIG['apiURL']}/sys/oss/upload?token=${Cookies.get('ucsToken')}`, fileUploadUrl: window.SITE_CONFIG.apiURL + '/upload',
dataForm: { dataForm: {
"name": "", "name": "",
"applicationArea": "", "applicationArea": "",
"description": "", "description": "",
"fuseAttrList": [], "fuseAttrList": [
{
"attrType": "服务图片",
"attrValue": "",
},
],
// //
"fuseResourceList": [ "fuseResourceList": [
{ {
@ -96,7 +114,7 @@ export default {
"sequence": "" "sequence": ""
} }
], ],
type: '融合服务' type: '打包模式'
}, },
rules: { rules: {
name: [ name: [
@ -126,7 +144,12 @@ export default {
'jcssDom': '基础设施', 'jcssDom': '基础设施',
}, },
modalTypeText: modalTypeText, modalTypeText: modalTypeText,
abilityListObj: {} abilityListObj: {},
//
handleExceed() {
this.$message({ type: 'error', message: '最多支持一张图片上传' })
},
imageUrl: ''
}; };
}, },
props: { props: {
@ -217,6 +240,11 @@ export default {
'update': 'put' 'update': 'put'
} }
this.dataForm.fuseResourceList = this.getFuseResourceList() this.dataForm.fuseResourceList = this.getFuseResourceList()
if (this.imageUrl == '') {
this.$message.error("请上传图片!");
return;
}
this.dataForm.fuseAttrList.find(v => v.attrType == '服务图片').attrValue = this.imageUrl || '';
this.$http this.$http
[methodsObj[this.modalType]]("/fuse", this.dataForm) [methodsObj[this.modalType]]("/fuse", this.dataForm)
.then(({ data: res }) => { .then(({ data: res }) => {
@ -249,8 +277,55 @@ export default {
for (const key in this.refsParseArray) { for (const key in this.refsParseArray) {
this.$refs[key] && this.$refs[key].getDataInfo && this.$refs[key].getDataInfo(data) this.$refs[key] && this.$refs[key].getDataInfo && this.$refs[key].getDataInfo(data)
} }
let _imgObj = data.fuseAttrList.find(v => v.attrType == '服务图片') || {};
this.imageUrl = _imgObj.attrValue;
console.log('this.dataForm----详情-------->', this.dataForm);
}) })
}, },
beforeAvatarUpload(file) {
const isImage =
file.type === 'image/jpeg' ||
file.type === 'image/jpg' ||
file.type === 'image/png'
// const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
this.$message.error('上传头像图片只能是 jpg/png 格式!')
}
// if (!isLt2M) {
// this.$message.error(' 2MB!')
// }
return isImage
},
addUploadRemoveFile(file, fileList) {
this.$refs.addUpload.clearFiles()
this.imageUrl = ''
},
editBeforeAvatarUpload(file) {
const isImage =
file.type === 'image/jpeg' ||
file.type === 'image/jpg' ||
file.type === 'image/png'
// const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
this.$message.error('上传头像图片只能是 jpg/png 格式!')
}
// if (!isLt2M) {
// this.$message.error(' 2MB!')
// }
return isImage
},
editUploadRemoveFile(file, fileList) {
this.$refs.editUpload.clearFiles()
this.imageUrl = ''
},
eidtHandleAvatarSuccess(res, file) {
if (res.code !== 0) {
return this.$message.error(res.msg)
}
this.imageUrl = res.data
},
}, },
beforeDestroy() { beforeDestroy() {
this.clearForm() this.clearForm()
@ -372,4 +447,17 @@ export default {
font-size: 18px; font-size: 18px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.title {
text-align: center;
font-weight: 600;
font-size: 18px;
margin-bottom: 10px;
}
.avatar {
height: 100px;
width: 100px;
display: block;
}
</style> </style>

View File

@ -57,7 +57,7 @@ import { type } from "os";
export const tableColumns = { export const tableColumns = {
'name': '融合服务名称', 'name': '融合服务名称',
'description': '融合服务描述', 'description': '融合服务描述',
'applicationArea': '支撑场景', 'applicationArea': '应用领域',
} }
export default { export default {
@ -78,7 +78,7 @@ export default {
name: "", name: "",
order: 'desc', order: 'desc',
orderField: 'create_date', orderField: 'create_date',
type: '融合服务' type: '打包模式'
}, },
qp: false, qp: false,
modalType: 'add', modalType: 'add',

View File

@ -31,9 +31,15 @@
<el-input type="textarea" :rows="3" v-model="dataForm.description" placeholder="请输入描述" style="width:90%"> <el-input type="textarea" :rows="3" v-model="dataForm.description" placeholder="请输入描述" style="width:90%">
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="图片" prop="img">
<upload :key="1" type="图片" btnName="上传图片" :maxCount="1" :data="imgData" :list="[]" tip="支持图片类型大小不超过100M"> <el-form-item label="图片">
</upload> <el-upload ref="editUpload" class="upload-demo" :action="fileUploadUrl"
:on-success="eidtHandleAvatarSuccess" :before-upload="editBeforeAvatarUpload" :limit="1" :file-list="[]"
:on-remove="editUploadRemoveFile" :on-exceed="handleExceed" list-type="picture">
<el-button size="small" type="primary" class="button-new">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传图片文件</div>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
</el-upload>
</el-form-item> </el-form-item>
</div> </div>
@ -55,8 +61,6 @@
<div class="title"> <div class="title">
组合能力 组合能力
</div> </div>
<!-- <combine-ability v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="基础设施" ref="jcssDom"
:getDataParams="getListParams['基础设施']"></combine-ability> -->
<InfrastructureModal v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="基础设施" <InfrastructureModal v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="基础设施"
ref="jcssDom"></InfrastructureModal> ref="jcssDom"></InfrastructureModal>
<combine-ability v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="数据资源" ref="sjzyDom" <combine-ability v-model="dataForm" :dataForm="dataForm" @update="updateDataForm" type="数据资源" ref="sjzyDom"
@ -100,10 +104,7 @@ import InfrastructureModal from './components/infrastructure-modal.vue'
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import upload from '@/views/modules/components/upload' import upload from '@/views/modules/components/upload'
let btnArray = ['基本信息', '场景痛点', '解决方案', '组合能力', '更多能力', '使用步骤'] let btnArray = ['基本信息', '场景痛点', '解决方案', '组合能力', '更多能力', '使用步骤']
// \ // \
export const getJson = (type) => { export const getJson = (type) => {
return { return {
@ -122,13 +123,12 @@ export const getDescJson = (text) => {
}, },
} }
} }
// //
export const getListParams = { export const getListParams = {
'数据资源': getJson('数据资源'), '数据资源': getJson('数据资源'),
'组件服务': getJson('组件服务'), '组件服务': getJson('组件服务'),
'基础设施': ''
} }
// //
export const modalTypeText = { export const modalTypeText = {
add: '挂接', add: '挂接',
@ -166,7 +166,7 @@ export default {
}, },
data() { data() {
return { return {
fileUploadUrl: `${window.SITE_CONFIG['apiURL']}/sys/oss/upload?token=${Cookies.get('ucsToken')}`, fileUploadUrl: window.SITE_CONFIG.apiURL + '/upload',
moreKeyTextObj: { moreKeyTextObj: {
nameObj: { nameObj: {
text: '能力名称', text: '能力名称',
@ -197,13 +197,12 @@ export default {
"attrType": "解决方案", "attrType": "解决方案",
"attrValue": [{ description: "" }], "attrValue": [{ description: "" }],
}, },
],
"fuseResourceList": [
{ {
"resourceId": '', "attrType": "服务图片",
"sequence": "" "attrValue": "",
} },
], ],
"fuseResourceList": [],
}, },
rules: { rules: {
name: [ name: [
@ -240,7 +239,12 @@ export default {
}, },
getListParams: getListParams, getListParams: getListParams,
abilityListObj: {}, abilityListObj: {},
imgData: [] imgData: [],
//
handleExceed() {
this.$message({ type: 'error', message: '最多支持一张图片上传' })
},
imageUrl: '',
}; };
}, },
props: { props: {
@ -321,10 +325,14 @@ export default {
'update': 'put' 'update': 'put'
} }
this.dataForm.fuseResourceList = this.getFuseResourceList() this.dataForm.fuseResourceList = this.getFuseResourceList()
if (this.imageUrl == '') {
this.$message.error("请上传图片!");
return;
}
this.dataForm.fuseAttrList.find(v => v.attrType == '服务图片').attrValue = this.imageUrl || '';
let _obj = Object.assign({}, this.dataForm, { let _obj = Object.assign({}, this.dataForm, {
type: '赋能场景' type: '赋能场景'
}) })
this.$http this.$http
[methodsObj[this.modalType]]("/fuse", _obj) [methodsObj[this.modalType]]("/fuse", _obj)
.then(({ data: res }) => { .then(({ data: res }) => {
@ -357,11 +365,61 @@ export default {
for (const key in this.refsParseArray) { for (const key in this.refsParseArray) {
this.$refs[key] && this.$refs[key].getDataInfo && this.$refs[key].getDataInfo(data) this.$refs[key] && this.$refs[key].getDataInfo && this.$refs[key].getDataInfo(data)
} }
let _imgObj = data.fuseAttrList.find(v => v.attrType == '服务图片') || {};
this.imageUrl = _imgObj.attrValue
console.log('this.dataForm----详情-------->', this.dataForm); console.log('this.dataForm----详情-------->', this.dataForm);
}) })
}, },
changeInfoList() { handleAvatarSuccess(res, file) {
if (res.code !== 0) {
return this.$message.error(res.msg)
}
this.imageUrl = res.data
},
beforeAvatarUpload(file) {
const isImage =
file.type === 'image/jpeg' ||
file.type === 'image/jpg' ||
file.type === 'image/png'
// const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
this.$message.error('上传头像图片只能是 jpg/png 格式!')
}
// if (!isLt2M) {
// this.$message.error(' 2MB!')
// }
return isImage
},
addUploadRemoveFile(file, fileList) {
this.$refs.addUpload.clearFiles()
this.imageUrl = ''
},
editBeforeAvatarUpload(file) {
const isImage =
file.type === 'image/jpeg' ||
file.type === 'image/jpg' ||
file.type === 'image/png'
// const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
this.$message.error('上传头像图片只能是 jpg/png 格式!')
}
// if (!isLt2M) {
// this.$message.error(' 2MB!')
// }
return isImage
},
editUploadRemoveFile(file, fileList) {
this.$refs.editUpload.clearFiles()
this.imageUrl = ''
},
eidtHandleAvatarSuccess(res, file) {
if (res.code !== 0) {
return this.$message.error(res.msg)
}
this.imageUrl = res.data
// this.imageUrl = URL.createObjectURL(file.raw);
}, },
}, },
beforeDestroy() { beforeDestroy() {
@ -486,4 +544,10 @@ export default {
font-size: 18px; font-size: 18px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.avatar {
height: 100px;
width: 100px;
display: block;
}
</style> </style>

View File

@ -0,0 +1,81 @@
<template>
<div class="show-box" v-if="displayList.length > 0">
<div class="list-box">
<div v-for="(item, i) in displayList" :key="i">
<el-tooltip popper-class="testTooltip" effect="dark" :content="item[showKey] || '--'" placement="top">
<div class="list-item">
{{ item[showKey] || '--' }}
</div>
</el-tooltip>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
displayList: {
type: Array,
default: () => []
},
showKey: {
type: String,
default: 'name'
},
},
watch: {
displayList: {
handler(newVal) {
this.displayList = newVal;
},
deep: true,
immediate: true,
}
}
}
</script>
<style lang="scss" scoped>
.show-box {
display: flex;
justify-content: flex-start;
align-items: flex-start;
}
.top {
display: flex;
align-items: center;
justify-content: flex-start;
}
.type {
padding-right: 12px;
font-size: 14px;
color: #606266;
width: 100px;
box-sizing: border-box;
text-align: right;
line-height: 40px;
height: 40px;
}
.list-box {
display: flex;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
margin-left: 100px;
}
.list-item {
font-size: 14px;
color: #606266;
width: 380px;
text-align: left;
margin-right: 10px;
line-height: 40px;
height: 40px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@ -46,7 +46,7 @@
<div class="tree-table-box"> <div class="tree-table-box">
<InfrastructureTree @changeParentId="changeParentId"></InfrastructureTree> <InfrastructureTree @changeParentId="changeParentId"></InfrastructureTree>
<div class="table-box"> <div class="table-box">
<el-table v-loading="dataListLoading" :data="dataList" border <el-table v-loading="dataListLoading" :data="dataList" border ref="dataTable"
@selection-change="dataListSelectionChangeHandle" @selection-change="dataListSelectionChangeHandle"
style="width: 95%;margin: 0 auto 10px auto" size="mini"> style="width: 95%;margin: 0 auto 10px auto" size="mini">
<el-table-column type="selection" header-align="center" align="center" width="50"> <el-table-column type="selection" header-align="center" align="center" width="50">
@ -64,7 +64,6 @@
</div> </div>
</div> </div>
</div> </div>
<template slot="footer"> <template slot="footer">
<el-button @click="showModal = false">{{ $t("cancel") }}</el-button> <el-button @click="showModal = false">{{ $t("cancel") }}</el-button>
<el-button type="primary" @click="confirmSubmitHandle()">{{ <el-button type="primary" @click="confirmSubmitHandle()">{{
@ -72,15 +71,19 @@
}}</el-button> }}</el-button>
</template> </template>
</el-dialog> </el-dialog>
<DisplayList :displayList="displayList"></DisplayList>
</div> </div>
</template> </template>
<script> <script>
import InfrastructureTree from './infrastructure-tree.vue' import InfrastructureTree from './infrastructure-tree.vue'
import DisplayList from './display-list.vue';
export default { export default {
components: { components: {
InfrastructureTree, InfrastructureTree,
DisplayList
}, },
props: { props: {
// //
@ -141,18 +144,61 @@ export default {
} }
}, },
watch: { watch: {
showModal(newVal) { async showModal(newVal) {
if (newVal) { if (newVal) {
this.getData() await this.getData();
//
this.$nextTick(() => {
this.selectCheckbox()
})
} }
} }
}, },
mounted() { mounted() {
// //
this.getVideoBtn() this.getVideoBtn()
//
this.getData() this.getData()
}, },
methods: { methods: {
selectCheckbox() {
if (this.selectedArray.length > 0) {
let list = []
this.dataList.forEach((item) => {
this.selectedArray.forEach(val => {
if (val === item.idtCameraChannel) {
list.push(item)
}
})
})
if (list.length) {
list.forEach((row) => {
this.$refs.dataTable.toggleRowSelection(row, true)
})
}
}
},
async getDataInfo(dataForm) {
await this.getVideoBtn();
await this.getData();
let arr = [];
let attrValue = dataForm.fuseResourceList.filter(v => v.type == this.type);
if (attrValue.length > 0) {
attrValue.map(val => {
let item = this.dataList.find(v => v.idtCameraChannel == val.resourceId) || {};
let _obj = {
type: val.type,
idtCameraChannel: val.resourceId,
name: item.channelName
};
arr.push(_obj);
});
}
//
this.displayList = JSON.parse(JSON.stringify(arr));
//
this.selectedArray = arr.map(v => v.idtCameraChannel);
},
handleShowModal() { handleShowModal() {
this.showModal = true; this.showModal = true;
}, },
@ -176,17 +222,22 @@ export default {
this.currentList = this.tabData.find(v => v.tabName == btn).list || [] this.currentList = this.tabData.find(v => v.tabName == btn).list || []
}, },
// //
getVideoBtn(btn) { getVideoBtn() {
this.$http.get('/api/project/selectAllLabel', {}).then(res => { return new Promise((resolve, reject) => {
if (res.data.code !== 1) { this.$http.get('/api/project/selectAllLabel', {}).then(res => {
return this.$message.error(res.msg); resolve(res)
} if (res.data.code !== 1) {
this.tabData[0].list = res.data.data || []; return this.$message.error(res.msg);
}
this.tabData[0].list = res.data.data || [];
this.changeBtn(this.activeBtn) this.changeBtn(this.activeBtn)
}).catch(err => { }).catch(err => {
this.$message.error(err); reject(err)
this.$message.error(err);
})
}) })
}, },
// - // -
clearVideo() { clearVideo() {
@ -216,16 +267,20 @@ export default {
radius: '', radius: '',
labelCodes: '', labelCodes: '',
} }
this.$http.get('/api/project/selectByParentIdNew', { params: postData }).then(res => { return new Promise((resolve, reject) => {
if (res.data.code !== 1) { this.$http.get('/api/project/selectByParentIdNew', { params: postData }).then(res => {
return this.$message.error(res.msg); resolve(res)
} if (res.data.code !== 1) {
this.dataList = res.data.data || [] return this.$message.error(res.msg);
this.pageData.total = res.data.count || 0; }
this.dataList = res.data.data || []
}).catch(err => { this.pageData.total = res.data.count || 0;
this.$message.error(err); }).catch(err => {
reject(err)
this.$message.error(err);
})
}) })
}, },
changeParentId(parentId) { changeParentId(parentId) {
this.getData(parentId) this.getData(parentId)
@ -236,14 +291,17 @@ export default {
} }
this.showModal = false; this.showModal = false;
this.displayList = [] this.displayList = []
let idtCameraChannelArray = this.selectedArray.map(v => v.idtCameraChannel)
this.dataList.map(v => { this.dataList.map(v => {
if (this.selectedArray.includes(v.id)) { if (idtCameraChannelArray.includes(v.idtCameraChannel)) {
this.displayList.push(v) this.displayList.push({
name: v.channelName
})
} }
}) })
this.$emit('update', { this.$emit('update', {
title: this.type, title: this.type,
list: this.selectedArray list: this.selectedArray.map(v => v.idtCameraChannel)
}) })
}, },
close() { close() {

View File

@ -23,6 +23,8 @@
</template> </template>
</el-dialog> </el-dialog>
<DisplayList></DisplayList>
<div class="show-box" v-if="displayList.length > 0"> <div class="show-box" v-if="displayList.length > 0">
<div class="list-box"> <div class="list-box">
<div v-for="(item, i) in displayList" :key="i"> <div v-for="(item, i) in displayList" :key="i">
@ -40,6 +42,7 @@
<script> <script>
import InfrastructureModal from '../assignedScene/components/infrastructure-modal.vue' import InfrastructureModal from '../assignedScene/components/infrastructure-modal.vue'
import DisplayList from '../assignedScene/components/display-list.vue'
let sjzyArray = [ let sjzyArray = [
@ -190,7 +193,7 @@ export default {
watch: { watch: {
showModal(newVal) { showModal(newVal) {
if (newVal) { if (newVal) {
this.getData(); this.getData();
} }
} }
}, },
@ -220,19 +223,6 @@ export default {
}, },
// //
getData() { getData() {
// if (this.getDataParams.url === '') {
// return;
// }
// this.$http[this.getDataParams.methods](this.getDataParams.url, this.getDataParams.postData).then(res => {
// console.log('res.data------------>', res.data);
// if (res.data.code !== 0) {
// return this.$message.error(res.msg);
// }
// this.transferData = res.data.data || [] // allData
// this.allData = res.data.data || []
// }).catch(err => {
// this.$message.error(err);
// })
this.allData = []; this.allData = [];
this.transferData = []; this.transferData = [];
let arr = JSON.parse(JSON.stringify(sjzyArray)); let arr = JSON.parse(JSON.stringify(sjzyArray));
@ -269,14 +259,13 @@ export default {
this.displayList = displayList this.displayList = displayList
}, },
close() { close() {
// this.$emit("isShowRelatePopup", false);
this.showModal = false; this.showModal = false;
this.selectedArray = []; this.selectedArray = [];
this.allData = []; this.allData = [];
this.transferData = []; this.transferData = [];
}, },
}, },
components: { InfrastructureModal } components: { InfrastructureModal, DisplayList }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>