Bläddra i källkod

质量指标看板

cfort 2 år sedan
förälder
incheckning
f0b274a080

+ 127 - 0
src/views/system/jbdHome/qualityTarget/components/container.vue

@@ -0,0 +1,127 @@
+<template>
+    <div :class="$style.container">
+        <template v-for="(row, rowIndex) in rowData">
+            <dv-decoration-10 v-if="rowIndex === 1" :key="`lineOne${rowIndex}`" />
+            <div :key="`row${rowIndex}`" :class="$style.row" :style="`width: ${row.length / 4 * 100}%;`">
+                <template v-for="(item, index) in row">
+                    <div :key="`${rowIndex * 4 + index}`" :class="$style.column" :style="`width: ${1 / row.length * 100}%;`">
+                        <div :id="`card${rowIndex * 4 + index}`" />
+                    </div>
+                    <dv-decoration-2
+                        v-if="index !== row.length - 1"
+                        :key="`line${rowIndex * 4 + index}`"
+                        :reverse="true"
+                        :dur="4 + index * 2"
+                    />
+                </template>
+            </div>
+            <dv-decoration-10 v-if="rowIndex === 1" :key="`lineTwo${rowIndex}`" />
+        </template>
+    </div>
+</template>
+<script>
+import echarts from 'echarts'
+import { chartOption } from './option'
+export default {
+    name: 'chart',
+    components: {},
+    props: {
+        info: {
+            type: Array,
+            default: () => []
+        },
+        fontSize: {
+            type: Number,
+            default: 18
+        }
+    },
+    data () {
+        return {
+            
+        }
+    },
+    computed: {
+        rowData () {
+            const data = []
+            for (let i = 0; i < this.info.length; i += 4) {
+                data.push(this.info.slice(i, i + 4))
+            }
+            return data
+        }
+    },
+    watch: {
+        info: {
+            handler () {
+                this.init()
+            },
+            deep: true
+        }
+    },
+    created () {},
+    mounted () {
+        this.init()
+    },
+    methods: {
+        init () {
+            const d = new Date()
+            const m = parseInt(d.toJSON().split('-')[1])
+            const w = window.innerWidth
+            this.fontSize = w >= 1600 ? 20 : w > 1366 && w < 1600 ? 18 : 16
+            setTimeout(() => {
+                this.info.forEach((item, index) => {
+                    const chart = echarts.init(document.getElementById(`card${index}`))
+                    const option = chartOption
+                    const xData = item.data.map((i, index) => index).slice(0, m)
+                    const yData = item.data.map(i => i.result || 0).slice(0, m)
+                    const limit = item.data.map(i => i.limit).filter(i => i !== undefined)[0]
+                    const limitValue = item.data.map(i => i.limitValue).filter(i => i)[0]
+
+                    option.title.text = item.title
+                    option.title.textStyle.fontSize = this.fontSize
+                    option.title.subtext = `限值${limitValue}`
+                    option.xAxis.data = xData
+                    option.series[0].data = yData
+                    option.series[0].markLine.data[0].yAxis = limit
+                    option.series[0].markLine.data[0].label.formatter = limit
+                    chart.setOption(option)
+                })
+            }, 1)
+        }
+    }
+}
+</script>
+<style lang="scss" module>
+    .container {
+        width: 96%;
+        height: calc(100% - 40px);
+        padding: 20px 2%;
+        .row {
+            position: relative;
+            display: flex;
+            justify-content: space-between;
+            width: 100%;
+            height: calc((100% - 70px) / 3);
+            // margin: 15px 0 15px;
+            .column {
+                width: 24%;
+                height: 100%;
+                background-color: rgba(6, 30, 93, 0.5);
+                > div {
+                    width: 100%;
+                    height: 100%;
+                }
+            }
+        }
+        :global {
+            .dv-decoration-10 {
+                width: 96%;
+                height: 5px;
+                margin: 15px 2%;
+            }
+            .dv-decoration-2 {
+                width:5px;
+                height:100%;
+            }
+        }
+    }
+</style>

