Răsfoiți Sursa

考勤统计 考勤异常处理

shudonghui 1 an în urmă
părinte
comite
3e123cf63c

+ 4 - 0
src/api/ding.js

@@ -9,3 +9,7 @@ export const dingStatistic = (data) => http.post('/api/admin/tb/ding/ding_statis
 export const dingFindRunningSop = (data) => http.post('/api/admin/tb/ding/ding_find_running_sop', data, {custom: {loading: true}})
 
 export const locAddr = (data) => http.get('https://apis.map.qq.com/ws/geocoder/v1/?location=' + data.latitude + ',' + data.longitude + '&key=ORUBZ-OXNW4-HMGUJ-KMTZJ-46N37-YWFVF');
+
+//考勤打卡-查询所有sop
+export const dingFindAllSop = (data) => http.post('/api/admin/tb/ding/ding_find_all_sop', data, {custom: {loading: true}})
+

+ 14 - 0
src/pages.json

@@ -62,6 +62,20 @@
         "navigationBarTitleText": "考勤",
         "enablePullDownRefresh": false
       }
+    },
+    {
+      "path": "pages/ding/ding-statistics",
+      "style": {
+        "navigationBarTitleText": "考勤统计",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/ding/ding-abnormal",
+      "style": {
+        "navigationBarTitleText": "考勤异常处理",
+        "enablePullDownRefresh": false
+      }
     }
   ],
   "subPackages": [

+ 220 - 0
src/pages/ding/ding-abnormal.vue

@@ -0,0 +1,220 @@
+<template>
+  <view class="ding flex flex-col">
+    <RadiusSelect title="服务单元名称" placeholder="请选择服务单元" :list="serviceUnit" :value.sync="params.serviceId" :on-change="getSopList"></RadiusSelect>
+    <RadiusSelect title="SOP" placeholder="请选择SOP" :list="sopList" :value.sync="params.sopNo" :on-change="getList"></RadiusSelect>
+    <view class="msg-item">
+      <view class=" flex justify-between">
+        <view class="msg-foot flex flex-col">
+          <text :class="statistic.exceptionCount===0?'title-disable':'title'">{{ statistic.exceptionCount }}</text>
+          <text :class="statistic.exceptionCount===0?'sub-title-disable':'sub-title'">异常共计</text>
+        </view>
+        <view class="msg-separator"></view>
+        <view class="msg-foot flex flex-col" >
+          <text :class="statistic.remainCount===0?'title-disable':'title'">{{ statistic.remainCount }}</text>
+          <text :class="statistic.remainCount===0?'sub-title-disable':'sub-title'">剩余补卡次数</text>
+        </view>
+      </view>
+    </view>
+    <scroll-view
+      scroll-y="true"
+      refresher-enabled="false"
+      class="scroll-Y"
+    >
+      <view class="m-item" v-for="(item, index) in statistic.dingFormList" :key="index" v-if="item.signInInfo.status!=='SIGN'||item.signOutInfo.status!=='SIGN'">
+        <view class="m-head flex items-center justify-between">
+          <view class="m-title">
+            <text class="title">{{ item.signDate }}</text>
+          </view>
+        </view>
+        <view class="m-body">
+          <view class="key-value flex items-center justify-between" v-if="item.signInInfo.status!=='SIGN'">
+            <text class="sub-title" v-if="item.signInInfo.status==='RE_SIGN'">签到:补卡</text>
+            <text class="sub-title" v-if="item.signInInfo.status==='OTHER'">--</text>
+            <text class="sub-title" v-if="item.signInInfo.status==='NO_SIGN'">签到:未签到</text>
+            <u-button type="primary" size="mini" @click="this.$Router.push({ path: '/pages/ding/ding-supplement', query: {dingId: item.dingId, signDate: item.signDate,singnType:'IN'} })" v-if="item.signInInfo.status==='NO_SIGN'">补卡</u-button>
+          </view>
+          <view class="key-value flex items-center justify-between" v-if="item.signOutInfo.status!=='SIGN'">
+            <text class="sub-title" v-if="item.signOutInfo.status==='RE_SIGN'">签退:补卡</text>
+            <text class="sub-title" v-if="item.signOutInfo.status==='OTHER'">--</text>
+            <text class="sub-title" v-if="item.signOutInfo.status==='NO_SIGN'">签退:未签退</text>
+            <u-button type="primary" size="mini" @click="this.$Router.push({ path: '/pages/ding/ding-supplement', query: {dingId: item.dingId, signDate: item.signDate,singnType:'OUT'} })" v-if="item.signOutInfo.status==='NO_SIGN'">补卡</u-button>
+          </view>
+        </view>
+        <view class="m-separator" v-if="index!==statistic.dingFormList.length-1"></view>
+      </view>
+    </scroll-view>
+    <u-toast ref="uToast"/>
+  </view>
+</template>
+
+<script>
+
+import {getServiceUnit} from '@/api/common'
+import {dateFormat} from '@/utils/utils'
+import RadiusSelect from "@/components/radius-select.vue";
+import {dingFindAllSop,dingStatistic} from "@/api/ding";
+
+
+
+export default {
+  name: 'ding-statistics',
+  components: {RadiusSelect},
+  data() {
+    return {
+      dateFormat,
+      loadingFlag: 0,
+      serviceUnit: [],
+      sopList: [],
+      params: {serviceId: '', sopNo: null},
+      statistic: {
+        workDays: 0,
+        exceptionCount: 0,
+        remainCount: 0,
+        // dingFormList:[{
+        //   signDate:'',
+        //   signInInfo: {},
+        //   signOutInfo: {}
+        // }]
+        dingFormList:[]
+
+      }
+    }
+  },
+  mounted() {
+
+    getServiceUnit().then((res) => {
+      this.serviceUnit = (res || []).map((item) => {
+        return {
+          value: item.id,
+          label: item.name
+        }
+      })
+    })
+    this.params=this.$Route.query;
+    if(this.params.sopNo) this.getList(this.params.sopNo);
+  },
+
+  methods: {
+    getSopList(serviceId) {
+        dingFindAllSop({serviceUnitId: serviceId}).then((res) => {
+        this.sopList = (res || []).map((item) => {
+          return {
+            value: item.sopNo,
+            label: item.sopNo + item.customName
+          }
+        })
+      })
+    },
+    getList(sopNo) {
+      dingStatistic({ sopNo:sopNo}).then((res) => {
+        this.statistic=res;
+      })
+    },
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.ding {
+  height: calc(100% - 80rpx);
+  padding: 24rpx;
+}
+
+.msg-item {
+  padding: 24rpx;
+  border-radius: 12rpx;
+  background: #fff;
+  .msg-separator {
+    width: 1px;
+    background: #E5E5E5;
+  }
+  .msg-foot {
+    min-width: 48%;
+    align-items: center;
+    .sub-title {
+      height: 20px;
+      font-size: 12px;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: 400;
+      color: #8C8C8C;
+      line-height: 20px;
+    }
+    .sub-title-disable{
+      height: 20px;
+      font-size: 12px;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: 400;
+      color: #BFBFBF;
+      line-height: 20px;
+    }
+    .title {
+      height: 28px;
+      font-size: 20px;
+      font-family: PingFangSC-Medium, PingFang SC;
+      font-weight: 500;
+      color: #262626;
+      line-height: 28px;
+    }
+    .title-disable{
+      height: 28px;
+      font-size: 20px;
+      font-family: PingFangSC-Medium, PingFang SC;
+      font-weight: 500;
+      color: #BFBFBF;
+      line-height: 28px;
+    }
+  }
+}
+
+.scroll-Y {
+  height: calc(100% - 184rpx);
+  margin-top: 12px;
+  border-radius: 6px;
+  background: #FFFFFF;
+  .m-item {
+    padding: 12px;
+    .m-separator {
+      height: 1px;
+      background: #F0F0F0;
+      margin-bottom: 8rpx;
+    }
+    .m-body {
+      .key-value {
+        height: 56px;
+        background: #F7F7F7;
+        border-radius: 6px;
+        padding: 18px 12px 18px;
+        margin-bottom: 8rpx;
+        justify-content: space-between;
+        .sub-title{
+          height: 20px;
+          font-size: 14px;
+          font-family: PingFangSC-Regular, PingFang SC;
+          font-weight: 400;
+          color: #595959;
+          line-height: 20px;
+        }
+
+      }
+    }
+    .m-head {
+      .m-title {
+        display: flex;
+        align-items: center;
+        .title {
+          height: 24px;
+          font-size: 16px;
+          font-family: SourceHanSansCN-Medium, SourceHanSansCN;
+          font-weight: 500;
+          color: #262626;
+          line-height: 24px;
+        }
+      }
+    }
+  }
+}
+
+
+
+
+</style>

+ 227 - 0
src/pages/ding/ding-statistics.vue

@@ -0,0 +1,227 @@
+<template>
+  <view class="ding flex flex-col">
+    <RadiusSelect title="服务单元名称" placeholder="请选择服务单元" :list="serviceUnit" :value.sync="params.serviceId" :on-change="getSopList"></RadiusSelect>
+    <RadiusSelect title="SOP" placeholder="请选择SOP" :list="sopList" :value.sync="params.sopNo" :on-change="getList"></RadiusSelect>
+    <view class="msg-item">
+      <view class=" flex justify-between">
+        <view class="msg-foot flex flex-col">
+          <text :class="statistic.workDays===0?'title-disable':'title'">{{ statistic.workDays }}</text>
+          <text :class="statistic.workDays===0?'sub-title-disable':'sub-title'">出勤天数</text>
+        </view>
+        <view class="msg-separator"></view>
+        <view class="msg-foot flex flex-col">
+          <text :class="statistic.exceptionCount===0?'title-disable':'title'">{{ statistic.exceptionCount }}</text>
+          <text :class="statistic.exceptionCount===0?'sub-title-disable':'sub-title'">异常共计</text>
+        </view>
+        <view class="msg-separator"></view>
+        <view class="msg-foot flex flex-col" >
+          <text :class="statistic.remainCount===0?'title-disable':'title'">{{ statistic.remainCount }}</text>
+          <text :class="statistic.remainCount===0?'sub-title-disable':'sub-title'">剩余补卡次数</text>
+        </view>
+      </view>
+    </view>
+    <scroll-view
+      scroll-y="true"
+      refresher-enabled="false"
+      class="scroll-Y"
+    >
+      <view class="m-item" v-for="(item, index) in statistic.dingFormList" :key="index">
+        <view class="m-head flex items-center justify-between">
+          <view class="m-title">
+            <text class="title">{{ item.signDate }}</text>
+          </view>
+        </view>
+        <view class="m-body">
+          <view class="key-value flex items-center justify-between">
+            <text class="sub-title" v-if="item.signInInfo.status==='SIGN'">签到:{{ dateFormat(item.signInInfo.signTime, 'hh:mm') }} {{item.signInInfo.signAddress }}</text>
+            <text class="sub-title" v-if="item.signInInfo.status==='RE_SIGN'">签到:补卡</text>
+            <text class="sub-title" v-if="item.signInInfo.status==='OTHER'">--</text>
+            <text class="sub-title" v-if="item.signInInfo.status==='NO_SIGN'">签到:未签到</text>
+            <u-button type="primary" size="mini" @click="this.$Router.push({ path: '/pages/ding/ding-supplement', query: {dingId: item.dingId, signDate: item.signDate,singnType:'IN'} })" v-if="item.signInInfo.status==='NO_SIGN'">补卡</u-button>
+          </view>
+          <view class="key-value flex items-center justify-between">
+            <text class="sub-title" v-if="item.signOutInfo.status==='SIGN'">签退:{{ dateFormat(item.signOutInfo.signTime, 'hh:mm') }} {{item.signOutInfo.signAddress }}</text>
+            <text class="sub-title" v-if="item.signOutInfo.status==='RE_SIGN'">签退:补卡</text>
+            <text class="sub-title" v-if="item.signOutInfo.status==='OTHER'">--</text>
+            <text class="sub-title" v-if="item.signOutInfo.status==='NO_SIGN'">签退:未签退</text>
+            <u-button type="primary" size="mini" @click="this.$Router.push({ path: '/pages/ding/ding-supplement', query: {dingId: item.dingId, signDate: item.signDate,singnType:'OUT'} })" v-if="item.signOutInfo.status==='NO_SIGN'">补卡</u-button>
+          </view>
+        </view>
+        <view class="m-separator" v-if="index!==statistic.dingFormList.length-1"></view>
+      </view>
+    </scroll-view>
+    <u-toast ref="uToast"/>
+  </view>
+</template>
+
+<script>
+
+import {getServiceUnit} from '@/api/common'
+import {dateFormat} from '@/utils/utils'
+import RadiusSelect from "@/components/radius-select.vue";
+import {dingFindAllSop,dingStatistic} from "@/api/ding";
+
+
+
+export default {
+  name: 'ding-statistics',
+  components: {RadiusSelect},
+  data() {
+    return {
+      dateFormat,
+      loadingFlag: 0,
+      serviceUnit: [],
+      sopList: [],
+      params: {serviceId: '', sopNo: null},
+      statistic: {
+        workDays: 0,
+        exceptionCount: 0,
+        remainCount: 0,
+        // dingFormList:[{
+        //   signDate:'',
+        //   signInInfo: {},
+        //   signOutInfo: {}
+        // }]
+        dingFormList:[]
+
+      }
+    }
+  },
+  mounted() {
+
+    getServiceUnit().then((res) => {
+      this.serviceUnit = (res || []).map((item) => {
+        return {
+          value: item.id,
+          label: item.name
+        }
+      })
+    })
+    this.params=this.$Route.query;
+    if(this.params.sopNo) this.getList(this.params.sopNo);
+  },
+
+  methods: {
+    getSopList(serviceId) {
+        dingFindAllSop({serviceUnitId: serviceId}).then((res) => {
+        this.sopList = (res || []).map((item) => {
+          return {
+            value: item.sopNo,
+            label: item.sopNo + item.customName
+          }
+        })
+      })
+    },
+    getList(sopNo) {
+      dingStatistic({ sopNo:sopNo}).then((res) => {
+        this.statistic=res;
+      })
+    },
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.ding {
+  height: calc(100% - 80rpx);
+  padding: 24rpx;
+}
+
+.msg-item {
+  padding: 24rpx;
+  border-radius: 12rpx;
+  background: #fff;
+  .msg-separator {
+    width: 1px;
+    background: #E5E5E5;
+  }
+  .msg-foot {
+    min-width: 30%;
+    align-items: center;
+    .sub-title {
+      height: 20px;
+      font-size: 12px;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: 400;
+      color: #8C8C8C;
+      line-height: 20px;
+    }
+    .sub-title-disable{
+      height: 20px;
+      font-size: 12px;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: 400;
+      color: #BFBFBF;
+      line-height: 20px;
+    }
+    .title {
+      height: 28px;
+      font-size: 20px;
+      font-family: PingFangSC-Medium, PingFang SC;
+      font-weight: 500;
+      color: #262626;
+      line-height: 28px;
+    }
+    .title-disable{
+      height: 28px;
+      font-size: 20px;
+      font-family: PingFangSC-Medium, PingFang SC;
+      font-weight: 500;
+      color: #BFBFBF;
+      line-height: 28px;
+    }
+  }
+}
+
+.scroll-Y {
+  height: calc(100% - 184rpx);
+  margin-top: 12px;
+  border-radius: 6px;
+  background: #FFFFFF;
+  .m-item {
+    padding: 12px;
+    .m-separator {
+      height: 1px;
+      background: #F0F0F0;
+      margin-bottom: 8rpx;
+    }
+    .m-body {
+      .key-value {
+        height: 56px;
+        background: #F7F7F7;
+        border-radius: 6px;
+        padding: 18px 12px 18px;
+        margin-bottom: 8rpx;
+        justify-content: space-between;
+        .sub-title{
+          height: 20px;
+          font-size: 14px;
+          font-family: PingFangSC-Regular, PingFang SC;
+          font-weight: 400;
+          color: #595959;
+          line-height: 20px;
+        }
+
+      }
+    }
+    .m-head {
+      .m-title {
+        display: flex;
+        align-items: center;
+        .title {
+          height: 24px;
+          font-size: 16px;
+          font-family: SourceHanSansCN-Medium, SourceHanSansCN;
+          font-weight: 500;
+          color: #262626;
+          line-height: 24px;
+        }
+      }
+    }
+  }
+}
+
+
+
+
+</style>

+ 4 - 3
src/pages/ding/ding.vue

@@ -44,12 +44,12 @@
     </view>
     <view class="msg-item">
       <view class="m-head flex justify-between">
-        <view class="msg-foot flex flex-col">
+        <view class="msg-foot flex flex-col" @click="this.$Router.push({ path: '/pages/ding/ding-abnormal', query: params })">
           <u-icon name="error-circle" color="#8C8C8C" size="50"></u-icon>
           <text class="sub-title">异常处理</text>
         </view>
         <view class="msg-separator"></view>
-        <view class="msg-foot flex flex-col">
+        <view class="msg-foot flex flex-col" @click="this.$Router.push({ path: '/pages/ding/ding-statistics', query: params })">
           <u-icon name="clock" color="#8C8C8C" size="50"></u-icon>
           <text class="sub-title">统计</text>
         </view>
@@ -90,7 +90,7 @@ export default {
       loadingFlag: 0,
       serviceUnit: [],
       sopList: [],
-      params: {serviceId: '', sopNo: null},
+      params: {serviceId: '', sopNo: ''},
       show: false,
       faceModel: false,
       signOutEnable: false,
@@ -284,6 +284,7 @@ export default {
     .msg-foot{
       min-width: 48%;
       align-items: center;
+      cursor: pointer;
     }
     .msg-separator{
       width: 2rpx;