feat: 地图数据管理功能页面开发

地图数据管理功能页面开发
main
hong.yang 2 years ago
parent 8b5561ed85
commit 5fbd906269

@ -113,6 +113,12 @@ export default {
name: '数据集管理',
path: window?.BS_CONFIG?.routers?.dataSetUrl || '/big-screen-dataSet',
icon: 'icon-data'
},
{
id: 5,
name: '地图数据管理',
path: '/big-screen-map-data',
icon: 'icon-data'
}
]
}

@ -0,0 +1,7 @@
import MapDataManagement from './src/index.vue'
MapDataManagement.install = function (Vue) {
Vue.component(MapDataManagement.name, MapDataManagement)
}
export default MapDataManagement

@ -0,0 +1,338 @@
<template>
<div>
<el-dialog
:append-to-body="true"
:before-close="handleClose"
:close-on-click-modal="false"
:title="title"
:visible.sync="mapFormVisible"
class="bs-dialog-wrap bs-el-dialog"
width="700px"
>
<el-form
ref="mapForm"
:model="mapForm"
:rules="rules"
class="bs-el-form"
label-width="120px"
>
<el-form-item
label="上级地图"
prop="parentCode"
>
<el-input
v-model="parentName"
class="bs-el-input"
disabled
/>
</el-form-item>
<el-form-item
label="地图名称"
prop="name"
>
<el-input
v-model="mapForm.name"
class="bs-el-input"
placeholder="请输入"
/>
</el-form-item>
<el-form-item
label="地图编码"
prop="mapCode"
>
<el-input
v-if="mapForm.parentCode === '0'"
v-model="mapForm.mapCode"
class="bs-el-input"
placeholder="请输入地图编码"
/>
<el-select
v-else
v-model="mapForm.mapCode"
class="bs-el-select"
placeholder="请选择地图编码"
popper-class="bs-el-select"
>
<el-option
v-for="mapCode in mapCodeList"
:key="mapCode.name"
:disabled="mapCode.exist"
:label="mapCode.name"
:value="mapCode.name"
>
<span style="float: left">{{ mapCode.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ mapCode.exist ? '已存在' : '' }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item
label="地图级别"
prop="level"
>
<el-select
v-model="mapForm.level"
:disabled="mapForm.parentCode !== '0'"
class="bs-el-select"
placeholder="请选择地图级别"
popper-class="bs-el-select"
>
<el-option
v-for="level in levelList"
:key="level.value"
:label="level.label"
:value="level.value"
/>
</el-select>
</el-form-item>
<el-form-item
label="开启下钻"
prop="enableDown"
>
<el-switch
v-model="mapForm.enableDown"
:active-value="1"
:inactive-value="0"
class="bs-el-switch"
/>
</el-form-item>
<el-form-item
label="geoJson上传"
>
<vue-json-viewer
v-model="mapForm.geoJson"
theme="dark"
:show-btns="false"
mode="code"
/>
<el-button
class="bs-el-button-default"
@click="upload"
>
<i class="el-icon-upload2" />
上传
</el-button>
</el-form-item>
<el-form-item
v-if="autoParseNextLevelShow"
label="自动解析下一级"
prop="autoParseNextLevel"
>
<el-switch
v-model="mapForm.autoParseNextLevel"
:active-value="1"
:inactive-value="0"
class="bs-el-switch"
/>
</el-form-item>
</el-form>
<span
slot="footer"
class="dialog-footer"
>
<el-button
class="bs-el-button-default"
@click="handleClose"
>
取消
</el-button>
<el-button
type="primary"
@click="submitForm"
>
确定
</el-button>
</span>
</el-dialog>
<input
ref="geoJsonFile"
accept=".json"
name="file"
style="display: none"
type="file"
@change="handleBatchUpload"
>
</div>
</template>
<script>
import vueJsonViewer from 'vue-json-viewer'
import {getMapChildFromGeoJson, mapAdd, repeatCheck} from 'data-room-ui/js/utils/mapDataService'
export default {
name: "AddForm",
components: {
vueJsonViewer
},
computed: {
autoParseNextLevelShow() {
// geoJson
return !this.isEmpty(this.mapForm.geoJson) && this.mapForm.enableDown === 1
}
},
data() {
const validateCode = (rule, value, callback) => {
console.log(value)
if (this.mapForm.parentCode !== '0') {
//
callback()
}
repeatCheck({
parentCode: this.mapForm.parentCode,
mapCode: value
}).then(res => {
if (res) {
callback(new Error('地图编码已存在'))
} else {
callback()
}
})
}
return {
mapFormVisible: false,
geoJsonVisible: false,
uploadLoading: false,
title: '新增地图数据',
parentName: '顶级',
mapForm: {
parentId: '0',
parentCode: '0',
mapCode: '',
name: '',
level: 0,
enableDown: 0,
geoJson: {},
uploadedGeoJson: 0,
autoParseNextLevel: 0
},
rules: {
mapCode: [
{required: true, message: '请选择地图编码', trigger: 'blur'},
{validator: validateCode, trigger: 'blur'}
],
name: [
{required: true, message: '请输入地图名称', trigger: 'blur'}
],
level: [
{required: true, message: '请选择地图级别', trigger: 'change'}
]
},
levelList: [
{value: 0, label: '世界'},
{value: 1, label: '国家'},
{value: 2, label: '省份'},
{value: 3, label: '城市'},
{value: 4, label: '区县'}
],
mapCodeList: []
}
},
methods: {
init(parentMap) {
this.mapForm = {
parentId: '0',
parentCode: '0',
mapCode: `map-${new Date().getTime()}`,
name: '',
level: 0,
enableDown: 0,
geoJson: {},
uploadedGeoJson: 0,
autoParseNextLevel: 0
}
this.parentName = '顶级'
if (parentMap) {
this.mapForm.parentId = parentMap.id
this.mapForm.parentCode = parentMap.mapCode
this.parentName = parentMap.name
this.mapForm.level = parentMap.level + 1
this.mapForm.mapCode = ''
this.getMapCodeList()
}
},
handleClose() {
this.mapFormVisible = false
},
submitForm() {
this.$refs.mapForm.validate(valid => {
if (!valid) {
return false
}
let geoJson
// geoJson
if (this.isEmpty(this.mapForm.geoJson)) {
geoJson = ''
} else {
geoJson = JSON.stringify(this.mapForm.geoJson)
}
mapAdd({
...this.mapForm,
geoJson: geoJson
}).then(res => {
this.mapFormVisible = false
this.$emit('refresh')
})
})
},
isEmpty(obj) {
if (typeof obj === 'object') {
return Object.keys(obj).length === 0 && obj.constructor === Object;
}
if (typeof obj === 'string') {
return /^\s*$/.test(obj);
}
return Array.isArray(obj) && obj.length === 0;
},
getMapCodeList() {
this.mapCodeList = []
if (this.mapForm.parentCode === '0') {
this.mapCodeList = [{
name: `map-${new Date().getTime()}`,
exist: false
}]
} else {
getMapChildFromGeoJson(this.mapForm.parentCode).then(res => {
this.mapCodeList = res
})
}
},
upload() {
this.$refs.geoJsonFile.click()
},
handleBatchUpload(source) {
this.uploadLoading = true
const file = source.target.files
const reader = new FileReader() // FileReader
reader.readAsText(file[0], 'UTF-8') //
reader.onload = (event) => {
let jsonStr = event.target.result
//
try {
this.mapForm.geoJson = JSON.parse(jsonStr)
} catch (e) {
this.uploadLoading = false
this.$message.error('JSON文件格式错误')
return false
}
this.uploadLoading = false
// inputonchangejsonchangeinput
source.target.value = ''
}
},
}
}
</script>
<style lang="scss" scoped>
@import '../../assets/style/bsTheme.scss';
.jv-container.dark {
color: aliceblue;
background: #161A26;
}
</style>

@ -0,0 +1,273 @@
<template>
<div>
<el-dialog
:append-to-body="true"
:before-close="handleClose"
:close-on-click-modal="false"
:title="title"
:visible.sync="mapFormVisible"
class="bs-dialog-wrap bs-el-dialog"
width="700px"
>
<el-form
ref="mapForm"
:model="mapForm"
:rules="rules"
class="bs-el-form"
label-width="120px"
>
<el-form-item
label="上级地图"
prop="parentCode"
>
<el-input
v-model="parentName"
class="bs-el-input"
disabled
/>
</el-form-item>
<el-form-item
label="地图名称"
prop="name"
>
<el-input
v-model="mapForm.name"
class="bs-el-input"
placeholder="请输入"
/>
</el-form-item>
<el-form-item
label="地图编码"
prop="mapCode"
>
<el-input
v-model="mapForm.mapCode"
class="bs-el-input"
disabled
placeholder="请输入地图编码"
/>
</el-form-item>
<el-form-item
label="地图级别"
prop="level"
>
<el-select
v-model="mapForm.level"
disabled
class="bs-el-select"
placeholder="请选择地图级别"
popper-class="bs-el-select"
>
<el-option
v-for="level in levelList"
:key="level.value"
:label="level.label"
:value="level.value"
/>
</el-select>
</el-form-item>
<el-form-item
label="开启下钻"
prop="enableDown"
>
<el-switch
v-model="mapForm.enableDown"
:active-value="1"
:inactive-value="0"
class="bs-el-switch"
/>
</el-form-item>
<el-form-item
label="geoJson"
>
<vue-json-viewer
v-model="mapForm.geoJson"
theme="dark"
:show-btns="false"
mode="code"
/>
<el-button
v-if="mapForm.uploadedGeoJson !== 1"
class="bs-el-button-default"
@click="upload"
>
<i class="el-icon-upload2" />
上传
</el-button>
</el-form-item>
<el-form-item
v-if="autoParseNextLevelShow"
label="自动解析下一级"
prop="autoParseNextLevel"
>
<el-switch
v-model="mapForm.autoParseNextLevel"
:active-value="1"
:inactive-value="0"
class="bs-el-switch"
/>
</el-form-item>
</el-form>
<span
slot="footer"
class="dialog-footer"
>
<el-button
class="bs-el-button-default"
@click="handleClose"
>
取消
</el-button>
<el-button
type="primary"
@click="submitForm"
>
确定
</el-button>
</span>
</el-dialog>
<input
ref="geoJsonFile"
accept=".json"
name="file"
style="display: none"
type="file"
@change="handleBatchUpload"
>
</div>
</template>
<script>
import _ from 'lodash'
import vueJsonViewer from 'vue-json-viewer'
import {mapUpdate} from 'data-room-ui/js/utils/mapDataService'
export default {
name: "EditForm",
components: {
vueJsonViewer
},
computed: {
autoParseNextLevelShow() {
// geoJson ,
return !this.isWhitespace(this.mapForm.geoJson) && this.mapForm.enableDown === 1 && this.mapForm.uploadedGeoJson === 0
}
},
data() {
return {
mapFormVisible: false,
geoJsonVisible: false,
uploadLoading: false,
title: '编辑地图数据',
parentName: '顶级',
mapForm: {
parentId: '0',
parentCode: '0',
mapCode: '',
name: '',
level: 0,
enableDown: 0,
geoJson: '',
uploadedGeoJson: 0,
autoParseNextLevel: 0
},
rules: {
mapCode: [
{required: true, message: '请输入地图编码', trigger: 'blur'}
],
name: [
{required: true, message: '请输入地图名称', trigger: 'blur'}
],
level: [
{required: true, message: '请选择地图级别', trigger: 'change'}
],
geoJson: [
{required: true, message: '请上传地图数据', trigger: 'change'}
]
},
levelList: [
{value: 0, label: '世界'},
{value: 1, label: '国家'},
{value: 2, label: '省份'},
{value: 3, label: '城市'},
{value: 4, label: '区县'}
],
mapCodeList: []
}
},
methods: {
init(map) {
this.mapForm = _.cloneDeep(map)
if (!this.isWhitespace(this.mapForm.geoJson)) {
this.mapForm.geoJson = JSON.parse(this.mapForm.geoJson)
}
},
handleClose() {
this.mapFormVisible = false
},
submitForm() {
this.$refs.mapForm.validate(valid => {
if (!valid) {
return false
}
let geoJson
// geoJson
if (this.isWhitespace(this.mapForm.geoJson) || this.mapForm.geoJson === '{}' || this.mapForm.geoJson === '[]') {
geoJson = ''
} else {
geoJson = JSON.stringify(this.mapForm.geoJson)
}
mapUpdate({
...this.mapForm,
geoJson: geoJson
}).then(res => {
this.mapFormVisible = false
this.$emit('refresh')
})
})
},
isWhitespace(str) {
// nullundefinedtrue
if (str == null) {
return true
}
return /^\s*$/.test(str);
},
upload() {
this.$refs.geoJsonFile.click()
},
handleBatchUpload(source) {
this.uploadLoading = true
const file = source.target.files
const reader = new FileReader() // FileReader
reader.readAsText(file[0], 'UTF-8') //
reader.onload = (event) => {
let jsonStr = event.target.result
//
try {
this.mapForm.geoJson = JSON.parse(jsonStr)
} catch (e) {
this.uploadLoading = false
this.$message.error('JSON文件格式错误')
return false
}
this.uploadLoading = false
// inputonchangejsonchangeinput
source.target.value = ''
}
},
}
}
</script>
<style lang="scss" scoped>
@import '../../assets/style/bsTheme.scss';
.jv-container.dark {
color: aliceblue;
background: #161A26;
}
</style>

@ -0,0 +1,426 @@
<template>
<div class="bs-container">
<div class="inner-container">
<el-form
:inline="true"
class="filter-container"
>
<el-form-item class="filter-input filter-item">
<el-input
v-model="searchForm.searchKey"
class="bs-el-input"
clearable
maxlength="200"
placeholder="请输入地图名称或编码"
/>
</el-form-item>
<el-form-item class="filter-item">
<el-select
v-model="searchForm.level"
class="bs-el-select"
clearable
placeholder="请选择地图级别"
popper-class="bs-el-select"
@change="getDataList"
>
<el-option
v-for="level in levelList"
:key="level.value"
:label="level.label"
:value="level.value"
/>
</el-select>
</el-form-item>
<el-form-item class="filter-item">
<el-button
:loading="searchLoading"
icon="el-icon-search"
type="primary"
@click="getDataList"
>
查询
</el-button>
</el-form-item>
<el-form-item class="filter-item">
<el-button
class="bs-el-button-default"
@click="addMap"
>
新增
</el-button>
</el-form-item>
</el-form>
<div class="bs-table-box">
<el-table
v-loading="searchLoading"
ref="table"
v-table
:data="mapList"
:element-loading-text="loadingText"
:load="load"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
class="bs-el-table bs-scrollbar"
height="0"
lazy
row-key="id"
>
<el-empty slot="empty"/>
<el-table-column
align="left"
label="名称"
prop="name"
show-overflow-tooltip
/>
<el-table-column
align="center"
label="编码"
prop="mapCode"
show-overflow-tooltip
/>
<el-table-column
align="center"
label="级别"
prop="level"
show-overflow-tooltip
>
<template slot-scope="scope">
<span v-if="scope.row.level === 0"></span>
<span v-else-if="scope.row.level === 1">国家</span>
<span v-else-if="scope.row.level === 2">省份</span>
<span v-else-if="scope.row.level === 3">城市</span>
<span v-else-if="scope.row.level === 4">区县</span>
</template>
</el-table-column>
<el-table-column
align="center"
label="开启下钻"
prop="enableDown"
show-overflow-tooltip
>
<template slot-scope="scope">
<span v-if="scope.row.enableDown === 1"></span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column
align="center"
label="已上传配置JSON"
prop="uploadedGeoJson"
show-overflow-tooltip
>
<template slot-scope="scope">
<span v-if="scope.row.uploadedGeoJson === 1"></span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column
align="center"
label="操作"
width="200"
>
<template slot-scope="scope">
<el-button
class="bs-el-button-default"
@click="editMap(scope.row)"
>
编辑
</el-button>
<el-button
class="bs-el-button-default"
@click="deleteMap(scope.row)"
>
删除
</el-button>
<el-button
v-if="scope.row.uploadedGeoJson === 1"
class="bs-el-button-default"
@click="addChild(scope.row)"
>
添加下级
</el-button>
<el-button
v-if="scope.row.uploadedGeoJson === 0"
class="bs-el-button-default"
@click="uploadGeoJson(scope.row)"
>
上传配置
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<add-form
ref="addForm"
@refresh="getDataList"
/>
<edit-form
ref="editForm"
@refresh="getDataList"
/>
<el-dialog
:close-on-click-modal="false"
:visible.sync="geoJsonVisible"
append-to-body
class="bs-dialog-wrap bs-el-dialog"
height="1000px"
title="geoJson数据"
width="1000px"
>
<vue-json-viewer
v-model="currentMapGeoJSon"
theme="dark"
:show-btns="false"
mode="code"
/>
<el-button
class="bs-el-button-default"
@click="upload()"
>
<i class="el-icon-upload2"></i>
上传
</el-button>
<span
slot="footer"
class="dialog-footer"
>
<el-button
class="bs-el-button-default"
@click="submitUpload"
>提交</el-button>
</span>
</el-dialog>
<input
ref="geoJsonFileUpload"
accept=".json"
name="file"
style="display: none"
type="file"
@change="handleBatchUpload"
>
</div>
</template>
<script>
import table from 'data-room-ui/js/utils/table.js'
import {mapList, mapDelete, uploadGeoJson, mapCascadeDelete} from 'data-room-ui/js/utils/mapDataService'
import AddForm from "./AddForm"
import EditForm from "./EditForm"
import vueJsonViewer from 'vue-json-viewer'
export default {
name: "MapManagement",
directives: {
table //
},
components: {
AddForm,
EditForm,
vueJsonViewer
},
data() {
return {
currentMap: {}, //
currentMapGeoJSon: {}, // geoJson
loadingText: '',
searchLoading: false,
geoJsonVisible: false,
lazyResolveIds: [],
searchForm: {
searchKey: '',
level: null,
enableDown: null,
uploadedGeoJson: null,
parentCode: '0'
},
levelList: [
{
label: '世界',
value: 0
},
{
label: '国家',
value: 1
},
{
label: '省份',
value: 2
},
{
label: '城市',
value: 3
},
{
label: '区县',
value: 4
}
],
mapList: []
}
},
mounted() {
this.init()
},
methods: {
init() {
this.searchLoading = true
this.loadingText = '正在加载地图数据...'
mapList(this.searchForm).then(res => {
this.mapList = res
this.searchLoading = false
}).catch(err => {
this.searchLoading = false
})
},
getDataList() {
this.searchLoading = true
this.loadingText = '正在加载地图数据...'
mapList(this.searchForm).then(res => {
this.mapList = res
this.searchLoading = false
}).catch(err => {
this.searchLoading = false
})
//
for (let i = 0; i < this.lazyResolveIds.length; i++) {
this.$refs.table.store.states.treeData[this.lazyResolveIds[i]].loaded = false;
this.$refs.table.store.states.treeData[this.lazyResolveIds[i]].expanded = false
}
},
addMap() {
this.$refs.addForm.mapFormVisible = true
this.$refs.addForm.init()
},
load(data, treeNode, resolve) {
this.lazyResolveIds.push(data.id)
mapList({
parentCode: data.mapCode
}).then(res => {
resolve(res)
}).catch(err => {
resolve([])
})
},
deleteMap(map) {
this.$confirm('确定删除该地图?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
customClass: 'bs-el-message-box'
}).then(async () => {
mapDelete(map.id).then(() => {
this.$message({
type: 'success',
message: '删除成功'
})
this.getDataList()
}).catch(() => {
this.deleteMapCascade(map)
})
}).catch(() => {
})
},
deleteMapCascade(map) {
this.$confirm('该地图存在子级,是否直接删除该地图以及其所有子级?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
customClass: 'bs-el-message-box'
}).then(async () => {
mapCascadeDelete(map.id).then(() => {
this.$message({
type: 'success',
message: '删除成功'
})
this.getDataList()
}).catch(() => {
})
}).catch(() => {
})
},
addChild(map) {
this.$refs.addForm.mapFormVisible = true
this.$refs.addForm.init(map)
},
editMap(map) {
this.$refs.editForm.mapFormVisible = true
this.$refs.editForm.init(map)
},
uploadGeoJson(map) {
this.currentMap = map
this.currentMapGeoJSon = {}
this.geoJsonVisible = true
},
upload() {
this.$refs.geoJsonFileUpload.click()
},
handleBatchUpload(source) {
this.uploadLoading = true
const file = source.target.files
const reader = new FileReader() // FileReader
reader.readAsText(file[0], 'UTF-8') //
reader.onload = (event) => {
let jsonStr = event.target.result
//
try {
this.currentMapGeoJSon = JSON.parse(jsonStr)
} catch (e) {
this.uploadLoading = false
this.$message.error('JSON文件格式错误')
return false
}
this.uploadLoading = false
// inputonchangejsonchangeinput
source.target.value = ''
}
},
submitUpload() {
// JSON
if (typeof this.currentMapGeoJSon === 'string') {
this.$message.error('JSON文件格式错误')
return false
}
if (this.currentMapGeoJSon === {}) {
this.$message.error('JSON数据不能为空')
return false
}
//
uploadGeoJson({
id: this.currentMap.id,
geoJson: JSON.stringify(this.currentMapGeoJSon)
}).then(res => {
this.$message({
type: 'success',
message: '上传成功'
})
this.geoJsonVisible = false
this.getDataList()
}).catch(err => {
this.$message({
type: 'error',
message: '上传失败'
})
})
this.geoJsonVisible = false
},
isWhitespace(str) {
// nullundefinedtrue
if (str == null) {
return true
}
return /^\s*$/.test(str);
},
},
}
</script>
<style lang="scss" scoped>
@import '../../assets/style/bsTheme.scss';
.jv-container.dark {
color: aliceblue;
background: #161A26;
}
</style>

