完成统计模块

加入会议室服务绑定
加入会议室筛选
This commit is contained in:
471615499@qq.com 2024-10-04 17:25:18 +08:00
parent a2c1482afe
commit 1259f068a8
7 changed files with 843 additions and 12 deletions

View File

@ -1,6 +1,9 @@
<h1 align="center">ICS Ant</h1> <h1 align="center">ICS Ant</h1>
Overview Overview
## 前端文档地址:
https://1x.antdv.com/components/
---- ----
项目下载和运行 项目下载和运行
---- ----

View File

@ -9,18 +9,21 @@
<script> <script>
import { domTitle, setDocumentTitle } from '@/utils/domUtil' import { domTitle, setDocumentTitle } from '@/utils/domUtil'
import { i18nRender } from '@/locales' import { i18nRender } from '@/locales'
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
export default { export default {
data () { data () {
return { return {
locale: zhCN, // ant
} }
}, },
computed: { computed: {
locale () { //
// // locale () {
const { title } = this.$route.meta // //
title && (setDocumentTitle(`${i18nRender(title)} - ${domTitle}`)) // const { title } = this.$route.meta
return this.$i18n.getLocaleMessage(this.$store.getters.lang).antLocale // title && (setDocumentTitle(`${i18nRender(title)} - ${domTitle}`))
} // return this.$i18n.getLocaleMessage(this.$store.getters.lang).antLocale
// }
} }
} }
</script> </script>

View File

@ -0,0 +1,93 @@
import { axios } from '@/utils/request'
/**
* 会议室统计数据
* /admin/ms/roomStats
* @参数 day 格式2024-09-30
* @返回
* alreadyBooking, 已预约会议室数量
* noBooking,未预约会议室数量
* going,开会中会议室数量
* free,空闲中会议室数量
* @param parameter
* @returns {AxiosPromise}
*/
export function getNum(parameter) {
return axios({
url: '/admin/ms/roomStats',
method: 'post',
params: parameter
})
}
/**
* 按日历统计数据
* /admin/ms/calendar
* @参数 month 月份格式2024-09
* @返回,月份的每一天 预约记录
* @param parameter
* @returns {AxiosPromise}
*/
export function getCalendar(parameter) {
return axios({
url: '/admin/ms/calendar',
method: 'post',
params: parameter
})
}
/**
* 按照会议状态统计
* /admin/ms/meetingStats
* 无参
* @返回
* * wait, 待开始会议数量
* * going,进行中会议数量
* * closed,已结束会议数量
* @param parameter
* @returns {AxiosPromise}
*/
export function getStatus(parameter) {
return axios({
url: '/admin/ms/meetingStats',
method: 'post',
params: parameter
})
}
/**
* 会议待办 前10条
* /admin/ms/meetingAudit
* 无参
* @param parameter
* @returns {AxiosPromise}
*/
export function getWait(parameter) {
return axios({
url: '/admin/ms/meetingAudit',
method: 'post',
params: parameter
})
}
/**
* 图表统计数据
*
*
* @参数 startDate 统计数据开始时间,日期格式示例2024-08-23
* @参数 endDate 统计数据结束时间,日期格式示例2024-09-23
* @返回
* roomRank会议室使用排名
* serve服务情况
* roomType会议室形式统计
* orgMeeting部门开会情况取前10个部门
* everyDay开会情况--按天取前20天
*/
export function getChartData(parameter) {
return axios({
url: '/admin/ms/chartStats',
method: 'post',
params: parameter
})
}

View File

