houyaf 2 anos atrás
pai
commit
a05a23f142

+ 433 - 0
src/views/duty/record/components/Day.vue

@@ -0,0 +1,433 @@
+<template>
+  <div class="content-container">
+    <el-row>
+      <v-chart ref="myDayChart" autoresize style="width: 100%;height: 160px;" :options="chartOptions" />
+    </el-row>
+
+    <el-row class="tool-bar">
+      <el-col :span="12" class="left">
+        <div class="block-title">
+          {{ viewData.year }}年{{ viewData.month }}月 <span v-if="viewData.day>0">{{ viewData.day }}日</span>
+          <span class="block-note"> (从上面图表中可选择目标日期)</span>
+        </div>
+      </el-col>
+      <el-col :span="12" class="right">
+        <el-button class="filter-item" type="primary" icon="el-icon-document" size="mini" @click="handleExport">导出</el-button>
+      </el-col>
+    </el-row>
+
+    <el-row class="content-body">
+      <el-table
+        v-loading="listLoading"
+        class="page-table"
+        border
+        :data="dataList"
+        :max-height="maxHeight"
+      >
+        <el-table-column type="selection" />
+        <el-table-column type="index" label="序号" align="center" header-align="center" width="60" />
+
+        <el-table-column label="部门" align="center" header-align="center" width="200">
+          <template v-slot="{row}">
+            <span>{{ row.groupName }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="" align="right" header-align="right" width="80">
+          <template v-slot="{row}">
+            <el-avatar v-if="row.accountAvatar" :size="30" :src="row.accountAvatar" />
+            <svg-icon v-else icon-class="user" />
+          </template>
+        </el-table-column>
+
+        <el-table-column label="人员" align="left" header-align="left" :min-width="150">
+          <template v-slot="{row}">
+            <span>{{ row.accountName }}</span>
+            -
+            <span>{{ row.positionName }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="打卡次数" align="center" header-align="center" :min-width="80">
+          <template v-slot="{row}">
+            <span><el-tag type="warning">{{ row.dutyCount }}</el-tag></span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="最早打卡" align="left" header-align="left" :min-width="300">
+          <template v-slot="{row}">
+            <div class="flex">
+              <div class="flex-1">
+                <p v-if="row.dutyStartTime"><i class="el-icon-time" />{{ row.dutyStartTime }}</p>
+                <p v-if="row.dutyStartLocation"><i class="el-icon-location" />{{ row.dutyStartLocation }}</p>
+              </div>
+              <div v-if="row.dutyStartIcon" class="image__preview">
+                <el-image
+                  :src="row.dutyStartIcon"
+                  :preview-src-list="[row.dutyStartPhoto]"
+                  fit="contain"
+                />
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="最晚打卡" align="left" header-align="left" :min-width="300">
+          <template v-slot="{row}">
+            <div class="flex">
+              <div class="flex-1">
+                <p v-if="row.dutyLastTime"><i class="el-icon-time" />{{ row.dutyLastTime }}</p>
+                <p v-if="row.dutyLastLocation"><i class="el-icon-location" />{{ row.dutyLastLocation }}</p>
+              </div>
+              <div v-if="row.dutyLastIcon" class="image__preview">
+                <el-image
+                  :src="row.dutyLastIcon"
+                  :preview-src-list="[row.dutyLastPhoto]"
+                  fit="contain"
+                />
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" header-align="center" align="center" width="100">
+          <template v-slot="{row}">
+            <el-button size="mini" type="primary" @click="handleDetail(row)">详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-container" style="float:right;">
+        <pagination v-show="total > 0" :total="total" :page.sync="viewData.page" :limit.sync="viewData.limit" @pagination="getDutyRecord" />
+      </div>
+    </el-row>
+    <duty-record-item-list ref="DutyRecordItemList" />
+  </div>
+</template>
+
+<script>
+import Pagination from '@/components/Pagination/index.vue'
+import { getDutyRecordByPage, getDutyRecordByList } from '@/api/aqpt/dutyRecordApi'
+import { counterByDay } from '@/api/aqpt/dutyRecordStatisAPi'
+import DutyRecordItemList from './DutyRecordItemList.vue'
+import 'echarts/lib/chart/line'
+import { formatDateByYmd, parseDate } from '@/utils'
+import exportExcel from '@/utils/excel-export'
+
+export default {
+  name: 'DayCounterRecord',
+  components: {
+    Pagination,
+    DutyRecordItemList
+  },
+  filters: {},
+  data() {
+    return {
+      listLoading: false,
+      ops: {
+        bar: {
+          keepShow: false,
+          background: 'rgba(144, 147, 153, 0.4)',
+          onlyShowBarOnScroll: false
+        }
+      },
+      time_option: {
+        disabledDate(time) {
+          return time.getTime() > Date.now()
+        }
+      },
+      chartOptions: {
+        tooltip: {
+          trigger: 'axis'
+        },
+        legend: {
+          data: ['人数', '次数'],
+          textStyle: {
+            color: '#fefefe'
+          }
+        },
+        xAxis: {
+          splitLine: {
+            show: false
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#1F3C51'
+            }
+          },
+          axisLabel: {
+            show: true,
+            color: '#FFF',
+            fontSize: '8'
+          },
+          data: []
+        },
+        yAxis: {
+          type: 'value',
+          axisTick: {
+            show: false
+          },
+          splitLine: {
+            show: false
+          },
+          axisLabel: {
+            show: true,
+            color: '#FFF',
+            fontSize: '8'
+          },
+          axisLine: {
+            show: false,
+            lineStyle: {
+              color: '#FFF'
+            }
+          }
+        },
+        grid: {
+          top: '20px',
+          bottom: '10px',
+          left: '25px',
+          right: '10px',
+          containLabel: true
+        },
+        series: [
+          {
+            name: '人数',
+            type: 'line',
+            smooth: true,
+            symbol: 'circle',
+            symbolSize: 5,
+            // sampling: 'average',
+            itemStyle: {
+              normal: {
+                color: '#00f693',
+                label: {
+                  show: true
+                }
+              }
+            },
+            label: {
+              show: true
+            },
+            data: []
+          },
+          {
+            name: '次数',
+            type: 'line',
+            smooth: true,
+            symbol: 'circle',
+            symbolSize: 5,
+            // sampling: 'average',
+            itemStyle: {
+              normal: {
+                color: '#b9b3a9',
+                label: {
+                  show: true
+                }
+              }
+            },
+            label: {
+              show: true
+            },
+            data: []
+          }
+        ]
+      },
+      maxHeight: 0,
+      conditions: {},
+      dayCounter: undefined,
+      viewData: {
+        page: 1,
+        limit: 10,
+        keywords: '',
+        groupId: undefined,
+        year: undefined,
+        month: undefined,
+        day: 0
+      },
+      total: 0,
+      dataList: [],
+      exportItems: []
+    }
+  },
+  mounted() {
+  },
+  methods: {
+
+    // 加载数据
+    loadData(params) {
+      this.conditions = params
+      this.viewData.keywords = params.keywords
+      this.viewData.page = 1
+      this.viewData.limit = 10
+      this.viewData.groupId = params.groupId
+      this.viewData.year = params.year
+      this.viewData.month = params.month
+      this.viewData.day = new Date().getDate()
+      this.getData()
+    },
+
+    // 请求数据
+    getData() {
+      this.getDayCountOfMonth()
+      this.getDutyRecord()
+    },
+
+    // 按月请求统计
+    getDayCountOfMonth() {
+      this.listLoading = true
+      counterByDay(this.conditions).then((resp) => {
+        const { data } = resp
+        this.listLoading = false
+        this.dayCounter = data
+        this.$nextTick(() => {
+          this.maxHeight = window.innerHeight - 470
+          this.initCharts()
+        })
+      })
+    },
+
+    // 请求用户记录
+    getDutyRecord() {
+      this.listLoading = true
+      const { keywords, page, limit, groupId, year, month, day } = this.viewData
+      const params = {
+        keywords: keywords,
+        page: page,
+        limit: limit,
+        groupId: groupId,
+        dutyDate: formatDateByYmd(year, month, day)
+      }
+      getDutyRecordByPage(params).then((resp) => {
+        const { data, total } = resp
+        this.listLoading = false
+        this.total = total
+        this.dataList = data
+      })
+    },
+
+    // 初始化图表
+    initCharts() {
+      const keys = []
+      const vals1 = []
+      const vals2 = []
+      const dayNum = this.dayCounter.days
+      for (let i = 1; i <= dayNum; i++) {
+        const dayData = this.dayCounter['d' + i]
+        keys.push(i)
+        vals1.push(dayData.nou)
+        vals2.push(dayData.tou)
+      }
+      if (this.chartOptions.title) this.chartOptions.title.text = this.viewData.year + '年' + this.viewData.month + '月'
+      this.chartOptions.xAxis.data = keys
+      this.chartOptions.series[0].data = vals1
+      this.chartOptions.series[1].data = vals2
+      const self = this
+      this.$refs.myDayChart.chart.on('click', function(params) {
+        self.$emit('on-change', params)
+        self.viewData.day = params.name
+        self.getDutyRecord()
+      })
+    },
+
+    // 显示详情
+    handleDetail(data) {
+      const { accountId, dutyStartTime } = data
+      const dutyDate = parseDate(dutyStartTime)
+      this.$refs['DutyRecordItemList'].showView(accountId, dutyDate)
+    },
+
+    // 导出
+    handleExport() {
+      this.listLoading = true
+      const { groupId, year, month, day } = this.viewData
+      const params = {
+        groupId: groupId,
+        dutyDate: formatDateByYmd(year, month, day)
+      }
+      getDutyRecordByList(params).then((resp) => {
+        const { data } = resp
+        this.exportToExcel(data)
+        this.listLoading = false
+      })
+    },
+
+    // 导出Excel
+    exportToExcel(data) {
+      const headers = [
+        { title: '部门', dataIndex: 'groupName', width: 150 },
+        { title: '人员', dataIndex: 'accountName', width: 60 },
+        { title: '岗位', dataIndex: 'positionName', width: 60 },
+        { title: '打卡次数', dataIndex: 'dutyCount', width: 50 },
+        { title: '首次打卡时间', dataIndex: 'dutyStartTime', width: 150 },
+        { title: '首次打卡地点', dataIndex: 'dutyStartLocation', width: 150 },
+        { title: '最后打卡时间', dataIndex: 'dutyLastTime', width: 150 },
+        { title: '最后打卡地点', dataIndex: 'dutyLastTime', width: 150 }
+      ]
+
+      let t1 = this.viewData.year + '年' + this.viewData.month + '月'
+      if (this.viewData.day > 0) {
+        t1 += this.viewData.day + '日'
+      }
+      t1 += '-人员在岗记录'
+
+      const options = [
+        { title: t1 }
+      ]
+
+      const type = { bookType: 'xlsx', bookSST: true, type: 'binary', cellStyles: true }
+      exportExcel(headers, data, options, type, t1)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.content-container {
+  margin: 0 10px 0 10px ;
+  padding-top: 0;
+  height: calc(100vh - 120px);
+  background-color: #193142FF;
+  p {
+    margin: 0;
+    line-height: 2em;
+    padding: 0;
+  }
+
+  .tool-bar {
+    padding: 5px;
+    display: flex;
+    align-items: center;
+
+    .block-title {
+      font-size: 14px;
+      color: #fff;
+
+      .block-note {
+        font-size: 12px;
+        color: #bdb7b7;
+      }
+    }
+  }
+
+  .content-body {
+    .flex {
+      width: 240px;
+      display: flex;
+      align-items: center;
+      text-align: left;
+
+      .flex-1 {
+        flex: 1;
+      }
+
+      .image__preview {
+        width: 60px;
+        height: 40px;
+        border-radius: 4px;
+        overflow: hidden;
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 124 - 0
src/views/duty/record/components/DutyRecordItemList.vue

@@ -0,0 +1,124 @@
+<template>
+  <el-drawer
+    :title="title"
+    :append-to-body="true"
+    :modal="false"
+    size="50%"
+    :visible.sync="dialogVisible"
+  >
+    <template slot="title">
+      <div class="el-drawer-title">
+        <span class="name">{{ title }}</span>
+      </div>
+    </template>
+
+    <div class="content-container">
+      <el-row class="content-body">
+        <el-table v-loading="listLoading" class="page-table" border :data="dataList" size="mini" height="calc(100vh - 220px)">
+          <el-table-column type="index" label="序号" header-align="center" align="center" width="80" />
+
+          <el-table-column prop="dutyTime" label="打卡时间" header-align="center" align="center" width="160">
+            <template v-slot="{row}">
+              <span><i class="el-icon-time" />{{ row.dutyTime }}</span>
+            </template>
+          </el-table-column>
+
+          <el-table-column prop="groupName" label="所在部门" header-align="center" align="center" width="100">
+            <template v-slot="{row}">
+              <span>{{ row.groupName }}</span>
+            </template>
+          </el-table-column>
+
+          <el-table-column prop="accountName" label="人员" header-align="center" align="center" width="180">
+            <template v-slot="{row}">
+              <span><i class="el-icon-user-solid" />{{ row.accountName }}-{{ row.positionName }}</span>
+            </template>
+          </el-table-column>
+
+          <el-table-column prop="scenePhoto" label="照片" header-align="center" align="center" width="60">
+            <template v-slot="{row}">
+              <div class="custom-avatar">
+                <el-image
+                  :src="row.dutyIcon"
+                  :preview-src-list="previewList(row.dutyPhoto)"
+                  fit="contain"
+                />
+              </div>
+            </template>
+          </el-table-column>
+
+          <el-table-column prop="dutyLocation" label="打卡地点" header-align="left" align="left">
+            <template v-slot="{row}">
+              <span><i class="el-icon-location" />{{ row.dutyLocation }}</span>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-row>
+    </div>
+  </el-drawer>
+</template>
+
+<script>
+import { getDutyRecordItemByDate } from '@/api/aqpt/dutyRecordItemApi'
+
+export default {
+  name: 'DutyRecordItemList',
+  components: {},
+  filters: {},
+  data() {
+    return {
+      title: '人员打卡记录',
+      previewVisible: false,
+      ops: {
+        bar: {
+          keepShow: false,
+          background: 'rgba(144, 147, 153, 0.4)',
+          onlyShowBarOnScroll: false
+        }
+      },
+      listLoading: false,
+      dialogVisible: false,
+      dataList: [],
+      conditions: {
+        accountId: undefined,
+        dutyDate: undefined,
+        status: 1
+      }
+    }
+  },
+  computed: {},
+  mounted() {},
+  methods: {
+
+    // 显示视图
+    showView(accountId, dutyDate) {
+      this.dialogVisible = true
+      this.conditions.accountId = accountId
+      this.conditions.dutyDate = dutyDate
+      this.getData()
+    },
+
+    // 请求数据
+    getData() {
+      const { accountId, dutyDate } = this.conditions
+      getDutyRecordItemByDate(accountId, dutyDate).then((resp) => {
+        const { data } = resp
+        this.dataList = data
+      }).catch((error) => {
+        console.log(error)
+      })
+    },
+
+    // Preview Photo
+    previewList(photos) {
+      if (Array.isArray(photos)) {
+        const list = photos.map((item) => {
+          return item
+        })
+        return list
+      }
+      return [photos]
+    }
+  }
+}
+</script>

+ 366 - 0
src/views/duty/record/components/Month.vue

@@ -0,0 +1,366 @@
+<template>
+  <div class="content-container">
+    <el-row>
+      <v-chart ref="myMonthChart" autoresize style="width: 100%;height: 160px;" :options="chartOptions" />
+    </el-row>
+
+    <el-row class="tool-bar">
+      <el-col :span="12" class="left">
+        <div class="block-title">
+          {{ viewData.year }}年<span v-if="viewData.month>0">{{ viewData.month }}月</span>
+          <span class="block-note"> (从上面图表中可选择目标月份)</span>
+        </div>
+      </el-col>
+      <el-col :span="12" class="right">
+        <el-button class="filter-item" type="primary" icon="el-icon-document" size="mini" @click="handleExport">导出</el-button>
+      </el-col>
+    </el-row>
+
+    <el-row class="content-body">
+      <el-table
+        v-loading="listLoading"
+        class="page-table"
+        border
+        :data="dataList"
+        :max-height="maxHeight"
+      >
+        <el-table-column type="selection" />
+        <el-table-column type="index" label="序号" align="center" header-align="center" width="60" />
+
+        <el-table-column label="部门" align="center" header-align="center">
+          <template v-slot="{row}">
+            <span>{{ row.groupName }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="" align="right" header-align="right" width="80">
+          <template v-slot="{row}">
+            <el-avatar v-if="row.accountAvatar" :size="30" :src="row.accountAvatar" />
+            <svg-icon v-else icon-class="user" />
+          </template>
+        </el-table-column>
+
+        <el-table-column label="人员" align="left" header-align="left" width="200">
+          <template v-slot="{row}">
+            <span>{{ row.accountName }}</span>
+            -
+            <span>{{ row.positionName }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="在岗天数" align="center" header-align="center" :min-width="120">
+          <template v-slot="{row}">
+            <span>{{ row.days }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="打卡次数" align="center" header-align="center" :min-width="120">
+          <template v-slot="{row}">
+            <span>{{ row.times }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-container" style="float:right;">
+        <pagination v-show="total > 0" :total="total" :page.sync="viewData.page" :limit.sync="viewData.limit" @pagination="getDutyRecord" />
+      </div>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { counterByMonth, userCounterRecordByPage, userCounterRecordByList } from '@/api/aqpt/dutyRecordStatisAPi'
+import 'echarts/lib/chart/line'
+import exportExcel from '@/utils/excel-export'
+import Pagination from '@/components/Pagination/index.vue'
+
+export default {
+  name: 'MonthCounterRecord',
+  components: { Pagination },
+  filters: {},
+  data() {
+    return {
+      listLoading: false,
+      time_option: {
+        disabledDate(time) {
+          return time.getTime() > Date.now()
+        }
+      },
+      chartOptions: {
+        tooltip: {
+          trigger: 'axis'
+        },
+        legend: {
+          data: ['人数', '次数'],
+          textStyle: {
+            color: '#fefefe'
+          }
+        },
+        xAxis: {
+          axisTick: {
+            show: false
+          },
+          splitLine: {
+            show: false,
+            lineStyle: {
+              color: '#1F3C51',
+              width: 2
+            }
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#1F3C51',
+              width: 2
+            }
+          },
+          axisLabel: {
+            show: true,
+            color: '#FFF',
+            fontSize: '8'
+          },
+          data: []
+        },
+        yAxis: {
+          type: 'value',
+          axisTick: {
+            show: false
+          },
+          splitLine: {
+            show: false,
+            lineStyle: {
+              color: '#1F3C51',
+              width: 2
+            }
+          },
+          axisLabel: {
+            show: true,
+            color: '#FFF',
+            fontSize: '10'
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: '#1F3C51'
+            }
+          }
+        },
+        grid: {
+          top: '20px',
+          bottom: '10px',
+          left: '25px',
+          right: '10px',
+          containLabel: true
+        },
+        series: [
+          {
+            name: '人数',
+            type: 'line',
+            smooth: true,
+            symbol: 'circle',
+            symbolSize: 5,
+            showSymbol: true,
+            itemStyle: {
+              color: '#00f693'
+            },
+            lineStyle: {
+              width: 1
+            },
+            label: {
+              show: true
+            },
+            data: []
+          },
+          {
+            name: '次数',
+            type: 'line',
+            smooth: true,
+            symbol: 'circle',
+            symbolSize: 5,
+            showSymbol: true,
+            itemStyle: {
+              color: '#b9b3a9'
+            },
+            lineStyle: {
+              width: 1
+            },
+            label: {
+              show: true
+            },
+            data: []
+          }
+        ]
+      },
+      maxHeight: 0,
+      conditions: {},
+      monthCounter: undefined,
+      viewData: {
+        page: 1,
+        limit: 10,
+        keywords: '',
+        groupId: undefined,
+        year: undefined,
+        month: 0
+      },
+      total: 0,
+      dataList: undefined,
+      exportItems: []
+    }
+  },
+  mounted() {},
+  methods: {
+
+    // 加载数据
+    loadData(params) {
+      this.conditions = params
+      this.viewData.keywords = params.keywords
+      this.viewData.page = 1
+      this.viewData.limit = 10
+      this.viewData.groupId = params.groupId
+      this.viewData.year = params.year
+      this.viewData.month = new Date().getMonth() + 1
+      this.getData()
+    },
+
+    // 请求数据
+    getData() {
+      this.getCountOfMonth()
+      this.getDutyRecord()
+    },
+
+    // 按月请求统计
+    getCountOfMonth() {
+      this.listLoading = true
+      counterByMonth(this.conditions).then((resp) => {
+        const { data } = resp
+        this.listLoading = false
+        this.monthCounter = data
+        this.$nextTick(() => {
+          this.maxHeight = window.innerHeight - 400
+          this.initCharts()
+        })
+      })
+    },
+
+    // 请求人员记录
+    getDutyRecord() {
+      this.listLoading = true
+      userCounterRecordByPage(this.viewData).then((resp) => {
+        const { data, total } = resp
+        this.listLoading = false
+        this.total = total
+        this.dataList = data
+      })
+    },
+
+    // 初始化图表
+    initCharts() {
+      const keys = []
+      const vals1 = []
+      const vals2 = []
+      for (let i = 1; i <= 12; i++) {
+        const mData = this.monthCounter['m' + i]
+        keys.push(i)
+        vals1.push(mData.nou)
+        vals2.push(mData.tou)
+      }
+      this.chartOptions.xAxis.data = keys
+      this.chartOptions.series[0].data = vals1
+      this.chartOptions.series[1].data = vals2
+      const self = this
+      this.$refs.myMonthChart.chart.on('click', function(params) {
+        self.$emit('on-change', params)
+        self.viewData.month = params.name
+        self.conditions.month = params.name
+        self.getDutyRecord()
+      })
+    },
+
+    // 导出
+    handleExport() {
+      this.listLoading = true
+      userCounterRecordByList(this.viewData).then((resp) => {
+        const { data } = resp
+        this.listLoading = false
+        this.exportItems = data
+        this.exportToExcel(this.exportItems)
+      })
+    },
+
+    // 导出Excel
+    exportToExcel(data) {
+      const headers = [
+        { title: '部门', dataIndex: 'groupName', width: 150 },
+        { title: '人员', dataIndex: 'accountName', width: 60 },
+        { title: '岗位', dataIndex: 'positionName', width: 60 },
+        { title: '在岗天数', dataIndex: 'days', width: 100 },
+        { title: '打卡次数', dataIndex: 'times', width: 100 }
+      ]
+
+      let t1 = this.viewData.year + '年'
+      if (this.viewData.month > 0) {
+        t1 += this.viewData.month + '月'
+      }
+      t1 += '-人员在岗月统计'
+
+      const options = [
+        { title: t1 }
+      ]
+
+      const type = { bookType: 'xlsx', bookSST: true, type: 'binary', cellStyles: true }
+      exportExcel(headers, data, options, type, t1)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+.content-container {
+  margin: 0 10px 10px 10px ;
+  padding-top: 0;
+  height: calc(100vh - 95px);
+  background-color: #193142FF;
+  p {
+    margin: 0;
+    line-height: 2em;
+    padding: 0;
+  }
+
+  .tool-bar {
+    padding: 5px;
+    display: flex;
+    align-items: center;
+
+    .block-title {
+      font-size: 14px;
+      color: #fff;
+
+      .block-note {
+        font-size: 12px;
+        color: #bdb7b7;
+      }
+    }
+  }
+
+  .content-body {
+    padding-top: 0;
+    margin-top: 0;
+  }
+  .flex{
+    width: 240px;
+    display: flex;
+    align-items: center;
+    text-align: left;
+    .flex-1{
+      flex: 1;
+    }
+    .image__preview{
+      width: 60px;
+      height: 40px;
+      border-radius: 4px;
+      overflow: hidden;
+      cursor: pointer;
+    }
+  }
+}
+</style>

+ 372 - 0
src/views/duty/record/components/Week.vue

@@ -0,0 +1,372 @@
+<template>
+  <div class="content-container">
+    <el-row>
+      <v-chart ref="myWeekChart" autoresize style="width: 100%;height: 160px;" :options="chartOptions" />
+    </el-row>
+
+    <el-row class="tool-bar">
+      <el-col :span="12" class="left">
+        <div class="block-title">
+          {{ viewData.year }}年 <span v-if="viewData.week>0">第{{ viewData.week }}周</span>
+          <span class="block-note">  (从上面图表中可选择目标周)</span>
+        </div>
+      </el-col>
+      <el-col :span="12" class="right">
+        <el-button class="filter-item" type="primary" icon="el-icon-document" size="mini" @click="handleExport">导出</el-button>
+      </el-col>
+    </el-row>
+
+    <el-row class="content-body">
+      <el-table
+        v-loading="listLoading"
+        class="page-table"
+        border
+        :data="dataList"
+        :max-height="maxHeight"
+      >
+        <el-table-column type="selection" />
+        <el-table-column type="index" label="序号" align="center" header-align="center" width="60" />
+
+        <el-table-column label="部门" align="center" header-align="center">
+          <template v-slot="{row}">
+            <span>{{ row.groupName }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="" align="right" header-align="right" width="80">
+          <template v-slot="{row}">
+            <el-avatar v-if="row.accountAvatar" :size="30" :src="row.accountAvatar" />
+            <svg-icon v-else icon-class="user" />
+          </template>
+        </el-table-column>
+
+        <el-table-column label="人员" align="left" header-align="left" width="200">
+          <template v-slot="{row}">
+            <span>{{ row.accountName }}</span>
+            -
+            <span>{{ row.positionName }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="在岗天数" align="center" header-align="center" :min-width="120">
+          <template v-slot="{row}">
+            <span>{{ row.days }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="打卡次数" align="center" header-align="center" :min-width="120">
+          <template v-slot="{row}">
+            <span>{{ row.times }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-container" style="float:right;">
+        <pagination v-show="total > 0" :total="total" :page.sync="viewData.page" :limit.sync="viewData.limit" @pagination="getDutyRecord" />
+      </div>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { counterByWeek, userCounterRecordByList, userCounterRecordByPage } from '@/api/aqpt/dutyRecordStatisAPi'
+import 'echarts/lib/chart/line'
+import exportExcel from '@/utils/excel-export'
+import Pagination from '@/components/Pagination/index.vue'
+import { getYearWeek } from '@/utils/date'
+
+export default {
+  name: 'WeekCounterRecord',
+  components: { Pagination },
+  filters: {},
+  data() {
+    return {
+      listLoading: false,
+      time_option: {
+        disabledDate(time) {
+          return time.getTime() > Date.now()
+        }
+      },
+      chartOptions: {
+        tooltip: {
+          trigger: 'axis'
+        },
+        legend: {
+          data: ['人数', '次数'],
+          textStyle: {
+            color: '#fefefe'
+          }
+        },
+        xAxis: {
+          axisTick: {
+            show: false
+          },
+          splitLine: {
+            show: false,
+            lineStyle: {
+              color: '#1F3C51',
+              width: 2
+            }
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#1F3C51',
+              width: 2
+            }
+          },
+          axisLabel: {
+            show: true,
+            color: '#FFF',
+            fontSize: '8'
+          },
+          data: []
+        },
+        yAxis: {
+          type: 'value',
+          axisTick: {
+            show: false
+          },
+          splitLine: {
+            show: false,
+            lineStyle: {
+              color: '#1F3C51',
+              width: 2
+            }
+          },
+          axisLabel: {
+            show: true,
+            color: '#FFF',
+            fontSize: '8'
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: '#1F3C51'
+            }
+          }
+        },
+        grid: {
+          top: '20px',
+          bottom: '10px',
+          left: '25px',
+          right: '10px',
+          containLabel: true
+        },
+        series: [
+          {
+            name: '人数',
+            type: 'line',
+            smooth: true,
+            symbol: 'circle',
+            symbolSize: 5,
+            itemStyle: {
+              color: '#00f693'
+            },
+            lineStyle: {
+              width: 1
+            },
+            label: {
+              show: true
+            },
+            data: []
+          },
+          {
+            name: '次数',
+            type: 'line',
+            smooth: true,
+            symbol: 'circle',
+            symbolSize: 5,
+            itemStyle: {
+              color: '#b9b3a9'
+            },
+            lineStyle: {
+              width: 1
+            },
+            label: {
+              show: true
+            },
+            data: []
+          }
+        ]
+      },
+      maxHeight: 0,
+      conditions: {},
+      weekCounter: undefined,
+      viewData: {
+        page: 1,
+        limit: 10,
+        keywords: '',
+        groupId: undefined,
+        year: undefined,
+        week: 0
+      },
+      total: 0,
+      dataList: undefined,
+      exportItems: []
+    }
+  },
+  mounted() {},
+  methods: {
+
+    // 加载数据
+    loadData(params) {
+      this.conditions = params
+      this.viewData.keywords = params.keywords
+      const now = new Date()
+      const y = now.getFullYear()
+      const m = now.getMonth() + 1
+      const d = now.getDate()
+      this.viewData.groupId = params.groupId
+      this.viewData.year = params.year
+      this.viewData.week = getYearWeek(y, m, d)
+      this.viewData.page = 1
+      this.viewData.limit = 10
+      this.getData()
+    },
+
+    // 请求数据
+    getData() {
+      this.getCounterOfWeek()
+      this.getDutyRecord()
+    },
+
+    // 按月请求统计
+    getCounterOfWeek() {
+      this.listLoading = true
+      counterByWeek(this.conditions).then((resp) => {
+        const { data, total } = resp
+        this.listLoading = false
+        this.total = total
+        this.weekCounter = data
+        this.$nextTick(() => {
+          this.maxHeight = window.innerHeight - 400
+          this.initCharts()
+        })
+      })
+    },
+
+    // 请求人员记录
+    getDutyRecord() {
+      this.listLoading = true
+      userCounterRecordByPage(this.viewData).then((resp) => {
+        const { data, total } = resp
+        this.listLoading = false
+        this.total = total
+        this.dataList = data
+      })
+    },
+
+    // 初始化图表
+    initCharts() {
+      const keys = []
+      const vals1 = []
+      const vals2 = []
+      const wMin = this.weekCounter.weekMin
+      const wMax = this.weekCounter.weekMax
+      for (let i = wMin; i <= wMax; i++) {
+        const wData = this.weekCounter['w' + i]
+        keys.push(i)
+        vals1.push(wData.nou)
+        vals2.push(wData.tou)
+      }
+      this.chartOptions.xAxis.data = keys
+      this.chartOptions.series[0].data = vals1
+      this.chartOptions.series[1].data = vals2
+      const self = this
+      this.$refs.myWeekChart.chart.on('click', function(params) {
+        self.$emit('on-change', params)
+        self.viewData.week = params.name
+        self.conditions.week = params.name
+        self.getDutyRecord()
+      })
+    },
+
+    // 导出
+    handleExport() {
+      this.listLoading = true
+      userCounterRecordByList(this.viewData).then((resp) => {
+        const { data } = resp
+        this.listLoading = false
+        this.exportItems = data
+        this.exportToExcel(this.exportItems)
+      })
+    },
+
+    // 导出Excel
+    exportToExcel(data) {
+      const headers = [
+        { title: '部门', dataIndex: 'groupName', width: 150 },
+        { title: '人员', dataIndex: 'accountName', width: 60 },
+        { title: '岗位', dataIndex: 'positionName', width: 60 },
+        { title: '在岗天数', dataIndex: 'days', width: 100 },
+        { title: '打卡次数', dataIndex: 'times', width: 100 }
+      ]
+
+      let t1 = this.viewData.year + '年'
+      if (this.viewData.week > 0) {
+        t1 += this.viewData.week + '周'
+      }
+      t1 += '-人员在岗周统计'
+
+      const options = [
+        { title: t1 }
+      ]
+
+      const type = { bookType: 'xlsx', bookSST: true, type: 'binary', cellStyles: true }
+      exportExcel(headers, data, options, type, t1)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+.content-container {
+  margin: 0 10px 10px 10px ;
+  padding: 10px;
+  height: calc(100vh - 95px);
+  background-color: #193142FF;
+  p {
+    margin: 0;
+    line-height: 2em;
+    padding: 0;
+  }
+
+  .tool-bar {
+    padding: 5px;
+    display: flex;
+    align-items: center;
+
+    .block-title {
+      font-size: 14px;
+      color: #fff;
+      .block-note {
+        font-size: 12px;
+        color: #bdb7b7;
+      }
+    }
+
+  }
+
+  .content-body {
+    padding-top: 0;
+    margin-top: 0;
+  }
+  .flex{
+    width: 240px;
+    display: flex;
+    align-items: center;
+    text-align: left;
+    .flex-1{
+      flex: 1;
+    }
+    .image__preview{
+      width: 60px;
+      height: 40px;
+      border-radius: 4px;
+      overflow: hidden;
+      cursor: pointer;
+    }
+  }
+}
+</style>

+ 366 - 0
src/views/duty/record/components/Year.vue

@@ -0,0 +1,366 @@
+<template>
+  <div class="content-container">
+    <el-row>
+      <v-chart ref="myYearChart" autoresize style="width: 100%;height: 160px;" :options="chartOptions" />
+    </el-row>
+
+    <el-row class="tool-bar">
+      <el-col :span="12" class="left">
+        <div class="block-title">
+          <span v-if="viewData.year > 0">{{ viewData.year }}年</span>人员年度统计
+          <span class="block-note"> (从上面图表中可选择目标年份)</span>
+        </div>
+      </el-col>
+      <el-col :span="12" class="right">
+        <el-button class="filter-item" type="primary" icon="el-icon-document" size="mini" @click="handleExport">导出</el-button>
+      </el-col>
+    </el-row>
+
+    <el-row class="content-body">
+      <el-table
+        v-loading="listLoading"
+        class="page-table"
+        border
+        :data="dataList"
+        :max-height="maxHeight"
+      >
+        <el-table-column type="selection" />
+        <el-table-column type="index" label="序号" align="center" header-align="center" width="60" />
+
+        <el-table-column label="部门" align="center" header-align="center">
+          <template v-slot="{row}">
+            <span>{{ row.groupName }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="" align="right" header-align="right" width="80">
+          <template v-slot="{row}">
+            <el-avatar v-if="row.accountAvatar" :size="30" :src="row.accountAvatar" />
+            <svg-icon v-else icon-class="user" />
+          </template>
+        </el-table-column>
+
+        <el-table-column label="人员" align="left" header-align="left" width="200">
+          <template v-slot="{row}">
+            <span>{{ row.accountName }}</span>
+            -
+            <span>{{ row.positionName }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="在岗天数" align="center" header-align="center" :min-width="120">
+          <template v-slot="{row}">
+            <span>{{ row.days }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="打卡次数" align="center" header-align="center" :min-width="120">
+          <template v-slot="{row}">
+            <span>{{ row.times }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-container" style="float:right;">
+        <pagination v-show="total > 0" :total="total" :page.sync="viewData.page" :limit.sync="viewData.limit" @pagination="getDutyRecord" />
+      </div>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { counterByYear, userCounterRecordByPage, userCounterRecordByList } from '@/api/aqpt/dutyRecordStatisAPi'
+import 'echarts/lib/chart/line'
+import exportExcel from '@/utils/excel-export'
+import Pagination from '@/components/Pagination/index.vue'
+
+export default {
+  name: 'YearCounterRecord',
+  components: { Pagination },
+  filters: {},
+  data() {
+    return {
+      listLoading: false,
+      time_option: {
+        disabledDate(time) {
+          return time.getTime() > Date.now()
+        }
+      },
+      chartOptions: {
+        tooltip: {
+          trigger: 'axis'
+        },
+        legend: {
+          data: ['人数', '次数'],
+          textStyle: {
+            color: '#fefefe'
+          }
+        },
+        xAxis: {
+          axisTick: {
+            show: false
+          },
+          splitLine: {
+            show: false,
+            lineStyle: {
+              color: '#1F3C51',
+              width: 2
+            }
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#1F3C51',
+              width: 2
+            }
+          },
+          axisLabel: {
+            show: true,
+            color: '#FFF',
+            format: '{value}年',
+            fontSize: '8'
+          },
+          data: []
+        },
+        yAxis: {
+          type: 'value',
+          axisTick: {
+            show: false
+          },
+          splitLine: {
+            show: false,
+            lineStyle: {
+              color: '#1F3C51',
+              width: 2
+            }
+          },
+          axisLabel: {
+            show: true,
+            color: '#FFF',
+            fontSize: '10'
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: '#1F3C51'
+            }
+          }
+        },
+        grid: {
+          top: '10px',
+          bottom: '10px',
+          left: '25px',
+          right: '10px',
+          containLabel: true
+        },
+        series: [
+          {
+            name: '人数',
+            type: 'line',
+            smooth: true,
+            symbol: 'circle',
+            symbolSize: 5,
+            itemStyle: {
+              color: '#00f693'
+            },
+            lineStyle: {
+              width: 1
+            },
+            label: {
+              show: true
+            },
+            data: []
+          },
+          {
+            name: '次数',
+            type: 'line',
+            smooth: true,
+            symbol: 'circle',
+            symbolSize: 5,
+            itemStyle: {
+              color: '#b9b3a9'
+            },
+            lineStyle: {
+              width: 1
+            },
+            label: {
+              show: true
+            },
+            data: []
+          }
+        ]
+      },
+      maxHeight: 0,
+      conditions: {},
+      yearCounter: undefined,
+      viewData: {
+        page: 1,
+        limit: 10,
+        keywords: '',
+        groupId: undefined,
+        year: undefined
+      },
+      total: 0,
+      dataList: undefined,
+      exportItems: []
+    }
+  },
+  mounted() {},
+  methods: {
+
+    // 加载数据
+    loadData(params) {
+      this.conditions = params
+      this.viewData.keywords = params.keywords
+      this.viewData.groupId = params.groupId
+      this.viewData.year = new Date().getFullYear()
+      this.viewData.page = 1
+      this.viewData.limit = 10
+      this.getData()
+    },
+
+    // 请求数据
+    getData() {
+      this.getCounterOfYear()
+      this.getDutyRecord()
+    },
+
+    // 按月请求统计
+    getCounterOfYear() {
+      this.listLoading = true
+      counterByYear(this.conditions).then((resp) => {
+        const { data } = resp
+        this.listLoading = false
+        this.yearCounter = data
+        this.$nextTick(() => {
+          this.maxHeight = window.innerHeight - 400
+          this.initCharts()
+        })
+      })
+    },
+
+    // 请求用户记录
+    getDutyRecord() {
+      this.listLoading = true
+      userCounterRecordByPage(this.viewData).then((resp) => {
+        const { data, total } = resp
+        this.listLoading = false
+        this.total = total
+        this.dataList = data
+      })
+    },
+
+    // 初始化图表
+    initCharts() {
+      const keys = []
+      const vals1 = []
+      const vals2 = []
+      const yMin = this.yearCounter.yearMin
+      const yMax = this.yearCounter.yearMax
+      for (let i = yMin; i <= yMax; i++) {
+        const yData = this.yearCounter['y' + i]
+        keys.push(i)
+        vals1.push(yData.nou)
+        vals2.push(yData.tou)
+      }
+      this.chartOptions.xAxis.data = keys
+      this.chartOptions.series[0].data = vals1
+      this.chartOptions.series[1].data = vals2
+      const self = this
+      this.$refs.myYearChart.chart.on('click', function(params) {
+        self.$emit('on-change', params)
+        console.log(params)
+        self.viewData.year = params.name
+        self.conditions.year = params.name
+        self.getDutyRecord()
+      })
+    },
+
+    // 导出
+    handleExport() {
+      this.listLoading = true
+      userCounterRecordByList(this.viewData).then((resp) => {
+        const { data } = resp
+        this.listLoading = false
+        this.exportItems = data
+        this.exportToExcel(this.exportItems)
+      })
+    },
+
+    // 导出Excel
+    exportToExcel(data) {
+      const headers = [
+        { title: '部门', dataIndex: 'groupName', width: 150 },
+        { title: '人员', dataIndex: 'accountName', width: 60 },
+        { title: '岗位', dataIndex: 'positionName', width: 60 },
+        { title: '在岗天数', dataIndex: 'days', width: 100 },
+        { title: '打卡次数', dataIndex: 'times', width: 100 }
+      ]
+
+      let t1
+      if (this.viewData.year > 0) {
+        t1 = this.viewData.year + '年'
+      }
+      t1 += '-员工在岗年统计'
+
+      const options = [
+        { title: t1 }
+      ]
+
+      const type = { bookType: 'xlsx', bookSST: true, type: 'binary', cellStyles: true }
+      exportExcel(headers, data, options, type, t1)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+.content-container {
+  margin: 0 10px 10px 10px ;
+  padding-top: 0;
+  height: calc(100vh - 95px);
+  background-color: #193142FF;
+  p {
+    margin: 0;
+    line-height: 2em;
+    padding: 0;
+  }
+
+  .tool-bar {
+    padding: 5px;
+    display: flex;
+    align-items: center;
+
+    .block-title {
+      font-size: 14px;
+      color: #fff;
+
+      .block-note {
+        font-size: 12px;
+        color: #bdb7b7;
+      }
+    }
+  }
+
+  .content-body {
+    padding-top: 0;
+    margin-top: 0;
+  }
+  .flex{
+    width: 240px;
+    display: flex;
+    align-items: center;
+    text-align: left;
+    .flex-1{
+      flex: 1;
+    }
+    .image__preview{
+      width: 60px;
+      height: 40px;
+      border-radius: 4px;
+      overflow: hidden;
+      cursor: pointer;
+    }
+  }
+}
+</style>

+ 273 - 0
src/views/duty/record/index.vue

@@ -0,0 +1,273 @@
+<template>
+  <div class="content-container">
+    <el-row class="tool-bar">
+      <div class="tool-bar-lf">
+        <div class="content-title">
+          <span> {{ title }} </span>
+        </div>
+
+        <div class="list-group">
+          <div
+            v-for="(item, index) in tabList"
+            :key="index"
+            class="list-item"
+            :class="currentSelect === index ? 'active' : ''"
+            @click="tabSelect(index)"
+          >
+            {{ item.title }}
+          </div>
+        </div>
+      </div>
+      <div class="tool-bar-rt">
+        <div class="select-search group-select">
+          <span class="search-label">部门:</span>
+          <group-selector v-model="conditions.groupId" />
+        </div>
+
+        <div v-if="currentSelect < 3" class="select-search  right">
+          <span class="search-label">年度:</span>
+          <el-select v-model="conditions.year" filterable placeholder="年份" style="width:100px">
+            <el-option label="2022年" :value="2022" />
+            <el-option label="2023年" :value="2023" />
+            <el-option label="2024年" :value="2024" />
+            <el-option label="2025年" :value="2025" />
+            <el-option label="2026年" :value="2026" />
+            <el-option label="2027年" :value="2027" />
+          </el-select>
+        </div>
+
+        <div v-if="currentSelect === 0" class="select-search">
+          <span class="search-label">月份:</span>
+          <el-select v-model="conditions.month" filterable placeholder="月份" style="width:100px" clearable>
+            <el-option label="1月" :value="1" />
+            <el-option label="2月" :value="2" />
+            <el-option label="3月" :value="3" />
+            <el-option label="4月" :value="4" />
+            <el-option label="5月" :value="5" />
+            <el-option label="6月" :value="6" />
+            <el-option label="7月" :value="7" />
+            <el-option label="8月" :value="8" />
+            <el-option label="9月" :value="9" />
+            <el-option label="10月" :value="10" />
+            <el-option label="11月" :value="11" />
+            <el-option label="12月" :value="12" />
+          </el-select>
+        </div>
+
+        <div class="select-search" style="margin-left: 10px;">
+          <el-input v-model="conditions.keywords" class="filter-item m-right-15 search-input" style="width: 200px" placeholder="请输入关键字">
+            <el-button slot="append" icon="el-icon-search" @click="getData()" />
+          </el-input>
+        </div>
+
+      </div>
+    </el-row>
+
+    <el-row class="content-body">
+      <day v-show="currentSelect === 0" ref="day" />
+      <week v-show="currentSelect === 1" ref="week" />
+      <month v-show="currentSelect === 2" ref="month" />
+      <year v-show="currentSelect === 3" ref="year" />
+    </el-row>
+  </div>
+</template>
+
+<script>
+import Day from './components/Day.vue'
+import Week from './components/Week.vue'
+import Month from './components/Month.vue'
+import Year from './components/Year.vue'
+import { GroupSelector } from '@/components'
+import { mapGetters } from 'vuex'
+
+export default {
+  name: 'DutyRecordList',
+  components: {
+    GroupSelector,
+    Day, Week, Month, Year
+  },
+  filters: {},
+  data() {
+    return {
+      title: '人员在岗记录',
+      listLoading: false,
+      time_option: {
+        disabledDate(time) {
+          return time.getTime() > Date.now()
+        }
+      },
+      tabList: [
+        {
+          title: '日',
+          icon: ''
+        },
+        {
+          title: '周',
+          icon: ''
+        },
+        {
+          title: '月',
+          icon: ''
+        },
+        {
+          title: '年',
+          icon: ''
+        }
+      ],
+      currentSelect: 0,
+      conditions: {
+        groupId: undefined,
+        year: undefined,
+        month: undefined,
+        keywords: ''
+      }
+    }
+  },
+  computed: {
+    ...mapGetters([
+      'userData'
+    ])
+  },
+  mounted() {
+    const d = new Date()
+    this.conditions.groupId = this.userData.groupId
+    this.conditions.year = d.getFullYear()
+    this.conditions.month = d.getMonth() + 1
+    this.getData()
+  },
+  methods: {
+
+    // 请求数据
+    getData() {
+      let params
+      const curSel = this.currentSelect
+      if (curSel === 0) {
+        this.$refs.day.loadData(this.conditions)
+      } else if (curSel === 1) {
+        params = {
+          keywords: this.conditions.keywords,
+          year: this.conditions.year,
+          groupId: this.conditions.groupId
+        }
+        this.$refs.week.loadData(params)
+      } else if (curSel === 2) {
+        params = {
+          keywords: this.conditions.keywords,
+          year: this.conditions.year,
+          groupId: this.conditions.groupId
+        }
+        this.$refs.month.loadData(params)
+      } else if (curSel === 3) {
+        params = {
+          keywords: this.conditions.keywords,
+          groupId: this.conditions.groupId
+        }
+        this.$refs.year.loadData(params)
+      }
+    },
+
+    // 功能选择
+    tabSelect(index) {
+      this.currentSelect = index
+      this.getData()
+    },
+
+    // 导出
+    handleExport() {
+      const curSel = this.currentSelect
+      if (curSel === 0) {
+        this.$refs.day.handleExport()
+      } else if (curSel === 1) {
+        this.$refs.week.handleExport()
+      } else if (curSel === 2) {
+        this.$refs.month.handleExport()
+      } else if (curSel === 3) {
+        this.$refs.year.handleExport()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.content-container {
+  margin: 10px;
+  padding: 10px;
+  height: calc(100vh - 95px);
+  background-color: #193142FF;
+  .group-select{
+    display: flex;
+    width: auto;
+    align-items: center;
+    padding-right: 10px;
+  }
+  p {
+    margin: 0;
+    line-height: 2em;
+    padding: 0;
+  }
+  .tool-bar {
+    display: flex;
+    align-items: center;
+    .tool-bar-lf{
+      display: flex;
+      align-items: center;
+      flex: 1;
+    }
+    .tool-bar-rt{
+      display: flex;
+      align-items: center;
+    }
+    .list-group {
+      font-size: 16px;
+      color: #C1C6C8;
+      overflow: hidden;
+      padding:0 20px;
+      .list-item {
+        float: left;
+        margin-right: 20px;
+        cursor: pointer;
+        &.active {
+          color: #FFFFFF;
+          border-bottom: 3px solid #08A6DC;
+        }
+      }
+    }
+
+    .search-label {
+      padding-top:5px;
+      text-align: right;
+      font-size: 12px;
+      font-weight: 400;
+      color: #FFFFFF;
+
+      span {
+        font-size: 20px;
+        font-weight: 400;
+        color: #FFFFFF;
+      }
+    }
+  }
+
+  .content-body {
+    margin-top: 0;
+  }
+
+  .flex{
+    width: 240px;
+    display: flex;
+    align-items: center;
+    text-align: left;
+    .flex-1{
+      flex: 1;
+    }
+    .image__preview{
+      width: 60px;
+      height: 40px;
+      border-radius: 4px;
+      overflow: hidden;
+      cursor: pointer;
+    }
+  }
+}
+</style>