Browse Source

Merge branch 'Inspection' into online

wolyshaw 4 years ago
parent
commit
a5324ea38c

+ 5 - 1
package.json

@@ -22,6 +22,7 @@
     "copy-to-clipboard": "^3.3.1",
     "dayjs": "^1.8.35",
     "default-passive-events": "^1.0.10",
+    "echarts": "^4.8.0",
     "element-ui": "^2.13.2",
     "http-server": "^0.12.3",
     "i": "^0.3.6",
@@ -39,9 +40,11 @@
     "qrcodejs2": "0.0.2",
     "qs": "^6.8.0",
     "swiper": "^6.3.5",
+    "v-charts": "^1.19.0",
     "vue": "2.6.10",
     "vue-amap": "^0.5.10",
     "vue-awesome-swiper": "^4.1.1",
+    "vue-count-to": "^1.0.13",
     "vue-lunar-calendar-pro": "^1.0.14",
     "vue-qr": "^2.2.1",
     "vue-quill-editor": "^3.0.6",
@@ -79,7 +82,8 @@
     "serve-static": "^1.13.2",
     "svg-sprite-loader": "4.1.3",
     "svgo": "1.2.2",
-    "vue-template-compiler": "2.6.10"
+    "vue-template-compiler": "2.6.10",
+    "worker-loader": "^3.0.7"
   },
   "engines": {
     "node": ">=8.9",

+ 77 - 44
src/components/statistic/index.vue

@@ -1,14 +1,10 @@
 <template>
-  <el-row :gutter="cols" class="statistic">
-    <div v-for="(item, index) in tags" :key="index">
-      <el-col :span="span ? span : (index + 1) % col === 0 ? colSpan : colSpan - 1">
-        <statistic-content :item="item"></statistic-content>
-      </el-col>
-      <el-col :key="index" :span="1" v-if="(index + 1) % col !== 0 && (index + 1) < tags.length">
-        <el-divider direction="vertical"></el-divider>
-      </el-col>
+  <div class="statistic">
+    <div v-for="(item, index) in tags" :style="{flex: (100 / col) + '%'}" class="item" :key="index">
+      <statistic-content :class="item.data.class" v-on="{...item.data.on}" :item="item"/>
+      <el-divider class="divider" v-if="(index + 1) % col !== 0 && (index + 1) < tags.length" direction="vertical"></el-divider>
     </div>
-  </el-row>
+  </div>
 </template>
 <script>
 export default {
@@ -34,7 +30,7 @@ export default {
       },
       render(h) {
         return (
-          <div class="statistic-content">
+          <div onClick={this.item.data.on.click} class="statistic-content">
             {this.item.children}
           </div>
         )
@@ -43,7 +39,7 @@ export default {
   },
   computed: {
     colSpan() {
-      return this.cols / this.col
+      return 24 / this.col
     },
   },
   data() {
@@ -59,46 +55,83 @@ export default {
   },
   methods: {
     setTags() {
-      this.tags = this.$slots.default.filter(item => item.tag === 'statistic-item')
+      this.tags = (this.$slots.default || []).filter(item => item.tag === 'statistic-item').map(item => {
+        item.data = item.data || {on: {
+          click: () => {}
+        }}
+        return item
+      })
     }
   }
 }
 </script>
 <style lang="less" scoped>
-  .statistic{
-    padding: 20px 0;
-    text-align: center;
+.statistic{
+  display: flex;
+  text-align: center;
+  justify-content: space-between;
+  align-items: center;
+  flex-wrap: wrap;
+  .item{
+    display: flex;
+    position: relative;
+  }
+  /deep/ .statistic-content{
     width: 100%;
-    margin: auto!important;
-    /deep/ .el-icon-edit{
-      cursor: pointer;
-      color: #14928A;
-      font-size: 20px;
-    }
-    /deep/ .el-col {
-      display: flex;
-      align-items: center;
-      min-height: 60px;
-      align-items: center;
-      justify-content: center;
-      align-items: center;
-      flex-direction: column;
-    }
-    /deep/ .el-col-1{
-      font-size: 60px;
-    }
-    /deep/ .statistic-content{
-      >span{
-        display: block;
-        line-height: 1.8;
-        color: rgba(0, 0, 0, .85);
-        font-size: 24px;
-        &:first-child{
-          font-size: 14px;
-          font-weight: normal;
-          color: rgba(0, 0, 0, .45);
-        }
+    >span{
+      display: block;
+      line-height: 1.8;
+      color: rgba(0, 0, 0, .85);
+      font-size: 24px;
+      &:first-child{
+        font-size: 14px;
+        font-weight: normal;
+        color: rgba(0, 0, 0, .45);
       }
     }
   }
+  .divider{
+    width: 1px;
+    font-size: 60px;
+    margin: 0;
+    position: absolute;
+    right: 0;
+  }
+}
+  // .statistic{
+  //   padding: 20px 0;
+  //   text-align: center;
+  //   width: 100%;
+  //   margin: auto!important;
+  //   /deep/ .el-icon-edit{
+  //     cursor: pointer;
+  //     color: #14928A;
+  //     font-size: 20px;
+  //   }
+  //   /deep/ .el-col {
+  //     display: flex;
+  //     align-items: center;
+  //     min-height: 60px;
+  //     align-items: center;
+  //     justify-content: center;
+  //     align-items: center;
+  //     flex-direction: column;
+  //   }
+  //   /deep/ .el-col-1{
+  //     font-size: 60px;
+  //   }
+  //   /deep/ .statistic-content{
+  //     >span{
+  //       display: block;
+  //       line-height: 1.8;
+  //       color: rgba(0, 0, 0, .85);
+  //       font-size: 24px;
+  //       &:first-child{
+  //         font-size: 14px;
+  //         font-weight: normal;
+  //         color: rgba(0, 0, 0, .45);
+  //       }
+  //     }
+  //   }
+  // }
 </style>

+ 78 - 0
src/helpers/notification.js

@@ -0,0 +1,78 @@
+import dayjs from 'dayjs'
+import NotificationWorker from 'worker-loader!../workers/notification.js'
+import { createRandom } from '@/helpers/uuidv4'
+
+const clickEvents = {}
+const showEvents = {}
+const errorEvents = {}
+const closeEvents = {}
+
+const noop = () => {}
+
+export const notificationWorker = new NotificationWorker()
+
+const notificationClicked = data => {
+  const activeFn = clickEvents[data.callback_key]
+  if (typeof activeFn === 'function') {
+    activeFn(data)
+  }
+  delete clickEvents[data.callback_key]
+}
+
+const notificationShowed = data => {
+  const activeFn = showEvents[data.callback_key]
+  if (typeof activeFn === 'function') {
+    activeFn(data)
+  }
+  delete showEvents[data.callback_key]
+}
+
+const notificationErrored = data => {
+  const activeFn = errorEvents[data.callback_key]
+  if (typeof activeFn === 'function') {
+    activeFn(data)
+  }
+  delete errorEvents[data.callback_key]
+}
+
+const notificationClosed = data => {
+  const activeFn = closeEvents[data.callback_key]
+  if (typeof activeFn === 'function') {
+    activeFn(data)
+  }
+  delete closeEvents[data.callback_key]
+}
+
+notificationWorker.addEventListener('message', evt => {
+  if (evt.data.type === 'NotificationClicked') {
+    notificationClicked(evt.data.data || {})
+  } else if (evt.data.type === 'NotificationShowed') {
+    notificationShowed(evt.data.data || {})
+  } else if (evt.data.type === 'NotificationErrored') {
+    notificationErrored(evt.data.data || {})
+  } else if (evt.data.type === 'NotificationClosed') {
+    notificationClosed(evt.data.data || {})
+  }
+})
+
+export const createNotification = data => {
+  /**
+   *
+   * @param { Object } 会原样在onClick返回
+   *
+   */
+
+  const { onClick = noop, onShow = noop, onError = noop, onClose = noop, ...rest } = data
+  const timemap = dayjs().valueOf()
+  const callback_key = `${timemap}_${createRandom()}`
+  clickEvents[callback_key] = onClick
+  showEvents[callback_key] = onShow
+  errorEvents[callback_key] = onError
+  closeEvents[callback_key] = onClose
+  notificationWorker.postMessage({
+    ...rest,
+    timemap,
+    callback_key,
+    type: 'create'
+  })
+}

+ 16 - 0
src/helpers/uuidv4.js

@@ -0,0 +1,16 @@
+// from: https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid
+
+const replaceString = str => {
+  return str.replace(/[xy]/g, function(c) {
+    var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
+    return v.toString(16);
+  })
+}
+
+export default () => {
+  return replaceString('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx')
+}
+
+export const createRandom = (length = 8) => {
+  return replaceString(''.padEnd(length, 'x'))
+}

+ 56 - 0
src/views/main/abnormal/index.vue

@@ -0,0 +1,56 @@
+<template>
+  <div>
+    <el-collapse>
+      <el-collapse-item title="一致性 Consistency" name="1">
+        <template #title>
+          <title-item
+            :data="{
+              name: 'name',
+              school: 'school',
+              message: 'message'
+            }"
+          />
+        </template>
+        <div>与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;</div>
+        <div>在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。</div>
+      </el-collapse-item>
+      <div @click="send">21312312</div>
+      <el-collapse-item title="反馈 Feedback" name="2">
+        <div>控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作;</div>
+        <div>页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。</div>
+      </el-collapse-item>
+      <el-collapse-item title="效率 Efficiency" name="3">
+        <div>简化流程:设计简洁直观的操作流程;</div>
+        <div>清晰明确:语言表达清晰且表意明确,让用户快速理解进而作出决策;</div>
+        <div>帮助用户识别:界面简单直白,让用户快速识别而非回忆,减少用户记忆负担。</div>
+      </el-collapse-item>
+      <el-collapse-item title="可控 Controllability" name="4">
+        <div>用户决策:根据场景可给予用户操作建议或安全提示,但不能代替用户进行决策;</div>
+        <div>结果可控:用户可以自由的进行操作,包括撤销、回退和终止当前操作等。</div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+<script>
+import { createNotification } from '@/helpers/notification'
+import title from './title'
+export default {
+  components: {
+    'title-item': title
+  },
+  methods: {
+    send() {
+      createNotification({
+        title: '测试发送通知',
+        body: '您有一条待处理通知,请及时处理',
+        onClick: () => {
+          this.$router.replace('/main/main')
+        }
+      })
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+
+</style>

+ 45 - 0
src/views/main/abnormal/title.vue

@@ -0,0 +1,45 @@
+<template>
+  <div class="title">
+    <div v-if="type == 'philharmonic'">
+      <span>{{data.name}}</span>
+      <span>{{data.school}}</span>
+      <span><b>{{data.message}}</b></span>
+    </div>
+  </div>
+</template>
+<script>
+export default {
+  props: {
+    type: {
+      type: String,
+      default: 'philharmonic'
+    },
+    data: {
+      type: Object,
+      default: {}
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.title{
+  >div{
+    position: relative;
+    padding-left: 20px;
+    font-size: 14px;
+    &:before{
+      content: '';
+      display: block;
+      position: absolute;
+      width: 5px;
+      background-color: #F56C6C;
+      height: 48px;
+      left: 0;
+    }
+    >span{
+      margin-right: 10px;
+      display: inline-block;
+    }
+  }
+}
+</style>

+ 8 - 0
src/views/main/api.js

@@ -0,0 +1,8 @@
+import request2 from '@/utils/request2'
+
+// 填加学员接口
+export const getIndex = data => request2({
+  url: '/api-web/newIndex',
+  params: data,
+  method: 'get',
+})

+ 58 - 0
src/views/main/baseinfo/business.vue

@@ -0,0 +1,58 @@
+<template>
+  <el-card header="业务数据">
+    <statistic class="statistic" :cols="0">
+      <statistic-item v-for="(item, key) in items" :key="key" :class="{active: active === key}" @click="active = key">
+        <span>{{item.title}}</span>
+        <span>
+          <count-to :endVal="item.percent" :decimals="2"/>%
+        </span>
+      </statistic-item>
+    </statistic>
+    <ve-histogram style="width: 100%;" :data="chartData"></ve-histogram>
+  </el-card>
+</template>
+<script>
+import countTo from 'vue-count-to'
+import veHistogram from 'v-charts/lib/histogram.common'
+export default {
+  props: ['data'],
+  components: {
+    've-histogram': veHistogram,
+    'count-to': countTo
+  },
+  computed: {
+    items() {
+      return {
+        ACTIVATION_RATE: this.data['ACTIVATION_RATE'] || {},
+        HOMEWORK_CREATE_RATE: this.data['HOMEWORK_CREATE_RATE'] || {},
+        HOMEWORK_SUBMIT_RATE: this.data['HOMEWORK_SUBMIT_RATE'] || {},
+        HOMEWORK_COMMENT_RATE: this.data['HOMEWORK_COMMENT_RATE'] || {},
+      }
+    },
+    chartData() {
+      const data = this.data[this.active] || {}
+      return {
+        columns: ['月份', data.title],
+        rows: (data.indexMonthData || []).map(item => ({
+          '月份': this.$helpers.dayjs(item.month).month() + 1 + '月', [data.title]: item.percent,
+        }))
+      }
+    }
+  },
+  data () {
+    return {
+      active: 'ACTIVATION_RATE',
+    }
+  },
+}
+</script>
+<style lang="less" scoped>
+  .statistic{
+    /deep/ .statistic-content{
+      cursor: pointer;
+      &.active > span{
+        color: #14928a !important;
+      }
+    }
+  }
+</style>

+ 57 - 0
src/views/main/baseinfo/curriculum.vue

@@ -0,0 +1,57 @@
+<template>
+  <el-card header="本月课程">
+    <statistic class="statistic" :cols="0">
+      <statistic-item v-for="(item, key) in items" :key="key" :class="{active: active === key}" @click="active = key">
+        <span>{{item.title}}</span>
+        <span>
+          <count-to :endVal="item.percent"/>
+        </span>
+      </statistic-item>
+    </statistic>
+    <ve-line :data="chartData"/>
+  </el-card>
+</template>
+<script>
+import countTo from 'vue-count-to'
+import veLine from 'v-charts/lib/line.common'
+export default {
+  props: ['data'],
+  components: {
+    've-line': veLine,
+    'count-to': countTo
+  },
+  computed: {
+    items() {
+      return {
+        MUSIC_GROUP_COURSE: this.data['MUSIC_GROUP_COURSE'] || {},
+        VIP_GROUP_COURSE: this.data['VIP_GROUP_COURSE'] || {},
+        PRACTICE_GROUP_COURSE: this.data['PRACTICE_GROUP_COURSE'] || {},
+      }
+    },
+    chartData() {
+      const data = this.data[this.active] || {}
+      return {
+        columns: ['月份', data.title],
+        rows: (data.indexMonthData || []).map(item => ({
+          '月份': this.$helpers.dayjs(item.month).month() + 1 + '月', [data.title]: item.activateNum,
+        }))
+      }
+    }
+  },
+  data () {
+    return {
+      active: 'MUSIC_GROUP_COURSE',
+    }
+  },
+}
+</script>
+<style lang="less" scoped>
+  .statistic{
+    /deep/ .statistic-content{
+      cursor: pointer;
+      &.active > span{
+        color: #14928a !important;
+      }
+    }
+  }
+</style>

+ 58 - 0
src/views/main/baseinfo/hr.vue

@@ -0,0 +1,58 @@
+<template>
+  <el-card header="人事数据">
+    <statistic class="statistic" :cols="0">
+      <statistic-item v-for="(item, key) in items" :key="key" :class="{active: active === key}" @click="active = key">
+        <span>{{item.title}}</span>
+        <span>
+          <count-to :endVal="item.percent"/>
+        </span>
+      </statistic-item>
+    </statistic>
+    <ve-histogram style="width: 100%;" :data="chartData"></ve-histogram>
+  </el-card>
+</template>
+<script>
+import countTo from 'vue-count-to'
+import veHistogram from 'v-charts/lib/histogram.common'
+export default {
+  props: ['data'],
+  components: {
+    've-histogram': veHistogram,
+    'count-to': countTo
+  },
+  computed: {
+    items() {
+      return {
+        TEACHER_NUM: this.data['TEACHER_NUM'] || {},
+        FULL_TIME_NUM: this.data['FULL_TIME_NUM'] || {},
+        PART_TIME_NUM: this.data['PART_TIME_NUM'] || {},
+        DIMISSION_NUM: this.data['DIMISSION_NUM'] || {},
+      }
+    },
+    chartData() {
+      const data = this.data[this.active] || {}
+      return {
+        columns: ['月份', data.title],
+        rows: (data.indexMonthData || []).map(item => ({
+          '月份': this.$helpers.dayjs(item.month).month() + 1 + '月', [data.title]: item.activateNum,
+        }))
+      }
+    }
+  },
+  data () {
+    return {
+      active: 'TEACHER_NUM',
+    }
+  },
+}
+</script>
+<style lang="less" scoped>
+  .statistic{
+    /deep/ .statistic-content{
+      cursor: pointer;
+      &.active > span{
+        color: #14928a !important;
+      }
+    }
+  }
+</style>

+ 111 - 0
src/views/main/baseinfo/index.vue

@@ -0,0 +1,111 @@
+<template>
+  <div class="container">
+    <save-form inline :model="search" @submit="FetchDetail" @reset="FetchDetail">
+      <el-form-item prop="year">
+        <el-date-picker
+          v-model="search.year"
+          type="year"
+          placeholder="请选择年份">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item prop="organIds">
+        <el-select
+          multiple
+          clearable
+          filterable
+          collapse-tags
+          v-model="search.organIds"
+        >
+          <el-option v-for="(item,index) in selects.branchs"
+            :key="index"
+            :label="item.name"
+            :value="item.id"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-button native-type="submit" type="primary">搜索</el-button>
+      <el-button native-type="reset" type="danger">重置</el-button>
+    </save-form>
+    <el-row class="rows" :gutter="20">
+      <el-col :xs="24" :sm="24" :md="12" :span="7">
+        <operate :data="dataInfo"/>
+      </el-col>
+      <el-col :xs="24" :sm="24" :md="12" :span="7">
+        <business :data="dataInfo"/>
+      </el-col>
+      <el-col :xs="24" :sm="24" :md="24" :span="10">
+        <management :data="dataInfo"/>
+      </el-col>
+      <el-col :xs="24" :sm="24" :md="12" :span="7">
+        <hrdata :data="dataInfo"/>
+      </el-col>
+      <el-col :xs="24" :sm="24" :md="12" :span="7">
+        <student :data="dataInfo"/>
+      </el-col>
+      <el-col :xs="24" :sm="24" :md="24" :span="10">
+        <curriculum :data="dataInfo"/>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<script>
+import { getIndex } from '../api'
+import operate from './operate'
+import business from './business'
+import management from './management'
+import hrdata from './hr'
+import student from './student'
+import curriculum from './curriculum'
+
+export default {
+  components: {
+    operate,
+    business,
+    management,
+    hrdata,
+    student,
+    curriculum,
+  },
+  data () {
+    return {
+      search: {
+        year: '',
+        organIds: []
+      },
+      dataInfo: {},
+      business: {},
+    }
+  },
+  mounted () {
+    this.FetchDetail();
+    this.$store.dispatch('setBranchs')
+  },
+  methods: {
+    async FetchDetail() {
+      const data = {}
+      try {
+        const res = await getIndex({
+          ...this.search,
+          year: this.$helpers.dayjs(this.search.year).year() || '',
+          organIds: this.search.organIds.join(',')
+        })
+        for (const item of res.data) {
+          data[item.dataType] = item
+        }
+      } catch (error) {
+        console.log(error)
+      }
+      this.dataInfo = data
+    },
+  }
+}
+</script>
+<style lang="less" scoped>
+  .container{
+    overflow: hidden;
+    .rows{
+      >div{
+        margin-bottom: 20px;
+      }
+    }
+  }
+</style>

+ 58 - 0
src/views/main/baseinfo/management.vue

@@ -0,0 +1,58 @@
+<template>
+  <el-card header="经营数据">
+    <statistic class="statistic" :cols="0">
+      <statistic-item v-for="(item, key) in items" :key="key" :class="{active: active === key}" @click="active = key">
+        <span>{{item.title}}</span>
+        <span>
+          <count-to :endVal="item.percent"/>
+        </span>
+      </statistic-item>
+    </statistic>
+    <ve-line :data="chartData"/>
+  </el-card>
+</template>
+<script>
+import countTo from 'vue-count-to'
+import veLine from 'v-charts/lib/line.common'
+export default {
+  props: ['data'],
+  components: {
+    've-line': veLine,
+    'count-to': countTo
+  },
+  computed: {
+    items() {
+      return {
+        SHOULD_INCOME_MONEY: this.data['SHOULD_INCOME_MONEY'] || {},
+        MUSIC_GROUP_NUM: this.data['MUSIC_GROUP_NUM'] || {},
+        MUSIC_GROUP_STUDENT: this.data['MUSIC_GROUP_STUDENT'] || {},
+        OTHER_STUDENT: this.data['OTHER_STUDENT'] || {},
+      }
+    },
+    chartData() {
+      const data = this.data[this.active] || {}
+      return {
+        columns: ['月份', data.title],
+        rows: (data.indexMonthData || []).map(item => ({
+          '月份': this.$helpers.dayjs(item.month).month() + 1 + '月', [data.title]: item.activateNum,
+        }))
+      }
+    }
+  },
+  data () {
+    return {
+      active: 'SHOULD_INCOME_MONEY',
+    }
+  },
+}
+</script>
+<style lang="less" scoped>
+  .statistic{
+    /deep/ .statistic-content{
+      cursor: pointer;
+      &.active > span{
+        color: #14928a !important;
+      }
+    }
+  }
+</style>

+ 58 - 0
src/views/main/baseinfo/operate.vue

@@ -0,0 +1,58 @@
+<template>
+  <el-card header="运营数据">
+    <statistic class="statistic" :cols="0">
+      <statistic-item v-for="(item, key) in items" :key="key" :class="{active: active === key}" @click="active = key">
+        <span>{{item.title}}</span>
+        <span>
+          <count-to :endVal="item.percent"/>
+        </span>
+      </statistic-item>
+    </statistic>
+    <ve-histogram :data="chartData"></ve-histogram>
+  </el-card>
+</template>
+<script>
+import countTo from 'vue-count-to'
+import veHistogram from 'v-charts/lib/histogram.common'
+export default {
+  props: ['data'],
+  components: {
+    've-histogram': veHistogram,
+    'count-to': countTo
+  },
+  computed: {
+    items() {
+      return {
+        SCHOOL: this.data['SCHOOL'] || {},
+        MUSIC_GROUP_NUM: this.data['MUSIC_GROUP_NUM'] || {},
+        MUSIC_GROUP_STUDENT: this.data['MUSIC_GROUP_STUDENT'] || {},
+        OTHER_STUDENT: this.data['OTHER_STUDENT'] || {},
+      }
+    },
+    chartData() {
+      const data = this.data[this.active] || {}
+      return {
+        columns: ['月份', data.title],
+        rows: (data.indexMonthData || []).map(item => ({
+          '月份': this.$helpers.dayjs(item.month).month() + 1 + '月', [data.title]: item.activateNum,
+        }))
+      }
+    }
+  },
+  data () {
+    return {
+      active: 'SCHOOL',
+    }
+  },
+}
+</script>
+<style lang="less" scoped>
+  .statistic{
+    /deep/ .statistic-content{
+      cursor: pointer;
+      &.active > span{
+        color: #14928a !important;
+      }
+    }
+  }
+</style>

+ 57 - 0
src/views/main/baseinfo/student.vue

@@ -0,0 +1,57 @@
+<template>
+  <el-card header="学员变动">
+    <statistic class="statistic" :cols="0">
+      <statistic-item v-for="(item, key) in items" :key="key" :class="{active: active === key}" @click="active = key">
+        <span>{{item.title}}</span>
+        <span>
+          <count-to :endVal="item.percent"/>
+        </span>
+      </statistic-item>
+    </statistic>
+    <ve-histogram style="width: 100%;" :data="chartData"></ve-histogram>
+  </el-card>
+</template>
+<script>
+import countTo from 'vue-count-to'
+import veHistogram from 'v-charts/lib/histogram.common'
+export default {
+  props: ['data'],
+  components: {
+    've-histogram': veHistogram,
+    'count-to': countTo
+  },
+  computed: {
+    items() {
+      return {
+        NEWLY_STUDENT_NUM: this.data['NEWLY_STUDENT_NUM'] || {},
+        QUIT_MUSIC_GROUP_STUDENT_NUM: this.data['QUIT_MUSIC_GROUP_STUDENT_NUM'] || {},
+        STUDENT_CONVERSION: this.data['STUDENT_CONVERSION'] || {},
+      }
+    },
+    chartData() {
+      const data = this.data[this.active] || {}
+      return {
+        columns: ['月份', data.title],
+        rows: (data.indexMonthData || []).map(item => ({
+          '月份': this.$helpers.dayjs(item.month).month() + 1 + '月', [data.title]: item.activateNum,
+        }))
+      }
+    }
+  },
+  data () {
+    return {
+      active: 'NEWLY_STUDENT_NUM',
+    }
+  },
+}
+</script>
+<style lang="less" scoped>
+  .statistic{
+    /deep/ .statistic-content{
+      cursor: pointer;
+      &.active > span{
+        color: #14928a !important;
+      }
+    }
+  }
+</style>

+ 17 - 256
src/views/main/index.vue

@@ -1,270 +1,31 @@
 <template>
   <div class="m-container">
-    <h2 v-permission="'/main/main/allData/826'">
+    <h2>
       <div class="squrt"></div>首页
     </h2>
-    <div class="m-core"
-         v-permission="'/main/main/allData/826'">
-      <div class="itemWrap">
-        <div class="item">
-          <h4>
-            <p>分部乐团数</p> <img src=""
-                 alt="">
-          </h4>
-          <div class="infos">
-            <div class="info">
-              <p class="sub">全部</p>
-              <p class='msg'> {{ dataInfo.mdTotal }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">筹备中</p>
-              <p class='msg'>{{ dataInfo.prepare_num_ }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">进行中</p>
-              <p class='msg'>{{ dataInfo.progress_num_ }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">已完成</p>
-              <p class='msg'>{{ dataInfo.pause_num_ }}</p>
-            </div>
-          </div>
-        </div>
-        <!--  -->
-        <div class="item"
-             v-permission="'main/monthIncome'">
-          <h4>
-            <p>本月收入</p>
-            <img src=""
-                 alt="">
-          </h4>
-          <div class="infos">
-            <div class="info">
-              <p class="sub">收入笔数</p>
-              <p class='msg'>{{ dataInfo.incomeMap && dataInfo.incomeMap.total >= 0 ? dataInfo.incomeMap.total : '--' }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">收入金额</p>
-              <p class='msg'>{{ dataInfo.incomeMap && dataInfo.incomeMap.total_num_ >= 0 ? dataInfo.incomeMap.total_num_ : '--' }}</p>
-            </div>
-          </div>
-        </div>
-
-        <div class="item"
-             v-permission="'main/monthExpenditure'">
-          <h4>
-            <p>本月支出</p>
-            <img src=""
-                 alt="">
-          </h4>
-          <div class="infos">
-            <div class="info">
-              <p class="sub">支出笔数</p>
-              <p class='msg'>{{ dataInfo.incomeExpendMap && dataInfo.incomeExpendMap.expend_num_ >= 0 ? dataInfo.incomeExpendMap.expend_num_ : '--' }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">支出金额</p>
-              <p class='msg'>{{ dataInfo.incomeExpendMap && dataInfo.incomeExpendMap.expend_total_ >= 0 ? dataInfo.incomeExpendMap.expend_total_ : '--' }}</p>
-            </div>
-          </div>
-        </div>
-
-        <div class="item">
-          <h4>
-            <p>分部老师数</p>
-            <img src=""
-                 alt="">
-          </h4>
-          <div class="infos">
-            <div class="info">
-              <p class="sub">总计</p>
-              <p class='msg'>{{ dataInfo.teacherDatas && dataInfo.teacherDatas.total >= 0 ? dataInfo.teacherDatas.total : '--' }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">全职人数</p>
-              <p class='msg'>{{ dataInfo.teacherDatas && dataInfo.teacherDatas.full_time_num_ >= 0 ? dataInfo.teacherDatas.full_time_num_ : '--' }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">兼职人数</p>
-              <p class='msg'>{{ dataInfo.teacherDatas && dataInfo.teacherDatas.part_time_num_ >= 0 ? dataInfo.teacherDatas.part_time_num_ : '--' }}</p>
-            </div>
-          </div>
-        </div>
-
-        <div class="item">
-          <h4>
-            <p>VIP课数</p>
-            <img src=""
-                 alt="">
-          </h4>
-          <div class="infos">
-            <div class="info">
-              <p class="sub">总计</p>
-              <p class='msg'>
-                {{ dataInfo.vdTotal }}
-              </p>
-            </div>
-            <div class="info">
-              <p class="sub">进行中</p>
-              <p class='msg'>{{ dataInfo.applying_num_ }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">已完成</p>
-              <p class='msg'>{{ dataInfo.finished_num_ }}</p>
-            </div>
-          </div>
-        </div>
-
-        <div class="item">
-          <h4>
-            <p>试听课</p>
-            <img src=""
-                 alt="">
-          </h4>
-          <div class="infos">
-            <div class="info">
-              <p class="sub">总计</p>
-              <p class='msg'>{{ dataInfo.ddTotal }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">已预约</p>
-              <p class='msg'>{{ dataInfo.booked_num_ }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">未完成</p>
-              <p class='msg'>{{ dataInfo.unbooked_num_ }}</p>
-            </div>
-          </div>
-        </div>
-
-        <div class="item">
-          <h4>
-            <p>分部学生数</p>
-            <img src=""
-                 alt="">
-          </h4>
-          <div class="infos">
-            <div class="info">
-              <p class="sub">总计</p>
-              <p class='msg'>{{ dataInfo.studentDatas && dataInfo.studentDatas.total_num_ >= 0 ? dataInfo.studentDatas.total_num_ : '--' }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">本月新增</p>
-              <p class='msg'>{{ dataInfo.studentDatas && dataInfo.studentDatas.new_num_ >= 0 ? dataInfo.studentDatas.new_num_ : '--' }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">本月退团</p>
-              <p class='msg'>{{ dataInfo.studentDatas && dataInfo.studentDatas.quit_num_ >= 0 ? dataInfo.studentDatas.quit_num_ : '--' }}</p>
-            </div>
-          </div>
-        </div>
-
-        <div class="item">
-          <h4>
-            <p>本月预计上课</p>
-            <img src=""
-                 alt="">
-          </h4>
-          <div class="infos">
-            <div class="info">
-              <p class="sub">乐团课</p>
-              <p class='msg'>{{ dataInfo.musicGroupNum >= 0 ? dataInfo.musicGroupNum : '--' }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">VIP课</p>
-              <p class='msg'>{{ dataInfo.vipGroupNum >= 0 ? dataInfo.vipGroupNum : '--' }}</p>
-            </div>
-            <div class="info">
-              <p class="sub">试听课</p>
-              <p class='msg'>{{ dataInfo.demoGroupNum >= 0 ? dataInfo.demoGroupNum : '--' }}</p>
-            </div>
-          </div>
-        </div>
-
-      </div>
+    <div class="m-core">
+       <tab-router>
+        <el-tab-pane lazy label="基本信息" name="baseinfo">
+          <baseinfo/>
+        </el-tab-pane>
+        <!-- <el-tab-pane lazy label="异常处理" name="abnormal">
+          <abnormal/>
+        </el-tab-pane> -->
+      </tab-router>
     </div>
   </div>
 </template>
 <script>
-import { getIndex } from '@/api/user'
+import baseinfo from './baseinfo'
+import abnormal from './abnormal'
 export default {
   name: 'Main',
-  data () {
-    return {
-      dataInfo: {}
-    }
-  },
-  mounted () {
-    this.__init();
-
+  components: {
+    baseinfo,
+    abnormal,
   },
-  methods: {
-    __init () {
-      getIndex().then(res => {
-        if (res.code == 200) {
-          // this.dataInfo = res.data
-          let md = res.data.musicDatas || {},
-            vd = res.data.vipDatas || {},
-            dd = res.data.demoDatas || {}
-          let tempDate = {
-            mdTotal: (md.progress_num_ ? md.progress_num_ : 0) + (md.prepare_num_ ? md.prepare_num_ : 0) + (md.pause_num_ ? md.pause_num_ : 0),
-            progress_num_: md.progress_num_ >= 0 ? md.progress_num_ : '--',
-            prepare_num_: md.prepare_num_ >= 0 ? md.prepare_num_ : '--',
-            pause_num_: md.pause_num_ >= 0 ? md.pause_num_ : '--',
-            vdTotal: (vd.finished_num_ ? vd.finished_num_ : 0) + (vd.applying_num_ ? vd.applying_num_ : 0),
-            finished_num_: vd.finished_num_ >= 0 ? vd.finished_num_ : '--',
-            applying_num_: vd.applying_num_ >= 0 ? vd.applying_num_ : '--',
-            ddTotal: (dd.booked_num_ ? dd.booked_num_ : 0) + (dd.unbooked_num_ ? dd.unbooked_num_ : 0),
-            booked_num_: dd.booked_num_ >= 0 ? dd.booked_num_ : '--',
-            unbooked_num_: dd.unbooked_num_ >= 0 ? dd.unbooked_num_ : '--'
-          }
-          this.dataInfo = Object.assign(res.data, tempDate)
-        }
-      })
-    }
-  }
 }
 </script>
 <style lang="scss" scope>
-.itemWrap {
-  display: flex;
-  flex-direction: row;
-  justify-content: flex-start;
-  align-items: center;
-  flex-wrap: wrap;
-  .item {
-    width: 333px;
-    height: 137px;
-    padding: 22px 28px 20px;
-    box-shadow: 0px 8px 20px 0px rgba(0, 0, 0, 0.1);
-    border-radius: 6px;
-    margin: 0 15px 3rem;
-    h4 {
-      display: flex;
-      flex-direction: row;
-      justify-content: space-between;
-      margin-bottom: 20px;
-      color: #323c47;
-      font-size: 14px;
-    }
-    .infos {
-      display: flex;
-      flex-direction: row;
-      justify-content: space-between;
-      .sub {
-        font-size: 14px;
-        line-height: 20px;
-        color: #aaa;
-        margin-bottom: 8px;
-      }
-      .msg {
-        font-size: 24px;
-        font-weight: 500;
-        color: rgba(68, 68, 68, 1);
-        line-height: 28px;
-        text-align: center;
-      }
-    }
-  }
-}
-</style>
+
+</style>

+ 1 - 1
src/views/teamBuild/teamSeting/index.vue

@@ -311,4 +311,4 @@ export default {
     padding-top: 12px;
   }
 }
-</style>
+</style>

+ 41 - 0
src/workers/notification.js

@@ -0,0 +1,41 @@
+const createMessage = async data => {
+  if (self.Notification.permission === 'granted') {
+    const {title, body, ...rest} = data
+      const n = new Notification(title, {
+        body,
+        data: rest
+      })
+      n.addEventListener('click', () => {
+        n.close()
+        self.postMessage({
+          type: 'NotificationClicked',
+          data: rest
+        })
+      })
+      n.addEventListener('show', () => {
+        self.postMessage({
+          type: 'NotificationShowed',
+          data: rest
+        })
+      })
+      n.addEventListener('error', () => {
+        self.postMessage({
+          type: 'NotificationErrored',
+          data: rest
+        })
+      })
+      n.addEventListener('close', () => {
+        self.postMessage({
+          type: 'NotificationClosed',
+          data: rest
+        })
+      })
+   }
+}
+
+self.addEventListener('message', evt => {
+  const {type, ...rest} = evt.data
+  if (type === 'create') {
+    createMessage(rest)
+  }
+})

+ 3 - 1
vue.config.js

@@ -48,12 +48,14 @@ module.exports = {
     }
   },
   devServer: {
-    // port: port,
+    port: 443,
     open: true,
+    disableHostCheck: true,
     // overlay: {
     //   warnings: false,
     //   errors: true
     // },
+    https: true,
     proxy: {
       // change xxx-api/login => mock/login
       // detail: https://cli.vuejs.org/config/#devserver-proxy