Commit 55932a77 by 李君

排队记录

1 parent 6f062f12
...@@ -78,7 +78,12 @@ const queueManagementApi = { ...@@ -78,7 +78,12 @@ const queueManagementApi = {
deleteOpentime(params, config) { deleteOpentime(params, config) {
return req('DELETE', `/b-queue-mall-opentime/${params.id}`, params, config) return req('DELETE', `/b-queue-mall-opentime/${params.id}`, params, config)
}, },
getRecognition(params, config) {
return req('get', `/d-queue-recognition/page`, params, config)
},
getTrajectory(params, config) {
return req('get', `/queuing/person/trajectory`, params, config)
},
} }
......
...@@ -15,6 +15,14 @@ const faceAnalysisRouterMap = { ...@@ -15,6 +15,14 @@ const faceAnalysisRouterMap = {
component: () => component: () =>
import ('@/views/Behavior/camera'), import ('@/views/Behavior/camera'),
// component: UserManage // component: UserManage
},{
path: 'queueRecords',
name: 'queueRecords',
meta: {
permissionPath: 'queueRecords'
},
component: () =>
import ('@/views/Behavior/queueRecords'),
}, { }, {
path: 'clerkcamera', path: 'clerkcamera',
name: 'clerkcamera', name: 'clerkcamera',
......
<template>
<div class="behavior-more-option behavior-test-option">
<div class="opt-left">
<!-- 广场 -->
<div class="test-opt-mall test-camera">
<span class="labelName">{{$t('table.squareName')}}</span>
<el-select v-model="mallVal" class="mall-opt" filterable :placeholder="$t('pholder.shopSelect')" @change="chooseMall">
<el-option v-for="item in mallOpt" :label="item.name" :value="item.id" :key="item.value"></el-option>
</el-select>
</div>
<!-- 区域 -->
<div class="test-opt-mall test-camera">
<span class="labelName">{{$t('table.areaName')}}</span>
<el-select v-model="areaVal" class="mall-opt" filterable :placeholder="$t('pholder.areaSelect')" @change="chooseArea">
<el-option v-for="item in areaOpt" :label="item.name" :value="item.id" :key="item.value"></el-option>
</el-select>
</div>
<!-- 通道 -->
<div class="test-opt-mall test-camera">
<span class="labelName">{{$t('table.channelName')}}</span>
<el-select v-model="cashierChannelId" class="mall-opt" filterable :placeholder="$t('pholder.selectLane')" @change="chooseChannel">
<el-option value="" :label="$t('pholder.all')"></el-option>
<el-option v-for="item in channelOpt" :label="item.name" :value="item.id" :key="item.value"></el-option>
</el-select>
</div>
<!-- 人员类型 -->
<div class="test-opt-mall">
<span class="labelName">{{$t('table.personTypeName')}}</span>
<el-select class="manage-sel" v-model="personType" :placeholder="$t('asisTab.all') + $t('asisTab.manType')" @change="chooseType">
<el-option
v-for="item in allTypeList"
:key="item.id"
:label="item.name === 'all' ? $t('pholder.allType') : item.name"
:value="item.id"
></el-option>
</el-select>
</div>
<!-- 日期时间 -->
<div class="test-opt-time mr20">
<span class="labelName">{{$t('table.date')}}</span>
<el-date-picker
v-model="countDate"
type="datetimerange"
:range-separator="$t('dialog.to')"
:start-placeholder="$t('table.startTime')"
:end-placeholder="$t('table.endTime')">
</el-date-picker>
</div>
<!-- trackTime -->
<div class="test-opt-time mr20 timeHr">
<span class="labelName">trackTime</span>
<el-input-number v-model="trackTimeStart" :precision='0' :min=0 :controls='false'></el-input-number>
<el-input-number :controls='false' v-model="trackTimeEnd" :min=0 :precision='0'></el-input-number>
</div>
<!-- serviceTime -->
<div class="test-opt-time mr20 timeHr">
<span class="labelName">serviceTime</span>
<el-input-number v-model="serviceTimeStart" :precision='0' :min=0 :controls='false'></el-input-number>
<el-input-number :controls='false' v-model="serviceTimeEnd" :min=0 :precision='0'></el-input-number>
</div>
<!-- queueTime -->
<div class="test-opt-time mr20 timeHr">
<span class="labelName">queueTime</span>
<el-input-number v-model="queueTimeStart" :precision='0' :min=0 :controls='false'></el-input-number>
<el-input-number :controls='false' v-model="queueTimeEnd" :min=0 :precision='0'></el-input-number>
</div>
<div class="test-opt-time timeHr">
<el-button type="primary" class="primary-btn behavior-collapse-btn" size="small"
@click="clickHandle">{{$t('button.confirm')}}</el-button>
</div>
</div>
</div>
</template>
<script>
import * as Cookies from 'js-cookie'
import moment from 'moment'
export default {
props: {
params: {
type: Object,
default: () => {}
}
},
data() {
return {
mallName: '',
mallVal: '',
mallOpt: [],
areaVal:'',
areaOpt:[],
cashierChannelId:'',
channelOpt:[],
personType: null,
countDate:['',''],
trackTimeStart:0,
trackTimeEnd:100,
serviceTimeStart:0,
serviceTimeEnd:100,
queueTimeStart:0,
queueTimeEnd:100,
Params: {},
pageLimit: 45,
typeList: [],
accountId: this.$cookie.get('accountId'),
}
},
computed: {
allTypeList() {
let list = this.typeList.map(item => {
return item;
});
list.unshift({
name: "all",
id: null
});
return list;
},
},
async mounted() {
const storage = window.sessionStorage.valueOf()
this.typeList = []
let { dayDate } = this.createDate();
this.countDate = [moment().format('YYYY-MM-DD')+' 08:00:00',moment().format('YYYY-MM-DD')+' 23:00:00']
this.getMallList()
},
methods: {
// 获取人员类型
fetchPersonType() {
if (!this.mallVal) return false
this.personType = null
this.typeList = []
this.$api.management.personTypeList({
accountId: this.$cookie.get('accountId'),
mallId: this.mallVal,
// 是否有顾客店员基础类型
hasBaseType: 1,
}).then(res => {
const { code, data } = res.data
if (code === 200) {
this.typeList = data.reduce((arr, curr) => {
arr.push(curr)
return arr
}, [])
}
})
},
// 获取门店列表
getMallList() {
this.mallOpt = [];
this.mallVal = '';
this.mallName = '';
this.getCommonMalls().then(resolveData => {
let { mallData, localMallId, titleName, multiMallId } = resolveData;
this.mallOpt = mallData;
this.mallVal = localMallId;
this.mallName = titleName + ' ';
this.getAreaList()
this.fetchPersonType()
})
},
// 选择门店
async chooseMall() {
this.mallName = this.behaviorMallChange(this.mallVal, this.mallOpt)
this.getAreaList()
this.personType = null
this.fetchPersonType()
},
getAreaList(val) {
this.areaListData = [];
this.$api.queueManagementApi.getAreaList({
mallId: this.mallVal,
pageNum: 1,
pageSize: 999999,
}).then((res) => {
let result = res.data;
if (result.code == 200) {
if (result.data.list && result.data.list.length > 0) {
// this.floorImg = window._vionConfig.picUrl + result.data.list[0].pic;
this.areaOpt = result.data.list;
this.areaVal = result.data.list[0].id
this.getChannelList()
}
}
});
},
chooseArea(){
this.getChannelList()
},
getChannelList(){
this.$api.queueManagementApi
.getChannelList({
areaId: this.areaVal,
pageNum: 1,
pageSize: 9999999,
})
.then((res) => {
let result = res.data;
this.loading = false;
if (result.code == 200) {
this.channelOpt = result.data.list;
this.clickHandle()
}
});
},
chooseChannel() {
this.clickHandle()
},
chooseType() {
this.clickHandle()
},
chooseTime() {
this.clickHandle()
},
clickHandle() {
this.$emit('startLoading', 'camera');
// if((this.trackTimeStart>this.trackTimeStart) || (this.serviceTimeStart>this.serviceTimeEnd) || (this.queueTimeStart>this.queueTimeEnd)){
// return
// }
let _params = {
pageNum: 1,
pageSize: this.pageLimit,
cashierChannelId:this.cashierChannelId,
personType: typeof this.personType === 'number' ? this.personType : null,
countDateStart:moment(this.countDate[0]).format("YYYY-MM-DD HH:mm:ss"),
countDateEnd:moment(this.countDate[1]).format("YYYY-MM-DD HH:mm:ss"),
trackTimeStart:this.trackTimeStart,
trackTimeEnd:this.trackTimeEnd,
serviceTimeStart:this.serviceTimeStart,
serviceTimeEnd:this.serviceTimeEnd,
queueTimeStart:this.queueTimeStart,
queueTimeEnd:this.queueTimeEnd,
}
Object.keys(_params).forEach(item=>{
if (_params[item] === '' || _params[item] === undefined || _params[item] === null || _params[item] === 'null')
delete _params[item]
})
this.$emit('emitParams', _params);
},
}
}
</script>
<style scoped lang="less">
.test-opt-mall {
width: auto !important;
}
.labelName{
display: inline-block;
margin-right: 10px;
}
/deep/.el-date-editor .el-range-separator{
line-height: 22px !important;
}
/deep/.el-date-editor--datetimerange.el-input__inner{
width: 380px !important;
}
/deep/.el-input-number{
width: 85px;
line-height: 30px;
.el-input__inner{
height: 30px !important
}
}
.test-opt-time .date-time {
width: 150px !important;
}
.timeHr{
margin-top: 15px;
}
@media only screen and (max-width: 1680px) {
.test-opt-time .time-opt {
width: 264px;
}
}
@media only screen and (max-width: 1440px) {
.test-opt-time .time-opt {
width: 264px;
}
}
@media only screen and (max-width: 1366px) {
.test-opt-time .time-opt {
width: 264px;
}
}
@media only screen and (max-width: 1280px) {
.test-opt-time .time-opt {
width: 264px;
}
}
</style>
<template>
<div class="camera-wrapper">
<el-header>
<span class="behavior-title">{{$t('Behavior.capturecamera')}}</span>
</el-header>
<test-option
:person-type-list="personTypeList"
@emitParams="getPatams"
@startLoading="startLoad"
></test-option>
<div class="element-main behavior-main" v-loading="loading" style="margin-top: 160px">
<div class="behavior-table-page download-parent" :style="{ height: 'calc(100% - 1px)' }">
<div class="behavior-table" :style="{ height: 'calc(100% - 50px)', overflowY: 'auto' }">
<ul class="shotpic-wrapper" >
<li
v-if="tableData.length == 0"
style="text-align: center; margin: 10px 0; color: #909399;"
>{{ $t('echartsTitle.noData') }}</li>
<li class="shotpic-item" v-for="(item, index) in tableData" :key="index">
<div class="shotpic-img">
<img
:src="dealImg(item)"
@error="dealErrImg"
@click="showAllImg(item)"
:style="imgStyle"
/>
</div>
<div>
<p class="shotpic-info snap-info-wrapper">
<span
class="shotpic-text__left"
style="text-align: left"
>{{ personTypeFormatter(item.personType) }}</span>
</p>
<p class="shotpic-info snap-info-wrapper" style="text-align: left">
<span
class="shotpic-text__left"
style="text-align: left"
>trackTime: {{Math.floor(item.trackTime/60)}}min{{item.trackTime%60}}s</span>
</p>
<p class="shotpic-info snap-info-wrapper" style="text-align: left">
<el-tooltip class="item" effect="light" :content="item.counttime" placement="top">
<span class="shotpic-text">{{ item.counttime }}</span>
</el-tooltip>
</p>
</div>
</li>
</ul>
</div>
<div class="page-wrapper" style="margin-top: 5px">
<el-pagination
background
:current-page="currentPage"
:page-sizes="pageArr"
:page-size="pageSize"
@size-change="sizeChange"
@current-change="handleCurrentChange"
layout="sizes, prev, pager, next, slot, jumper"
:total="totalPage"
>
<span
class="slot-ele-total"
>{{$t('table.pageHead')}} {{ totalPage }} {{$t('table.pageTail')}}</span>
</el-pagination>
</div>
</div>
</div>
<el-dialog
:visible.sync="isShowAll"
v-if="isShowAll"
:close-on-click-modal="false"
class="manage-dialog"
:before-close="closeDialog"
width="1000"
:title="$t('button.showTrack')"
>
<div v-loading='canvasLoading' style="width: 100%;text-align: center;padding: 40px 0;height: 100%;">
<canvas id="cameraCanvas" class="xycanvas"></canvas>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="isShowAll = false" class="dialog-btn">{{ $t('dialog.close') }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import testOption from "./common/option/queueRecordsOption";
import defaultImg from "@/assets/img/behavior/default.png";
import femaleImg from "@/assets/img/behavior/female.png";
import maleImg from "@/assets/img/behavior/male.png";
import { param, cleanObject, param2Obj, cleanArray } from "@/utils";
import _ from 'underscore';
export default {
components: {
"test-option": testOption
},
data() {
return {
canvasLoading:true,
cmeraImage:'',
canvasDevice: null, //canvas实例
ctxDevice:null,
tableData: [],
currentPage: 1,
pageArr: [45, 90, 135],
pageSize: 45,
totalPage: 0,
repTitle: "",
loading: true,
tableHeight: 0,
emitData: {},
shotList: [],
isShowAll: false,
personData: {},
lang: null,
imgStyle: {},
dialogImgStyle: {},
showAllModel: true,
isNewCustomer: true,
clickUnid: "",
snapLoading: true,
loadText: "",
personTypeList: [],
picType: 'body',
clientHeight: (document.documentElement && document.documentElement.clientHeight) ? document.documentElement.clientHeight : document.body.clientHeight
};
},
created() {
this.tableHeight = this.calcTabMaxHeight();
window.addEventListener("resize", () => {
this.tableHeight = this.calcTabMaxHeight();
});
this.lang = window.localStorage.getItem("lang");
},
mounted() {
this.getPersonTypeList()
},
methods: {
calcTabMaxHeight() {
return (3 / 5) * window.innerHeight + "px";
},
getPersonTypeList() {
this.personTypeList = []
return new Promise(resolve => {
this.$api.management.personTypeList().then(res => {
const { code, data } = res.data
if (code === 200) {
this.personTypeList = data.reduce((arr, curr, index) => {
arr.push(curr)
return arr
}, [])
resolve()
}
})
})
},
getTableData() {
this.loading = true;
this.$api.queueManagementApi.getRecognition(this.emitData).then(res => {
let result = res.data;
this.loading = false;
if (result.code == 200) {
this.totalPage = result.data.total;
this.tableData = result.data.list;
} else {
this.$message({
showClose: true,
type: "error",
message: this.$t("message.captureFailed") + result.msg
});
}
})
},
dealDate(counttime, fmt = "$1$2$3") {
return counttime.replace(/^(\d{4})-(\d{2})-(\d{2}).*/, fmt);
},
dealImagePath(item) {
const { dealDate } = this;
let imageUrl = "";
if (this.picType == 'body') {
if(item.bodyPic) {
imageUrl = item.bodyPath && item.bodyPic
? item.bodyPath + item.bodyPic
: `body/${dealDate(item.counttime)}/${item.channelSerialnum}/${item.bodyPic}`
}
} else {
if(item.facePic) {
imageUrl = item.facePath && item.facePic
? item.facePath + item.facePic
: `/face/${dealDate(item.counttime)}/${item.channelSerialnum}/${item.facePic}`
}
}
return imageUrl;
},
dealImg(row) {
const imageUrl = this.dealImagePath(row);
let img_path = "";
img_path = window._vionConfig.picUrl + "/picture/" + imageUrl;
return img_path;
},
personTypeFormatter(personType, visibleNew) {
const matched = this.personTypeList.find(item => item.id === personType)
return matched && matched.name || (visibleNew ? this.$t('table.newCustomer') : this.$t("dictionary.custom"))
},
showAllImg(item) {
let picUrl = window._vionConfig.picUrl + "/snapshot/real/" + item.channelSerialnum + ".jpg?t=" + Date.parse(new Date()) / 1000;
this.isShowAll = true;
this.canvasLoading = true;
this.$api.queueManagementApi.getTrajectory({
personId:item.personUnid,
countTime:item.counttime
}).then(res => {
let result = res.data;
this.canvasDevice = document.getElementById("cameraCanvas");
this.ctxDevice = this.canvasDevice.getContext("2d");
this.canvasDevice.width = 1000;
this.canvasDevice.height = 500;
this.cmeraImage = new Image();
this.cmeraImage.src =picUrl;
this.cmeraImage.onload = () => {
this.drawCirlceDevice(result);
};
})
},
drawCirlceDevice(result){
this.ctxDevice.clearRect(0, 0, this.canvasDevice.width, this.canvasDevice.height);
this.ctxDevice.drawImage(this.cmeraImage, 0, 0, this.canvasDevice.width, this.canvasDevice.height);
let dataList = result.data;
this.canvasLoading = false
dataList.forEach((item) => {
this.ctxDevice.fillStyle = "red";
this.ctxDevice.beginPath();
this.ctxDevice.arc(
(item.x / 1920) * this.canvasDevice.width,
(item.y / 1080) * this.canvasDevice.height,
5,
0,
2 * Math.PI
);
this.ctxDevice.fill();
});
},
closeDialog() {
this.isShowAll = false;
},
dealErrImg(e) {
let ev = e || window.event;
ev.target.src = defaultImg;
},
handleSizeChange(val) {
this.emitData.pageSize = val;
this.pageSize = val;
this.loading = true;
this.getTableData();
},
handleCurrentChange(val) {
if (this.currentPage != val) {
this.emitData.page = val;
this.currentPage = val;
} else {
this.emitData.page = val;
this.currentPage = val;
// this.loading = true;
}
this.getTableData();
},
sizeChange(val) {
this.pageSize = val;
this.emitData["pageSize"] = val;
this.getTableData();
},
startLoad(isCurrentPage) {
if (isCurrentPage === "camera") {
this.loading = true;
}
},
getPatams(param) {
this.pageSize = param.pageSize;
this.currentPage = param.page;
this.emitData = Object.assign('',param)
this.getTableData();
},
}
};
</script>
<style scoped lang="less">
.manage-dialog{
/deep/.el-dialog{
width: 1200px;
height: 700px;
}
}
.shotpic-img img{
height: 250px !important;
}
.shotpic-text__left{
width: auto;
max-width: 140px;
}
.camera-wrapper {
width: 100%;
height: calc(100% - 175px);
}
.slot-ele-total {
margin-right: 10px;
font-weight: 400;
color: #606266;
float: left;
}
.shotpic-info.snap-info-wrapper {
margin-top: 2px;
margin-bottom: 2px;
}
.history-snap-item:after,
.history-snap-item::before {
content: "";
display: table;
}
.history-snap-item:after {
clear: both;
}
.snap-title {
color: #333;
font-size: 18px;
padding: 0 6px 0;
margin-bottom: 6px;
margin-left: 3px;
border-left: 4px solid #3bb8ff;
box-sizing: border-box;
}
</style>
...@@ -240,7 +240,7 @@ ...@@ -240,7 +240,7 @@
<!-- <p class="cell_unit">Customers/hour</p> --> <!-- <p class="cell_unit">Customers/hour</p> -->
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="throughput" align="center"> <el-table-column prop="abandonQueue" align="center">
<template slot="header"> <template slot="header">
<p class="cell_title">Customers Abandoned</p> <p class="cell_title">Customers Abandoned</p>
<p class="cell_title">the Queue</p> <p class="cell_title">the Queue</p>
...@@ -733,9 +733,11 @@ ...@@ -733,9 +733,11 @@
this.loading = false; this.loading = false;
let red = data.red ? data.red : []; let red = data.red ? data.red : [];
let green = data.green ? data.green : []; let green = data.green ? data.green : [];
let blue = data.blue ? data.blue : [];
this.dataList = [ this.dataList = [
...red.map((item) => ({ ...item, status: 1 })), ...red.map((item) => ({ ...item, status: 1 })),
...green.map((item) => ({ ...item, status: 0 })), ...green.map((item) => ({ ...item, status: 0 })),
...blue.map((item) => ({ ...item, status: 2 })),
]; ];
this.drawCirlce(); this.drawCirlce();
} }
...@@ -756,6 +758,8 @@ ...@@ -756,6 +758,8 @@
this.dataList.forEach((item) => { this.dataList.forEach((item) => {
if (item.status == 1) { if (item.status == 1) {
this.ctx.fillStyle = "red"; this.ctx.fillStyle = "red";
}else if(item.status == 2){
this.ctx.fillStyle = "blue";
} else { } else {
this.ctx.fillStyle = "green"; this.ctx.fillStyle = "green";
} }
......
...@@ -235,9 +235,11 @@ ...@@ -235,9 +235,11 @@
this.loading = false; this.loading = false;
let red = data.red ? data.red : []; let red = data.red ? data.red : [];
let green = data.green ? data.green : []; let green = data.green ? data.green : [];
let blue = data.blue ? data.blue : [];
this.dataList = [ this.dataList = [
...red.map((item) => ({ ...item, status: 1 })), ...red.map((item) => ({ ...item, status: 1 })),
...green.map((item) => ({ ...item, status: 0 })), ...green.map((item) => ({ ...item, status: 0 })),
...blue.map((item) => ({ ...item, status: 2 })),
]; ];
this.drawCirlce(); this.drawCirlce();
this.drawCirlceDevice() this.drawCirlceDevice()
...@@ -251,6 +253,8 @@ ...@@ -251,6 +253,8 @@
this.dataList.forEach((item) => { this.dataList.forEach((item) => {
if (item.status == 1) { if (item.status == 1) {
this.ctx.fillStyle = "red"; this.ctx.fillStyle = "red";
}else if(item.status == 2){
this.ctx.fillStyle = "blue";
} else { } else {
this.ctx.fillStyle = "green"; this.ctx.fillStyle = "green";
} }
...@@ -272,6 +276,8 @@ ...@@ -272,6 +276,8 @@
this.dataList.forEach((item) => { this.dataList.forEach((item) => {
if (item.status == 1) { if (item.status == 1) {
this.ctxDevice.fillStyle = "red"; this.ctxDevice.fillStyle = "red";
}else if (item.status == 2) {
this.ctxDevice.fillStyle = "blue";
} else { } else {
this.ctxDevice.fillStyle = "green"; this.ctxDevice.fillStyle = "green";
} }
......
...@@ -126,7 +126,7 @@ ...@@ -126,7 +126,7 @@
<!-- <p class="cell_unit">Customers/hour</p> --> <!-- <p class="cell_unit">Customers/hour</p> -->
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="throughput" align="center" width="180"> <el-table-column prop="abandonQueue" align="center" width="180">
<template slot="header"> <template slot="header">
<p class="cell_title">Customers Abandoned</p> <p class="cell_title">Customers Abandoned</p>
<p class="cell_title">the Queue</p> <p class="cell_title">the Queue</p>
...@@ -199,7 +199,7 @@ ...@@ -199,7 +199,7 @@
<!-- <p class="cell_unit">Customers/hour</p> --> <!-- <p class="cell_unit">Customers/hour</p> -->
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="throughput" align="center"> <el-table-column prop="abandonQueue" align="center">
<template slot="header"> <template slot="header">
<p class="cell_title">Customers Abandoned</p> <p class="cell_title">Customers Abandoned</p>
<p class="cell_title">the Queue</p> <p class="cell_title">the Queue</p>
...@@ -598,9 +598,11 @@ export default { ...@@ -598,9 +598,11 @@ export default {
if (code == 200) { if (code == 200) {
let red = data.red ? data.red : []; let red = data.red ? data.red : [];
let green = data.green ? data.green : []; let green = data.green ? data.green : [];
let blue = data.blue ? data.blue : [];
this.dataList = [ this.dataList = [
...red.map((item) => ({ ...item, status: 1 })), ...red.map((item) => ({ ...item, status: 1 })),
...green.map((item) => ({ ...item, status: 0 })), ...green.map((item) => ({ ...item, status: 0 })),
...blue.map((item) => ({ ...item, status: 2 })),
]; ];
this.drawCirlce(); this.drawCirlce();
...@@ -727,6 +729,8 @@ export default { ...@@ -727,6 +729,8 @@ export default {
this.dataList.forEach((item) => { this.dataList.forEach((item) => {
if (item.status == 1) { if (item.status == 1) {
this.ctx.fillStyle = "red"; this.ctx.fillStyle = "red";
}else if(item.status == 2){
this.ctx.fillStyle = "blue";
} else { } else {
this.ctx.fillStyle = "green"; this.ctx.fillStyle = "green";
} }
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!