+ 116 - 0
src/views/system/jbdHome/qualityTarget/components/option.js

@@ -0,0 +1,116 @@
+import echarts from 'echarts'
+
+export const chartOption = {
+    title: {
+        show: true,
+        text: '',
+        subtext: '',
+        textStyle: {
+            color: '#fff',
+            fontSize: 18,
+            fontWeight: '600'
+        },
+        subtextStyle: {
+            color: '#fff',
+            fontSize: 14,
+            fontWeight: '400',
+            align: 'center'
+        },
+        textAlign: 'center',
+        left: '50%',
+        top: '5px'
+    },
+    grid: {
+        top: '80px',
+        bottom: '30px'
+    },
+    xAxis: {
+        type: 'category',
+        data: [],
+        axisTick: {
+            alignWithLabel: true
+        },
+        axisLabel: {
+            style: {
+                fill: '#fff'
+            }
+        },
+        axisLine: {
+            lineStyle: {
+                color: '#fff'
+            }
+        }
+    },
+    yAxis: {
+        type: 'value',
+        name: '',
+        nameTextStyle: {
+            color: '#fff',
+            fontSize: 14
+        },
+        splitLine: {
+            show: false
+        },
+        axisLine: {
+            lineStyle: {
+                color: '#fff'
+            }
+        }
+    },
+    series: [{
+        type: 'line',
+        name: '',
+        data: [],
+        markLine: {
+            data: [
+                {
+                    yAxis: '',
+                    tooltip: {
+                        formatter: ''
+                    },
+                    label: {
+                        show: true, position: 'inside',
+                        color: '#83bff6',
+                        formatter: ''
+                    },
+                    lineStyle: {
+                        color: '#ff4757',
+                        type: 'dashed'
+                    }
+                }
+            ]
+        },
+        itemStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: '#83bff6' },
+                { offset: 0.5, color: '#188df0' },
+                { offset: 1, color: '#188df0' }
+            ])
+        },
+        emphasis: {
+            itemStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                    { offset: 0, color: '#2378f7' },
+                    { offset: 0.7, color: '#2378f7' },
+                    { offset: 1, color: '#83bff6' }
+                ])
+            }
+        },
+        label: {
+            show: true,
+            position: 'top',
+            textStyle: {
+                color: '#fff',
+                fontSize: 14
+            },
+            formatter (params) {
+                return params.value ? params.value : ''
+            }
+        }
+    }],
+    tooltip: {
+        show: true,
+        trigger: 'axis',
+        formatter: '任务情况<br/>{b}:{c}<br/>占比:{d}%'
+    }
+}

+ 279 - 0
src/views/system/jbdHome/qualityTarget/index.vue