@ -610,6 +610,7 @@
.el-switch__core {
background: var(--bs-el-background-1);
background-color: var(--bs-el-background-1);
border-color: var(--bs-el-border);
}
}

@ -0,0 +1,71 @@
/*!
* 地图数据管理
*/
import Vue from 'vue'
/**
* 获取地图列表
* @param params
* @param flag
* @returns {*}
*/
const mapList = (params = {}, flag = false) => Vue.prototype.$dataRoomAxios.get('/bigScreen/map/list', params, flag)
/**
* 新增地图
* @param params
* @param flag
* @returns {*}
*/
const mapAdd = (params = {}, flag = false) => Vue.prototype.$dataRoomAxios.post('/bigScreen/map/add', params, flag)
/**
* 更新地图
* @param params
* @param flag
* @returns {*}
*/
const mapUpdate = (params = {}, flag = false) => Vue.prototype.$dataRoomAxios.post('/bigScreen/map/update', params, flag)
/**
* 删除地图
* @param id
*/
const mapDelete = (id = '-1') => Vue.prototype.$dataRoomAxios.post(`/bigScreen/map/delete/${id}`)
/**
* 级联删除地图
* @param id
*/
const mapCascadeDelete = (id = '-1') => Vue.prototype.$dataRoomAxios.post(`/bigScreen/map/cascadeDelete/${id}`)
/**
* 根据父编码解析父级json中的子级
* @param code
*/
const getMapChildFromGeoJson = (code = '-1') => Vue.prototype.$dataRoomAxios.get(`/bigScreen/map/getMapChildFromGeoJson/${code}`)
/**
* 上传地图json
* @param params
* @param flag
*/
const uploadGeoJson = (params = {}, flag = false) => Vue.prototype.$dataRoomAxios.post('/bigScreen/map/upload', params, flag)
/**
* 编码重复校验
* @param params
* @param flag
*/
const repeatCheck = (params = {}, flag = false) => Vue.prototype.$dataRoomAxios.post('/bigScreen/map/repeat', params, flag)
export {
mapList,
mapAdd,
mapUpdate,
mapDelete,
mapCascadeDelete,
getMapChildFromGeoJson,
uploadGeoJson,
repeatCheck
}

@ -72,6 +72,13 @@ function registerRouters (config, router) {
title: '数据集管理'
}
},
{
path: '/big-screen-map-data',
component: () => import('data-room-ui/MapDataManagement'),
meta: {
title: '地图数据管理'
}
},
{
path: config?.routers?.SourceUrl || '/big-screen-source',
component: () => import('data-room-ui/SourceManagement'),

Loading…
Cancel
Save