@ -15,7 +15,7 @@ export function getMeetingDict(parameter) {
export function getRoomContentList(parameter) { export function getRoomContentList(parameter) {
return axios({ return axios({
// url: api.roomContent + '/list', // url: api.roomContent + '/list',
url: '/admin/meetingRoom/list?pageNum=', url: '/admin/meetingRoom/list',
method: 'post', method: 'post',
params: parameter params: parameter
}) })

View File

@ -0,0 +1,665 @@
<template>
<div class='container'>
<a-card title='会议室管理'>
<a slot='extra'>
<a-date-picker @change='onChangeNumCount' :default-value='nowDate' />
</a>
<a-row>
<a-col :span='6'>
<div class='eachNum'>
<div class='icon' style='background-color: #58A3F7;'>
<a-icon type='ordered-list' style='font-size: 50px;color: #FFF' />
</div>
<div class='des'>
<div class='title'>
已预约会议室
</div>
<div class='num'>
{{ roomNum.alreadyBooking }}
</div>
</div>
</div>
</a-col>
<a-col :span='6'>
<div class='eachNum'>
<div class='icon' style='background-color: #52C1F5;'>
<a-icon type='unordered-list' style='font-size: 50px;color: #FFF' />
</div>
<div class='des'>
<div class='title'>
未预约会议室
</div>
<div class='num'>
{{ roomNum.noBooking }}
</div>
</div>
</div>
</a-col>
<a-col :span='6'>
<div class='eachNum'>
<div class='icon' style='background-color: #4BCED0;'>
<a-icon type='team' style='font-size: 50px;color: #FFF' />
</div>
<div class='des'>
<div class='title'>
开会中会议室
</div>
<div class='num'>
{{ roomNum.going }}
</div>
</div>
</div>
</a-col>
<a-col :span='6'>
<div class='eachNum'>
<div class='icon' style='background-color: #FB6260;'>
<a-icon type='profile' style='font-size: 50px;color: #FFF' />
</div>
<div class='des'>
<div class='title'>
空闲中会议室
</div>
<div class='num'>
{{ roomNum.free }}
</div>
</div>
</div>
</a-col>
</a-row>
</a-card>
<a-row>
<a-col :span='18'>
<a-card title='会议日历'>
<a-calendar :header-render='headerRender'>
<ul slot='dateCellRender' slot-scope='value' class='c_meeting' style='padding: 0'>
<a-popover :title='item.title' v-for='item in getListData(value)'>
<template slot='content'>
<div>预约部门{{ item.org }}</div>
<div>会议时间{{ item.time }}</div>
</template>
<li>
{{ item.time }}
</li>
</a-popover>
</ul>
</a-calendar>
</a-card>
</a-col>
<a-col :span='6'>
<a-card title='会议状态'>
<a-row>
<a-col :span='18'>
<a-icon type='message' theme='filled' style='font-size: 24px;color: #E6A23C' />
<label style='position: relative;top: -1px;left: 10px;font-size: 20px'>
进行中
</label>
</a-col>
<a-col :span='6'>
<div style='text-align: right; font-weight: 700;font-size: 20px; position: relative;top: -1px;'>
{{ statusNum.going }}
</div>
</a-col>
</a-row>
<a-row style='margin-top: 15px'>
<a-col :span='18'>
<a-icon type='flag' theme='filled' style='font-size: 24px;color: #409EFF' />
<label style='position: relative;top: -1px;left: 10px;font-size: 20px'>
未开始
</label>
</a-col>
<a-col :span='6'>
<div style='text-align: right; font-weight: 700;font-size: 20px; position: relative;top: -1px;'>
{{ statusNum.wait }}
</div>
</a-col>
</a-row>
<a-row style='margin-top: 15px'>
<a-col :span='18'>
<a-icon type='carry-out' theme='filled' style='font-size: 24px;color: #919399' />
<label style='position: relative;top: -1px;left: 10px;font-size: 20px'>
已结束
</label>
</a-col>
<a-col :span='6'>
<div style='text-align: right; font-weight: 700;font-size: 20px; position: relative;top: -1px;'>
{{ statusNum.closed }}
</div>
</a-col>
</a-row>
</a-card>
<a-card title='任务代办(小程序审核)'>
<a-list item-layout='horizontal' :data-source='waitList'>
<a-list-item slot='renderItem' slot-scope='item, index'>
<!-- <a slot='actions'>审核</a>-->
<a-list-item-meta
:description="item.time + '' +item.org + ''"
>
<a slot='title'>{{ item.title }}</a>
</a-list-item-meta>
</a-list-item>
</a-list>
</a-card>
</a-col>
</a-row>
<a-card title='数据统计'>
<a slot='extra'>
<a-range-picker @change='onChangeChartCount' :default-value='[nowMonthStart,nowMonthEnd]' />
</a>
</a-card>
<a-row>
<a-col :span='8'>
<a-card title='会议室使用排名'>
<div id='chartUse' style='width: 100%; height: 500px'></div>
</a-card>
</a-col>
<a-col :span='16'>
<a-row>
<a-col :span='12'>
<a-card title='服务情况'>
<div id='chartServe' style='width: 100%; height: 200px'></div>
</a-card>
</a-col>
<a-col :span='12'>
<a-card title='形式统计'>
<div id='chartType' style='width: 100%; height: 200px'></div>
</a-card>
</a-col>
</a-row>
<a-row>
<a-col :span='24'>
<a-card title='部门开会情况'>
<div id='chartDep' style='width: 100%; height: 194px'></div>
</a-card>
</a-col>
</a-row>
</a-col>
</a-row>
<a-row>
<a-col :span='24'>
<a-card title='开会情况'>
<div id='chartMeeting' style='width: 100%; height: 300px'></div>
</a-card>
</a-col>
</a-row>
</div>
</template>
<style>
.eachNum {
margin-left: 50px;
}
.eachNum .icon {
width: 90px;
height: 90px;
float: left;
text-align: center;
border-radius: 10px;
padding-top: 18px;
}
.eachNum .des {
margin-left: 110px;
}
.eachNum .title {
width: 100%;
height: 45px;
line-height: 45px;
font-size: 20px;
color: #9D9D9D;
}
.eachNum .num {
width: 100%;
height: 45px;
line-height: 25px;
font-size: 30px;
color: #666666;
font-weight: 700;
}
.c_meeting li {
background-color: #F0F2F6;
margin-bottom: 5px;
padding: 2px 5px;
font-size: 0.8rem;
}
</style>
<script>
import * as echarts from 'echarts'
import moment from 'moment'
import { STable } from '@/components'
import {
getNum,
getCalendar,
getStatus,
getWait,
getChartData
} from '@/api/admin/meeting/count'
export default {
name: 'MeetingCount',
components: {
STable
},
data() {
return {
nowDate: moment().format('YYYY-MM-DD'), //
nowMonth: moment().format('YYYY-MM'),
nowMonthStart: moment().startOf('months').format('YYYY-MM-DD'), //
nowMonthEnd: moment().endOf('months').format('YYYY-MM-DD'), //
roomNum: {
alreadyBooking: 0, //
noBooking: 0, //
going: 0, //
free: 0 //
},
calendarData: {}, //
statusNum: {
going: 0,
closed: 0,
wait: 0
}, //
waitList: [] //
}
},
mounted() {
this.getRoomNum()
this.getCalendarCount()
this.getStatusCount()
this.getWaitList()
this.getChartCount()
// this.drawChart()
},
methods: {
/**
* 改变会议室管理统计日期
* @param date
* @param dateString
*/
onChangeNumCount(date, dateString) {
this.nowDate = dateString
this.getRoomNum()
},
onChangeCalendar(date, dateString) {
console.log(date)
console.log(dateString)
},
/**
* 获取第一行数据统计
*/
getRoomNum() {
getNum({
day: this.nowDate
}).then(res => {
const data = res.data
this.roomNum = {
alreadyBooking: data.alreadyBooking, //
noBooking: data.noBooking, //
going: data.going, //
free: data.free //
}
})
},
/**
* 日历选择器顶部改写方法
* @param value
* @param type
* @param onChange
* @param onTypeChange
* @returns {JSX.Element}
*/
headerRender({ value, type, onChange, onTypeChange }) {
const nowMonth = this.nowMonth
return (
<div slot='extra' style={{ textAlign: 'center', position: 'relative', top: '-88px' }}>
<div style={{ position: 'absolute', right: '15px', top: '20px' }}>
<a-month-picker onChange={(date, dateString) => {
this.onChangeCalendar(date, dateString, value, onChange)
}} defaultValue={nowMonth} />
</div>
</div>
)
},
/**
*
* 改变会议日历日期事件先获取数据再改写日期
* @param date
* @param dateString
* @param value
* @param onChange
*/
onChangeCalendar(date, dateString, value, onChange) {
let year = date.year()
let month = date.month()
this.nowMonth = dateString
const newValue = value.clone()
this.getCalendarCount()
newValue.year(year)
newValue.month(month)
onChange(newValue)
},
/**
* 获取日历统计数据方法
*/
getCalendarCount() {
let calendarData = {}
getCalendar({
month: this.nowMonth
}).then(res => {
let data = res.data
for (let key in data) {
let date = key
let meeting = data[key]
if (meeting.length > 0) {
//
let newMeeting = []
for (let each in meeting) {
let eachM = meeting[each]
let timeArr = eachM.time.split(' ')
eachM.time = timeArr[1]
newMeeting.push(eachM)
}
calendarData[date] = newMeeting
}
}
this.calendarData = calendarData
})
},
/**
* 比对日历日期返回当日会议方法
* @param value
* @returns {*[]}
*/
getListData(value) {
let nowDate = moment(value).format('YYYY-MM-DD')
let listData
let calendarData = this.calendarData
if (calendarData[nowDate] && calendarData[nowDate].length > 1) {
listData = calendarData[nowDate]
}
return listData || []
},
/**
* 获取状态统计
*/
getStatusCount() {
getStatus().then(res => {
this.statusNum = {
going: res.data.going,
closed: res.data.closed,
wait: res.data.wait
}
})
},
/**
* 获取待办列表
*/
getWaitList() {
getWait().then(res => {
let data = res.data
if (data.length > 6) {
// 610
data = data.splice(0, 6)
}
this.waitList = data
})
},
/**
* 统计图顶部时间范围选择变化方法
* @param date
* @param dateStringArr
* @param dateString
*/
onChangeChartCount(date, dateStringArr, dateString) {
console.log(dateStringArr)
this.nowMonthStart = dateStringArr[0]
this.nowMonthEnd = dateStringArr[1]
this.getChartCount()
},
/**
* 获取统计图数据
*/
getChartCount() {
getChartData({
startDate: this.nowMonthStart,
endDate: this.nowMonthEnd
}).then(res => {
this.drawChart(res.data)
})
},
drawChart(data) {
// 使
let chartUse = echarts.init(document.getElementById('chartUse'))
let useData = data.roomRank
let chartUseOp = {
yAxis: {
type: 'category',
data: useData.y
},
xAxis: {
type: 'value'
},
grid: {
top: '0',
left: '2%',
right: '2%',
bottom: '1%',
containLabel: true
},
series: [
{
data: useData.total,
type: 'bar',
label: {
show: true, //
position: 'inside' //
},
barWidth: '20px',
itemStyle: {
color: '#50B5FF', //
borderRadius: [0, 5, 5, 0] //
}
}
]
}
chartUse.setOption(chartUseOp)
//
let chartServe = echarts.init(document.getElementById('chartServe'))
let serveData = data.serve
let serveNumAll = 0
for (let key in serveData) {
let eachNum = serveData[key].value
serveNumAll = serveNumAll + eachNum
}
let chartServeOp = {
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical', //
right: 10, // 10px
top: 'bottom' //
}, //
grid: {
top: '0',
left: '2%',
right: '2%',
bottom: '1%',
containLabel: true
},
graphic: {
type: 'text',
left: 'center',
top: 'center',
style: {
text: serveNumAll, //
textAlign: 'center',
fill: '#000', //
fontSize: 20
}
},
series: [
{
name: '服务情况',
type: 'pie',
radius: ['70%', '100%'],
avoidLabelOverlap: false,
label: {
show: true
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
data: serveData
}
]
}
chartServe.setOption(chartServeOp)
//
let chartType = echarts.init(document.getElementById('chartType'))
let typeData = data.roomType
let typeNumAll = 0
for (let key in typeData) {
let eachNum = typeData[key].value
typeNumAll = typeNumAll + eachNum
}
let chartTypeOp = {
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical', //
right: 10, // 10px
top: 'bottom' //
}, //
grid: {
top: '0',
left: '2%',
right: '2%',
bottom: '1%',
containLabel: true
},
graphic: {
type: 'text',
left: 'center',
top: 'center',
style: {
text: typeNumAll, //
textAlign: 'center',
fill: '#000', //
fontSize: 20
}
},
series: [
{
name: '会议室形式',
type: 'pie',
radius: ['70%', '100%'],
avoidLabelOverlap: false,
label: {
show: true
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
data: typeData
// data: [
// { value: 1048, name: 'U' },
// { value: 735, name: '' },
// { value: 580, name: '' },
// ]
}
]
}
chartType.setOption(chartTypeOp)
//
let chartDep = echarts.init(document.getElementById('chartDep'))
let depData = data.orgMeeting
let chartDepOp = {
xAxis: {
type: 'category',
data: depData.x
},
yAxis: {
type: 'value'
},
grid: {
top: '3%',
left: '2%',
right: '2%',
bottom: '0%',
containLabel: true
},
series: [
{
data: depData.total,
label: {
show: true, //
position: 'inside' //
},
type: 'bar',
barWidth: '20px',
itemStyle: {
color: '#FFEC6D', //
}
}
]
}
chartDep.setOption(chartDepOp)
//
let chartMeeting = echarts.init(document.getElementById('chartMeeting'))
let meetingData = data.everyDay
let chartMeetingOp = {
xAxis: {
type: 'category',
boundaryGap: false,
data: meetingData.x
},
yAxis: {
type: 'value'
},
grid: {
top: '3%',
left: '2%',
right: '2%',
bottom: '0%',
containLabel: true
},
series: [
{
data: meetingData.total,
type: 'line',
label: {
show: true, //
position: 'top' //
},
itemStyle: {
color: new echarts.graphic.LinearGradient(
0, 0, 0, 1, // (x1, y1) (x2, y2)
[
{offset: 0, color: '#28A9FF'}, // 0%
{offset: 1, color: '#C8E8FE'} // 100%
]
)
},
areaStyle: {}
}
]
}
chartMeeting.setOption(chartMeetingOp)
}
}
}
</script>

View File

@ -48,7 +48,7 @@
41-50 41-50
</a-select-option> </a-select-option>
<a-select-option value='6'> <a-select-option value='6'>
50-100 51-100
</a-select-option> </a-select-option>
<a-select-option value='7'> <a-select-option value='7'>
100以上 100以上
@ -231,11 +231,34 @@ export default {
], ],
// Promise // Promise
loadData: (parameter) => { loadData: (parameter) => {
let minPerNum = 0
let maxPerNum = 1000
let perNumValue = this.queryParam.capacityNum
if (perNumValue == 6) {
// 51-100
maxPerNum = 100
minPerNum = 51
} else if (perNumValue == 7) {
// 100
minPerNum = 101
maxPerNum = 1000
} else if (perNumValue == '') {
//
minPerNum = 1
maxPerNum = 1000
} else {
//
// perNumValue * 10 - 9 ~ perNumValue * 10
maxPerNum = parseInt(perNumValue) * 10
minPerNum = maxPerNum - 9
}
const param = { const param = {
name: this.queryParam.meetingName, // name: this.queryParam.meetingName, //
floor: this.queryParam.typeName, // floor: this.queryParam.typeName, //
typeName: this.queryParam.shape, // typeName: this.queryParam.shape, //
capacityNum: this.queryParam.capacityNum // // capacityNum: this.queryParam.capacityNum //
min: minPerNum, //
max: maxPerNum, //
} }
// if (param.typeName === '') { // if (param.typeName === '') {
// delete param.typeName // delete param.typeName

View File

@ -58,7 +58,7 @@
<a-col :span='10'> <a-col :span='10'>
<a-form-item label='会议室面积' :labelCol='labelCol' :wrapperCol='wrapperCol'> <a-form-item label='会议室面积' :labelCol='labelCol' :wrapperCol='wrapperCol'>
<a-input placeholder='会议室面积' <a-input placeholder='会议室面积'
v-decorator="['area',{rules: [{ required: true, message: '请输入会议室面积' }]}]" /> v-decorator="['area']" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span='10'> <a-col :span='10'>
@ -72,7 +72,7 @@
<a-row> <a-row>
<a-col :span='10'> <a-col :span='10'>
<a-form-item label='会议室设备' :labelCol='labelCol' :wrapperCol='wrapperCol'> <a-form-item label='会议室设备' :labelCol='labelCol' :wrapperCol='wrapperCol'>
<a-checkbox-group :defaultValue='defaultCheckedList' <a-checkbox-group
v-decorator="['device',{rules: [{ required: true, message: '请选择会议室设备' }]}]"> v-decorator="['device',{rules: [{ required: true, message: '请选择会议室设备' }]}]">
<a-checkbox v-for='item in deviceList' :value='item.value'> <a-checkbox v-for='item in deviceList' :value='item.value'>
{{ item.text }} {{ item.text }}
@ -80,6 +80,16 @@
</a-checkbox-group> </a-checkbox-group>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span='10'>
<a-form-item label='会议室服务' :labelCol='labelCol' :wrapperCol='wrapperCol'>
<a-checkbox-group
v-decorator="['service',{rules: [{ required: true, message: '请选择会议室服务' }]}]">
<a-checkbox v-for='item in serviceList' :value='item.value'>
{{ item.text }}
</a-checkbox>
</a-checkbox-group>
</a-form-item>
</a-col>
<a-col :span='10'> <a-col :span='10'>
<a-form-item label='是否启用' :labelCol='labelCol' :wrapperCol='wrapperCol'> <a-form-item label='是否启用' :labelCol='labelCol' :wrapperCol='wrapperCol'>
<a-select v-decorator="['enable',{rules: [{ required: true, message: '请选择是否启用'}]}]" <a-select v-decorator="['enable',{rules: [{ required: true, message: '请选择是否启用'}]}]"
@ -175,9 +185,11 @@ export default {
typeList: [], typeList: [],
shapeList: [], shapeList: [],
deviceList: [], deviceList: [],
serviceList: [],
typeMap: {}, typeMap: {},
shapeMap: {}, shapeMap: {},
deviceMap: {}, deviceMap: {},
serviceMap: {},
// tenantList: [], // // tenantList: [], //
// parkList: [], // // parkList: [], //
// buildingList: [], // // buildingList: [], //
@ -243,6 +255,19 @@ export default {
this.deviceMap[keys[0]] = eachObj[keys[0]] this.deviceMap[keys[0]] = eachObj[keys[0]]
} }
this.deviceList = _deviceList this.deviceList = _deviceList
//
let _serviceList = []
for (let key in dataObj.services) {
let eachObj = dataObj.services[key]
const keys = Object.keys(eachObj)
_serviceList.push({
text: keys[0],
value: eachObj[keys[0]]
})
this.serviceMap[keys[0]] = eachObj[keys[0]]
}
this.serviceList = _serviceList
}) })
}, },
// //
@ -312,6 +337,11 @@ export default {
} }
res.room.device = deviceArr res.room.device = deviceArr
} }
let serviceStr = res.room.ext1
if (serviceStr && serviceStr != '') {
let serviceArr = serviceStr.split(',')
res.room.service = serviceArr
}
this.mdl = Object.assign(this.mdl, res.room) this.mdl = Object.assign(this.mdl, res.room)
this.visible = true this.visible = true
@ -333,7 +363,8 @@ export default {
'typeName', 'typeName',
'content', 'content',
'area', 'area',
'roomNum' 'roomNum',
'service'
) )
) )
}) })
@ -409,6 +440,19 @@ export default {
deviceStr += '#' + eachObj + ' ' deviceStr += '#' + eachObj + ' '
} }
values.device = deviceStr values.device = deviceStr
let serviceArr = values.service
let serviceStr = ''
for (let key in serviceArr) {
let eachObj = serviceArr[key]
serviceStr += eachObj + ','
}
// ,
if (serviceStr != '') {
serviceStr = serviceStr.substring(0, serviceStr.length - 1)
}
// ext1
values.ext1 = serviceStr
values.floorId = this.typeMap[values.floor] values.floorId = this.typeMap[values.floor]
values.typeId = this.shapeMap[values.typeName] values.typeId = this.shapeMap[values.typeName]
saveRoomContent(values, files) saveRoomContent(values, files)