@@ -0,0 +1,279 @@
+<template>
+    <div :class="$style.content">
+        <dv-full-screen-container>
+            <!-- 头部内容 -->
+            <div :class="$style.header">
+                <dv-decoration-8 :class="$style.left" />
+                <dv-decoration-5 :class="$style.center" :dur="5" />
+                <dv-decoration-8 :class="$style.right" :reverse="true" />
+                <div :class="$style.title">{{ title }}</div>
+                <dv-decoration-7 :class="$style.dept" :style="`font-size: ${fontSize}px;`">
+                    <span>{{ dept }}</span>
+                </dv-decoration-7>
+                <div :class="$style.back" @click.prevent="goBack()">
+                    <dv-border-box-8>返回</dv-border-box-8>
+                </div>
+            </div>
+            <!-- 图表区域 -->
+            <dv-border-box-1>
+                <container v-if="renderData.length" :info="renderData" :font-size="fontSize" />
+            </dv-border-box-1>
+        </dv-full-screen-container>
+    </div>
+</template>
+<script>
+import screenfull from 'screenfull'
+export default {
+    components: {
+        container: () => import('./components/container.vue')
+    },
+    data () {
+        const d = new Date()
+        const special = ['综合', '样品', '科研', '医疗', '教学', '急诊']
+        const { deptList = [] } = this.$store.getters || {}
+        const deptFilter = deptList.filter(item => { return !special.some(i => item.positionName.includes(i)) && item.depth === 4 }).sort(i => i.sn)
+        console.log(deptFilter)
+        const { first = '', second = '' } = this.$store.getters.level || {}
+        return {
+            // 过滤专业组
+            deptList: deptFilter,
+            dept: deptFilter[0].positionName,
+            // 记录部门下标,用于循环渲染
+            deptIndex: 0,
+            level: second || first,
+            title: '质量指标统计',
+            year: d.toJSON().slice(0, 4),
+            month: d.toJSON().slice(0, 7),
+            today: d.toJSON().slice(0, 10),
+            initData: {},
+            renderData: [],
+            showIndex: 0,
+            fontSize: 18,
+            timer: null,
+            changeTimer: null
+        }
+    },
+    // beforeRouteEnter(to, from, next){
+    //     Promise.all([  ]).done(([ res ]) => {
+    //         console.log(res)
+    //     }).then(err => {
+    //         window.observer.trigger('error', err)
+    //         next()
+    //     })
+    // },
+    created () {
+        const w = window.innerWidth
+        this.fontSize = w >= 1600 ? 20 : w > 1366 && w < 1600 ? 18 : 16
+
+        if (screenfull.isEnabled && !screenfull.isFullscreen) {
+            this.allView()
+        }
+
+        this.updateAll()
+        this.setTimer()
+    },
+    beforeDestroy () {
+        if (screenfull.isFullscreen) {
+            screenfull.toggle()
+        }
+        clearInterval(this.timer)
+        clearInterval(this.changeTimer)
+    },
+    methods: {
+        allView () {
+            // 默认显示全屏
+            screenfull.request()
+        },
+        goBack () {
+            this.$router.back(-1)
+        },
+        updateAll () {
+            this.getData()
+        },
+        setTimer () {
+            // 每3分钟获取一次数据
+            if (this.timer) {
+                clearInterval(this.timer)
+            }
+            this.timer = setInterval(() => {
+                this.updateAll()
+            }, 30 * 1000)
+
+            // 每5秒切换一次显示数据
+            if (this.changeTimer) {
+                clearInterval(this.changeTimer)
+            }
+            this.changeTimer = setInterval(() => {
+                this.renderData = this.getRenderData(+!this.showIndex)
+            }, 2 * 1000)
+        },
+        getData () {
+            const sql = `select a.id_ as aid, a.di_dian_ as place, a.bian_zhi_bu_men_ as dept, a.tong_ji_yue_fen_ as months, a.create_time_ as createTime, b.id_ as bid, b.zhi_liang_mu_biao as goal, b.zhi_liang_zhi_bia as target, b.zhi_biao_xian_zhi as limitValue, b.shi_ji_shu_zhi_ as result, b.yuan_shi_shu_ju_ as originalData from t_zlzbpjb a inner join (select bian_zhi_bu_men_, tong_ji_yue_fen_, max(create_time_) as max from t_zlzbpjb group by bian_zhi_bu_men_, tong_ji_yue_fen_) a_latest on a.bian_zhi_bu_men_ = a_latest.bian_zhi_bu_men_ and a.tong_ji_yue_fen_ = a_latest.tong_ji_yue_fen_ and a.create_time_ = a_latest.max left join t_zlzbpjzb b on a.id_ = b.parent_id_ where a.shi_fou_guo_shen_ = '已完成' and a.di_dian_ = '${this.level}' and a.create_time_ like '${this.year}%'`
+            this.$common.request('sql', sql).then(res => {
+                const { data = [] } = res.variables || {}
+                data.forEach(item => {
+                    const { dept, months, target } = item
+                    item.deptName = this.tranformData(dept)
+                    item.month = parseInt(months.split('-')[1])
+                    // 截取指标限值
+                    item.limit = parseFloat(item.limitValue.match(/(\d+(\.\d+)?)/))
+                    // 创建组装后的数据结构
+                    if (!this.initData[item.deptName]) {
+                        this.initData[item.deptName] = []
+                    }
+                    const index = this.initData[item.deptName].findIndex(i => i.title === target)
+                    // if (!this.initData[item.deptName][target]) {
+                    //     this.initData[item.deptName][target] = new Array(12).fill({})
+                    // }
+                    if (index === -1) {
+                        const t = {
+                            title: target,
+                            data: new Array(12).fill({})
+                        }
+                        t.data[item.month - 1] = item
+                        this.initData[item.deptName].push(t)
+                    } else {
+                        this.initData[item.deptName][index].data[item.month - 1] = item
+                    }
+                })
+                // console.log(this.initData)
+                this.renderData = this.getRenderData(0, false)
+                console.log(this.renderData)
+            })
+        },
+        tranformData (v) {
+            if (!v) {
+                return v
+            }
+            const t = this.deptList.find(i => i.positionId === v)
+            return t ? t.positionName : v
+        },
+        // 获取图表渲染数据
+        getRenderData (index, flag = true) {
+            this.showIndex = index
+            // 每两次切换渲染数据时切换一次部门(重新请求接口获取数据时不做切换)
+            if (!this.showIndex && flag) {
+                this.loopDept()
+            }
+            const t = this.initData[this.dept] && this.initData[this.dept].length
+            if (t) {
+                return this.initData[this.dept].slice(index * 12, (index + 1) * 12)
+            }
+            return []
+        },
+        loopDept () {
+            if (this.deptIndex < this.deptList.length - 1 && this.deptIndex > 0) {
+                this.deptIndex++
+                this.dept = this.deptList[this.deptIndex].positionName
+            } else {
+                this.deptIndex = 0
+                this.dept = this.deptList[this.deptIndex].positionName
+                this.deptIndex++
+            }
+        }
+    }
+}
+</script>
+<style lang="scss" module>
+    .content {
+        width: 100%;
+        height: 100%;
+        background-color: #030409;
+        position: absolute;
+        color: #fff;
+        z-index: 999;
+        :global {
+            #dv-full-screen-container {
+                background-image: url('~@/assets/images/screen/bg.png');
+                background-size: 100% 100%;
+                box-shadow: 0 0 3px blue;
+                display: flex;
+                flex-direction: column;
+            }
+            .dv-border-box-1 .border-box-content{
+                height: calc(100vh - 100px);
+            }
+            .main-content {
+                flex: 1;
+                display: flex;
+                flex-direction: column;
+            }
+
+            .block-left-right-content {
+                flex: 1;
+                display: flex;
+                margin-top: 0.8%;
+            }
+
+            .block-top-bottom-content {
+                flex: 1;
+                display: flex;
+                flex-direction: column;
+                box-sizing: border-box;
+                padding-left: 0.8%;
+            }
+
+            .block-top-content {
+                height: 55%;
+                display: flex;
+                flex-grow: 0;
+                box-sizing: border-box;
+                padding-bottom: 0.8%;
+            }
+        }
+        .header {
+            position: relative;
+            width: 100%;
+            height: 100px;
+            display: flex;
+            justify-content: space-between;
+            flex-shrink: 0;
+            .left, .right {
+                width: 25%;
+                height: 60px;
+            }
+            .center {
+                width: 40%;
+                height: 60px;
+                margin-top: 30px;
+            }
+            .title {
+                position: absolute;
+                font-size: 30px;
+                font-weight: bold;
+                left: 50%;
+                top: 15px;
+                transform: translateX(-50%);
+            }
+            .dept, .back {
+                width: 8%;
+                min-width: 120px;
+                cursor: pointer;
+                height: 2.825rem;
+                line-height: 2.825rem;
+                text-align: center;
+                margin-top: 2.5%;
+                flex: 1;
+                position: absolute;
+                color: #ffffff;
+            }
+            .dept {
+                display: flex;
+                justify-content: flex-end;
+                // width: 125px;
+                right: 75%;
+                padding: 0 10px;
+                color: #fff;
+                font-size: 22px;
+                font-weight: 400;
+                span {
+                    width: 120px;
+                    text-align: center;
+                }
+            }
+            .back {
+                left: 75%;
+            }
+        }
+    }
+</style>