Browse Source

提交车间改动的代码

Lijy 1 ngày trước cách đây
mục cha
commit
a05aa3971e

+ 231 - 0
fhKeeper/formulahousekeeper/timesheet-workshop-h5/src/components/departmentSelection.vue

@@ -0,0 +1,231 @@
+<template>
+  <div class="chooseSomeone">
+    <div class="chooseSomeone_selsect">
+      <van-search v-model.trim="selectValue" shape="round" background="#F4F4F4" placeholder="请输入" @search="onSearch"
+        @input="onSearch"></van-search>
+    </div>
+    <div class="chooseSomeone_Con contentRoll">
+      <div class="treeBox">
+        <div class="treeBox_tree">
+          <el-tree ref="tree" v-model="treeVal" show-checkbox node-key="id" :data="personnelTree" :props="defaultProps" check-strictly @check-change="treeHandChange"
+            :filter-node-method="filterNode">
+            <span class="custom-tree-node" slot-scope="{ node }">
+              <span>
+                {{ node.label }}
+              </span>
+            </span>
+          </el-tree>
+        </div>
+      </div>
+    </div>
+    <div class="chooseSomeone_btn">
+      <van-button round type="info" size="small" style="width: 100%;" :loading="loadingBtn"
+        @click="treeHandClick()">确定</van-button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    isAdd: {
+      type: Number,
+      default: () => 1
+    },
+  },
+  components: {},
+  data() {
+    return {
+      selectValue: '',
+      radioVal: '',
+      groupVal: [],
+      treeVal: [],
+      // 人员数组
+      personnelList: [],
+      personnelTree: [],
+      newPersonnelTree: [],
+      defaultProps: {
+        children: 'children',
+        label: 'label'
+      },
+
+      loadingBtn: false, // 确定按钮loading
+    };
+  },
+  computed: {},
+  watch: {
+    selectValue(val) {
+      this.$refs.tree.filter(val);
+    }
+  },
+  created() { },
+  mounted() {
+    this.getDepartmentPersonnel()
+  },
+  methods: {
+    onSearch() {
+      if (!this.selectValue) {
+        return
+      }
+    },
+    filterNode(value, data) {
+      if (!value) return true;
+      return data.label.indexOf(value) !== -1;
+    },
+    newGroupViewBackCli(flg, i) {
+      this.newGroupViewBack = flg
+      if (this.type != 1) {
+        this.newGroupView = i
+        this.$refs.tree.setCheckedKeys(this.groupVal)
+      } else {
+        this.getPeople()
+      }
+
+    },
+    // 获取部门人员
+    getDepartmentPersonnel() {
+      this.$axios.post('/department/listAllMemb', {})
+        .then(res => {
+          if (res.code == "ok") {
+            let list = res.data;
+            this.personnelTree = list
+            this.newPersonnelTree = JSON.parse(JSON.stringify(list))
+          } else {
+            this.$toast.clear();
+            this.$toast.fail(res.msg);
+          }
+        }).catch(err => { this.$toast.clear(); });
+    },
+    treeHandChange(val, flag) {
+      const { id } = val
+      if(flag) {
+        this.$refs.tree.setCheckedKeys([]);
+        this.$refs.tree.setCheckedKeys([id]);
+      }
+    },
+    treeHandClick() {
+      let arr = this.$refs.tree.getCheckedNodes()
+      if (arr.length <= 0) {
+        this.$toast('请选择部门');
+        return
+      }
+      let newArr = arr.map(item => {
+        return {
+          value: item.id,
+          label: item.label || '',
+        }
+      })
+      this.$emit('changeDepartmentSelection', newArr)
+    }
+  },
+};
+</script>
+
+<style scoped lang="less">
+* {
+  box-sizing: border-box;
+}
+
+.textDepartmentName {
+  color: #999;
+  font-size: 14px;
+}
+
+.chooseSomeone {
+  display: flex;
+  flex-wrap: wrap;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+  padding: 10px 15px 0px 15px;
+
+  .chooseSomeone_selsect {
+    .van-search__content {
+      background-color: #fff;
+    }
+  }
+
+  .treeBox {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+
+    .treeBox_tree {
+      flex: 1;
+      overflow-y: auto;
+      padding: 10px;
+    }
+
+    .treeBox_tree_text {
+      font-size: 14px;
+      color: #999;
+      display: flex;
+      align-items: center;
+      padding: 8px;
+
+      .van-icon {
+        margin-right: 6px;
+      }
+    }
+
+    // 调整组件的默认样式
+    .el-tree-node__content {
+      height: 30px;
+    }
+  }
+
+  .chooseSomeone_Con {
+    flex: 1;
+    background-color: #fff;
+    border-radius: 10px;
+    overflow-y: auto;
+    padding: 10px;
+
+    .chooseSomeoneo_group,
+    .chooseSomeone_radio_group {
+
+      .van-radio,
+      .van-checkbox {
+        width: 100%;
+        padding: 10px;
+        font-size: 16px;
+        color: #333;
+        border-bottom: 1px solid #F4F4F4;
+
+        .van-radio__label {
+          flex: 1 !important;
+        }
+
+        .van-checkbox__label {
+          flex: 1 !important;
+        }
+      }
+
+      .chooseSomeone_group_item,
+      .chooseSomeone_radio_group_item {
+        display: flex;
+        justify-content: space-between;
+
+        div:last-child {
+          font-size: 12px;
+          color: #999;
+          width: 50%;
+          text-align: right;
+        }
+      }
+    }
+  }
+
+  .chooseSomeone_btn {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 50px;
+
+    .van-button {
+      width: 48%;
+    }
+  }
+}
+</style>

+ 676 - 0
fhKeeper/formulahousekeeper/timesheet-workshop-h5/src/views/planView/todayPlan/distribution copy.vue

@@ -0,0 +1,676 @@
+<template>
+<div>
+  <div class="distribution">
+    <van-nav-bar :title="titleText" left-text="返回" :right-text="!todayAndTomorrow ? '下发计划' : ''" @click-left="back" @click-right="placeAnOrder" fixed left-arrow style="z-index: 4;"/>
+    <div class="dropDownSelection">
+      <van-dropdown-menu>
+        <van-dropdown-item v-model="dropDownSelectionValue" :options="dropDownSelectionList" @change="getDistributionList()"/>
+      </van-dropdown-menu>
+    </div>
+    
+    <div style="display: flex;justify-content: space-between;">
+      <div class="distribution_header">
+        <div>{{ productName }}</div>
+        <div>{{ productSchedulingNum }}</div>
+        <div>{{ dates }}</div>
+      </div>
+      <van-button @click="batchUnReceive()" :disabled="!isCanAgree || distributionList.length == 0" type="info" size="small">批量拒收</van-button>
+    </div>
+    
+    <div class="distribution_con contentRoll">
+      <div class="distribution_box" v-for="item,index in distributionList" :key="index">
+        <div class="distribution_ItemBom">
+          <van-checkbox v-if="todayAndTomorrow" :disabled="item.checkboxDisabled  || (item.teamNames&&item.teamNames.indexOf(user.name) == -1)"  v-model="item.prodProcedure.isSelected"  @click="itemChecked" shape="square">
+          </van-checkbox>
+          <div class="PlanItem">
+            <span>{{item.prodProcedure.seq}}. {{ item.prodProcedure.name }}</span>
+          </div>
+          <div class="PlanItem">
+            <div>单件工价:</div><span class="textBeyondHiding">{{ item.prodProcedure.unitPrice }}</span>
+          </div>
+          <div class="PlanItem">
+            <div>总工价:</div><span class="textBeyondHiding">{{ item.totalWages }}</span>
+          </div>
+          <div class="PlanItem">
+            <div>单件工时:</div><span class="textBeyondHiding">{{ item.prodProcedure.workingTime }} min</span>
+          </div>
+          <div class="PlanItem">
+            <div>总工时:</div><span class="textBeyondHiding">{{ item.totalWorkingHours }} min</span>
+          </div>
+          <div class="PlanItem" v-if="todayAndTomorrow">
+            <div>进度:</div><span class="textBeyondHiding">{{item.totalProgress}}%</span>
+          </div>
+          <div class="PlanItem">
+            <div>质检类型:</div><span class="textBeyondHiding">
+              {{ item.prodProcedure.checkType == 0 ? '自检' : item.prodProcedure.checkType == 1 ? '互检' : '专检' }}
+            </span>
+          </div>
+          <div class="PlanItem" v-show="todayAndTomorrow" style="width:100%">
+            <div>组员:</div>
+            <span class="" v-if="item.teamNames">{{ item.teamNames }}</span>
+            <span style="color: #1989fa;" v-if="!item.teamNames && beDeptList" @click="distributionProp(item,index,'add')">分配</span>
+            <div>
+              <span style="color: #1989fa;"  @click="workShowHide(index)">{{ item.flg ? paiArr[0] : paiArr[1] }}</span>
+            </div>
+          </div>
+          <div style="width: 100%">
+              <collapse style="width: 100%">
+                <div v-if="item.flg" style="width: 100%">
+                    <div style="width: 100%;border-top: 0.02667rem solid #E6E6E6;margin-top: 10px">
+                      <div class="disZhuyuan" v-for="second_item,index in item.prodProcedureTeamList " :key="index">
+                            <span>{{ second_item.user.name }}</span>
+                            <span>{{ second_item.status==0?"待接收":second_item.status==1?"进行中":second_item.status==2?"已完工":second_item.status==3?"已中止":"已换人"}}</span>
+                            <span class="" v-if="second_item.status==3&&beDeptList" @click="distributionProp(item,index,'change', second_item)"  style="color: #1989fa;">换人</span>
+                            <!-- <span class="" v-if="second_item.status==0&&second_item.isChange==1&&beDeptList" @click="deletePeople(second_item.id)"  style="color: #1989fa;">删除</span> -->
+                            <span class="" v-if="second_item.status==0&&beDeptList" @click="deletePeople(second_item.id)"  style="color: #1989fa;">删除</span>
+                      </div>
+                      <p style="margin-top:20px;">
+                        <span class="" v-if="beDeptList && item.totalProgress<100" @click="distributionProp(item,index,'add')"  style="color: #1989fa;">新增</span>
+                      </p>
+                    </div>
+                </div>
+              </collapse>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 弹出层选人 -->
+    <van-popup v-model="popupShow" round position="bottom" :style="{ height: '80%',background: '#F4F4F4' }" >
+      <ChooseSomeone ref="ChooseSomeone" :groupView="this.groupViewNum" :type="this.peopleType" :groupViewBack="true" :peopleList="peopleList" @ChooseSomeoneChanhe="chooseSomeoneChanhe" :peopleListId="peopleListId" :key="ChooseSomeoneKey"></ChooseSomeone>
+    </van-popup>
+  </div>
+  <div class="formBatch" v-if="todayAndTomorrow">
+        <van-checkbox v-model="isAllChecked" :disabled="distributionList.length == 0" @click="allChecked" shape="square" style="padding-left:3vw"></van-checkbox>
+        <div style="padding:1vh 2vw">
+          <template v-if="dropDownSelectionValue != 0">
+            <van-button style="margin-right: 10px;" @click="cancelReassignment()" :disabled="!isCanAgree || distributionList.length == 0" type="info" size="small">取消改派</van-button>
+          </template>
+          <template v-if="dropDownSelectionValue != 1">
+            <van-button style="margin-right: 10px;" @click="showReassignment()" :disabled="!isCanAgree || distributionList.length == 0" type="info" size="small">改派</van-button>
+          </template>
+          <van-button style="margin-right: 10px;" @click="batchReceive()" :disabled="!isCanAgree || distributionList.length == 0" type="info" size="small">批量接收</van-button>
+          <van-button @click="cancellationReceiveBatch()" :disabled="!isCanAgree || distributionList.length == 0" type="info" size="small">批量取消接收</van-button>
+          <!-- <van-button @click="batchAgree(false)" :disabled="!isCanAgree || distributionList.length == 0" type="danger" size="small" style="margin-left:2vw">批量驳回</van-button> -->
+        </div>
+  </div>
+  <!-- 弹出层选部门 -->
+  <van-popup v-model="popupDepartmentShow" round position="bottom" :style="{ height: '80%',background: '#F4F4F4' }" >
+    <DepartmentSelection @changeDepartmentSelection="changeDepartmentSelection"></DepartmentSelection>
+  </van-popup>
+</div>
+</template>
+
+<script>
+import ChooseSomeone from '../../../components/chooseSomeone.vue'
+import DepartmentSelection from '../../../components/departmentSelection.vue';
+import collapse from '../../../assets/collapse.js'
+import { CellGroup, Dialog } from 'vant';
+export default {
+  props: {},
+  components: {
+    collapse,
+    ChooseSomeone,
+    DepartmentSelection
+  },
+  data() {
+    return {
+      user:JSON.parse(localStorage.getItem("userInfo")),
+      isAllChecked:  false,
+      activeNames: ['1'],
+      beDeptList: JSON.parse(localStorage.getItem('beDeptList')), // 是否为工长
+      distributionList: [],
+      distributionIndex: null,
+      popupShow: false,
+      titleText: '今日计划', // 默认文字
+      todayAndTomorrow: true, // true 今日计划,false 明日计划
+      id: '', // 传过来的id
+      type: '', // 传过来的type
+      departmentId: '', // 部门id
+      peopleList: [], // 人员数据
+      peopleListId: [], // 回显人员ID
+      popupDepartmentShow: false,
+
+      dates: '',
+      productSchedulingNum: '',
+      productName: '',
+      isCanAgree:false,
+      user: JSON.parse(localStorage.userInfo),
+      peopleType:0,
+      groupViewNum:0,
+
+      ChooseSomeoneKey: 1,
+
+      peopleListIdObj: null, // 杯换人的所有信息
+
+      dropDownSelectionList: [
+        { value: 0, text: '自己工位' },
+        { value: 1, text: '其他工位' },
+        { value: 2, text: '全部' },
+      ],
+      dropDownSelectionValue: 0
+    };
+  },
+  computed: {},
+  watch: {},
+  created() {},
+  mounted() {
+    this.paiArr = ['收起', '展开'],
+    this.id = this.$route.query.id
+    this.type = this.$route.query.type
+    this.departmentId = this.$route.query.departmentId
+    this.dates = this.$route.query.dates
+    this.productSchedulingNum = this.$route.query.productSchedulingNum
+    this.productName = this.$route.query.productName
+    console.log(this.id)
+    this.titleText = this.$route.query.titleText
+    if(this.titleText == '今日计划') {
+      this.todayAndTomorrow = true
+    } else {
+      this.todayAndTomorrow = false
+    }
+    this.getDistributionList()
+    this.getPeople()
+  },
+  methods: {
+    cancelReassignment() {
+      const str = (this.distributionList || []).map(item => item.prodProcedure && item.prodProcedure.name).join(',')
+      const ids = (this.distributionList || []).map(item => item.id).join(',')
+      Dialog.confirm({
+        title: '取消改派',
+        message: `确定取消【${str}】改派吗?`,
+      }).then(() => {
+        this.$axios.post(
+          "/plan/planDetailCancelTransfer",
+          {
+            ids: ids,
+          }
+        ).then(res => {
+          if (res.code == "ok") {
+            this.$toast.success('取消改派成功');
+            this.getDistributionList()
+          } else {
+            this.$toast.clear();
+            this.$toast.fail(res.msg);
+          }
+        }).catch(err => { this.$toast.clear(); })
+      }).catch(() => {
+        // on cancel
+      });
+    },
+    changeDepartmentSelection(list) {
+      const { value, label } = list[0] || {}
+      console.log(this.distributionList, label)
+      const str = (this.distributionList || []).map(item => item.prodProcedure && item.prodProcedure.name).join(',')
+      const ids = (this.distributionList || []).map(item => item.id).join(',')
+      Dialog.confirm({
+        title: '改派',
+        message: `确定将【${str}】改派给【${label}】部门吗?`,
+      }).then(() => {
+        this.$axios.post(
+          "/plan/planDetailTransStation",
+          {
+            ids: ids,
+            stationId: value
+          }
+        ).then(res => {
+          if (res.code == "ok") {
+            this.$toast.success('改派成功');
+            this.getDistributionList()
+            this.popupDepartmentShow = false
+          } else {
+            this.$toast.clear();
+            this.$toast.fail(res.msg);
+          }
+        }).catch(err => { this.$toast.clear(); })
+      }).catch(() => {
+        // on cancel
+      });
+    },
+    showReassignment() {
+      this.popupDepartmentShow = true
+    },
+    cancellationReceiveBatch(){
+      let ids = ''
+        console.log(this.user)
+        let resArr=  this.distributionList.map(item=>{
+          if(item.prodProcedure.isSelected){
+            if(item.prodProcedureTeamList.filter(i=>i.userId==this.user.id).length>0){
+               return item.prodProcedureTeamList.filter(i=>i.userId==this.user.id)[0].id
+            }else{
+              return "-1"
+            }
+          }
+         })
+         console.log('res===============',resArr)
+         ids = resArr.join(",").trim()
+         if(resArr.length > 0){
+            this.$axios.post(
+              "/plan/cancellationReceiveBatch",
+              {
+                ids:ids
+              }
+            ).then(res => {
+              if (res.code == "ok") {
+                this.isAllChecked=false
+                this.isCanAgree=false
+                this.getDistributionList()
+              } else {
+                this.$toast.clear();
+                this.$toast.fail(res.msg);
+              }
+            }).catch(err => { this.$toast.clear(); })
+         }
+    },
+    // 批量操作
+    allChecked(){
+        let status=false
+        if(this.isAllChecked){
+            for(let i in this.distributionList){
+                if(this.distributionList[i].teamNames&&this.distributionList[i].teamNames.indexOf(this.user.name) != -1){
+                  this.distributionList[i].prodProcedure.isSelected = true
+                  status=true
+                }
+            }
+            this.isCanAgree = true
+            if(!status){
+              this.$toast.success('当前不存在可以接受/拒收工序');
+              this.isCanAgree = false
+              this.isAllChecked=false
+            }
+        }else{
+            for(let i in this.distributionList){
+                this.distributionList[i].prodProcedure.isSelected = false
+            }
+            this.isCanAgree = false
+        }
+        console.log(this.distributionList)
+        this.$forceUpdate();
+    },
+    itemChecked(){
+        let isall = true
+        let iscan = false
+        for(let i in this.distributionList){
+            if(!this.distributionList[i].prodProcedure.isSelected){
+                isall = false
+            }else {
+                iscan = true
+            }
+        }
+        this.isAllChecked = isall
+        this.isCanAgree = iscan
+    },
+    batchReceive(bol){
+        let ids = ''
+        console.log(this.user)
+        let resArr=  this.distributionList.map(item=>{
+          if(item.prodProcedure.isSelected){
+            if(item.prodProcedureTeamList.filter(i=>i.userId==this.user.id).length>0){
+               return item.prodProcedureTeamList.filter(i=>i.userId==this.user.id)[0].id
+            }else{
+              return "-1"
+            }
+          }
+         })
+         console.log('res===============',resArr)
+         ids = resArr.join(",").trim()
+        if(resArr.length > 0){
+            this.$axios.post(
+              "/plan/receivePlan",
+              {
+                ids:ids
+              }
+            ).then(res => {
+              if (res.code === "ok") {
+                this.isAllChecked=false
+                this.isCanAgree=false
+                this.getDistributionList()
+                this.$toast.success('接收成功');
+              } else {
+                this.$toast.clear();
+                console.log("bbb")
+                this.$toast.fail(res.msg);
+              }
+            }).catch(err => { this.$toast.clear(); })
+        }
+    },
+    batchUnReceive(bol){
+        let ids = ''
+        console.log(this.user)
+        let resArr=  this.distributionList.map(item=>{
+          if(item.prodProcedure.isSelected){
+            if(item.prodProcedureTeamList.filter(i=>i.userId==this.user.id).length>0){
+               return item.prodProcedureTeamList.filter(i=>i.userId==this.user.id)[0].id
+            }else{
+              return "-1"
+            }
+          }
+         })
+         console.log('res===============',resArr)
+         ids = resArr.join(",").trim()
+        if(resArr.length > 0){
+            this.$axios.post(
+              "/plan/unReceivePlan",
+              {
+                ids:ids
+              }
+            ).then(res => {
+              if (res.code === "ok") {
+                this.isAllChecked=false
+                this.isCanAgree=false
+                this.getDistributionList()
+                this.$toast.success('拒收成功');
+              } else {
+                this.$toast.clear();
+                console.log("bbb")
+                this.$toast.fail(res.msg);
+              }
+            }).catch(err => { this.$toast.clear(); })
+        }
+    },
+    workShowHide(index) {
+      this.distributionList[index].flg = !this.distributionList[index].flg;
+      console.log('=========>',this.distributionList[index].flg)
+    },
+    back() {
+      this.$router.go(-1);
+    },
+    // 删除分配人员
+    deletePeople(item) {
+      const _this=this
+      this.$axios.post(
+        "/plan/deletePeople",
+        {
+          id:item
+        }
+      ).then(res => {
+        if (res.code == "ok") {
+          this.getDistributionList()
+        } else {
+          this.$toast.clear();
+          this.$toast.fail(res.msg);
+        }
+      }).catch(err => { this.$toast.clear(); })
+    },
+    // 下单计划
+    placeAnOrder() {
+      let ids=[this.id];
+
+      this.$axios.post(
+        "/plan/allocationPlan",
+        {
+          ids: ids.join(","),
+          planType: this.type
+        }
+      )
+    },
+    distributionProp(item, index, str, itemObj) {
+      this.ChooseSomeoneKey++
+      if(str=='add'){
+        this.peopleType=0;
+        this.groupViewNum=2;
+        if(item.teamIds) {
+          this.peopleListId = item.teamIds.split(',')
+        } else {
+          this.peopleListId = []
+        }
+      }else if(str=='change'){
+        this.peopleType=1;
+        this.groupViewNum=1;
+        // 针对换人存之前人的所有信息
+        this.peopleListId = [itemObj.userId]
+        this.peopleListIdObj = itemObj
+      }
+      if(this.beDeptList) {
+        console.log(item, index)
+
+        this.distributionIndex = index
+        this.popupShow = true
+      }
+    },
+    getDistributionList() {
+      this.$axios.post('/plan/planDetailWithStation', {
+        id: this.id,
+        type: this.type,
+        stationType: this.dropDownSelectionValue
+      })
+      .then(res => {
+        if (res.code == "ok") {
+          res.data.forEach(item => { item.flg = false })
+          let arr = res.data
+          for(var i in arr) {
+            arr[i].checkboxDisabled = true
+            if(arr[i].prodProcedureTeamList&&arr[i].prodProcedureTeamList.length > 0) {
+              let arrList = arr[i].prodProcedureTeamList.filter(item => (item.status == 0||item.status == 1) && item.userId==this.user.id)
+              console.log(arrList, 'arrList')
+              if(arrList.length > 0) {
+                arr[i].checkboxDisabled = false
+              }
+            }
+          }
+          // this.distributionList = res.data
+          this.distributionList = arr
+        } else {
+          this.$toast.clear();
+          this.$toast.fail(res.msg);
+        }
+      }).catch(err => { this.$toast.clear(); });
+      // this.$axios.post('/plan/planDetail', {
+      //   id: this.id,
+      //   type: this.type
+      // })
+      // .then(res => {
+      //   if (res.code == "ok") {
+      //     res.data.forEach(item => { item.flg = false })
+      //     let arr = res.data
+      //     for(var i in arr) {
+      //       arr[i].checkboxDisabled = true
+      //       if(arr[i].prodProcedureTeamList&&arr[i].prodProcedureTeamList.length > 0) {
+      //         let arrList = arr[i].prodProcedureTeamList.filter(item => (item.status == 0||item.status == 1) && item.userId==this.user.id)
+      //         console.log(arrList, 'arrList')
+      //         if(arrList.length > 0) {
+      //           arr[i].checkboxDisabled = false
+      //         }
+      //       }
+      //     }
+      //     // this.distributionList = res.data
+      //     this.distributionList = arr
+      //   } else {
+      //     this.$toast.clear();
+      //     this.$toast.fail(res.msg);
+      //   }
+      // }).catch(err => { this.$toast.clear(); });
+    },
+    //换人
+    changePeople(item,newPeopleId){
+        delete item.user
+        this.$axios.post('/plan/changePeople', {
+        ...item,
+        newPeopleId:newPeopleId
+      })
+      .then(res => {
+        if (res.code == "ok") {
+            this.getDistributionList()
+            this.popupShow = false
+        } else {
+          this.$toast.clear();
+          this.$toast.fail(res.msg);
+        }
+      }).catch(err => { this.$toast.clear(); });
+    },
+    // 获取人员
+    getPeople() {
+      this.$axios.post('/user/getSimpleActiveUserList', {
+        departmentId: this.departmentId,
+      })
+      .then(res => {
+        if (res.code == "ok") {
+          this.peopleList = res.data.map(item => {
+            return {
+              name: item.name,
+              id: item.id,
+              phone: item.phone,
+              jobNumber: item.jobNumber,
+              departmentName: item.departmentName
+            }
+          })
+        } else {
+          this.$toast.clear();
+          this.$toast.fail(res.msg);
+        }
+      }).catch(err => { this.$toast.clear(); });
+    },
+    teamAllocation(item, nameArr){
+      let newDistributionList = JSON.parse(JSON.stringify(this.distributionList[this.distributionIndex]))
+      delete newDistributionList.prodProcedure
+      delete newDistributionList.prodProcedureTeamList
+      this.$axios.post('/plan/teamAllocation', {
+        ...newDistributionList,
+        teamIds: item.join(","),
+        teamNames: nameArr.join(','),
+        planType: this.type
+      })
+      .then(res => {
+        this.$refs.ChooseSomeone['loadingBtn'] = false
+        if (res.code == "ok") {
+          this.distributionList[this.distributionIndex].teamNames = nameArr.join(',')
+          this.getDistributionList()
+          this.popupShow = false
+        } else {
+          this.$toast.clear();
+          this.$toast.fail(res.msg);
+        }
+      }).catch(err => { this.$toast.clear();this.$refs.ChooseSomeone['loadingBtn'] = false});
+    },
+
+    // 选中人员
+    chooseSomeoneChanhe(item) {
+      // console.log('当前点击的人员', item, this.distributionList[this.distributionIndex])
+      let arr = item.map(item => {
+        return item.id
+      })
+      let nameArr = item.map(item => {
+        return item.name
+      })
+      if(this.peopleType == 1) {
+        console.log(arr)
+        console.log('被换人信息', this.peopleListIdObj)
+        this.changePeople(this.peopleListIdObj,arr[0])
+      } else {
+        this.teamAllocation(arr, nameArr)
+      }
+    }
+  },
+};
+</script>
+
+<style scoped lang="less">
+  * {
+    box-sizing: border-box;
+  }
+  .dropDownSelection {
+    position:fixed;
+    top: 46px;
+    left: 0;
+    width: 100%;
+    z-index: 3;
+  }
+  .formBatch{
+        position: fixed;
+        bottom: 0;
+        width: 100%;
+        z-index: 1;
+        background-color: #fff;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        height: 1.2rem;
+        border-top: 1px solid #ebebeb;
+    }
+
+    .disZhuyuan {
+      padding-left: 30px;
+      font-size: 14px;
+      width: 100%;
+      display: flex;
+      padding-right: 100px;
+      justify-content: space-between;
+      margin-top: 10px;
+
+    }
+  .distribution {
+    width: 100%;
+    height: 100%;
+    padding: 105px 15px 45px 15px;
+    display: flex;
+    flex-direction: column;
+    flex-wrap: wrap;
+    background-color: #F4F4F4;
+    color: #333;
+
+    .distribution_header {
+      font-size: 16px;
+      div {
+        margin-top: 6px;
+      }
+    }
+
+    .distribution_con {
+      flex: 1;
+      overflow: auto;
+      margin-top: 14px;
+
+      .distribution_box {
+        width: 100%;
+        background-color: #fff;
+        border-radius: 4px;
+        padding: 10px;
+        margin-bottom: 15px;
+        position: relative;
+        overflow: hidden;
+
+        .distribution_ItemBom {
+          font-size: 16px;
+          display: flex;
+          flex-wrap: wrap;
+          padding: 0px 6px 6px 6px;
+
+          .PlanItem {
+            width: 50%;
+            display: flex;
+            padding-top: 12px;
+
+
+            &:first-child {
+              width: 100%;
+              padding-top: 10px;
+              span {
+                font-size: 18px;
+                color: #333;
+              }
+            }
+
+            &:nth-child(2) {
+              width: 100%;
+              span {
+                width: 230px;
+                word-break: break-all;
+              }
+            }
+
+            div {
+              width: 80px;
+              text-align: right;
+              color: #666;
+            }
+
+            span {
+              display: inline-block;
+              color: #333;
+              font-size: 14px;
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 167 - 13
fhKeeper/formulahousekeeper/timesheet-workshop-h5/src/views/planView/todayPlan/distribution.vue

@@ -1,20 +1,27 @@
 <template>
 <div>
   <div class="distribution">
-    <van-nav-bar :title="titleText" left-text="返回" :right-text="!todayAndTomorrow ? '下发计划' : ''" @click-left="back" @click-right="placeAnOrder" fixed left-arrow/>
+    <van-nav-bar :title="titleText" left-text="返回" :right-text="!todayAndTomorrow ? '下发计划' : ''" @click-left="back" @click-right="placeAnOrder" fixed left-arrow style="z-index: 4;"/>
+    <div class="dropDownSelection">
+      <van-dropdown-menu>
+        <van-dropdown-item v-model="dropDownSelectionValue" :options="dropDownSelectionList" @change="getDistributionList()"/>
+      </van-dropdown-menu>
+    </div>
+    
     <div style="display: flex;justify-content: space-between;">
       <div class="distribution_header">
         <div>{{ productName }}</div>
         <div>{{ productSchedulingNum }}</div>
         <div>{{ dates }}</div>
       </div>
-      <van-button @click="batchUnReceive()" :disabled="!isCanAgree || distributionList.length == 0" type="info" size="small">批量拒收</van-button>
+      <van-button @click="batchUnReceive()" :disabled="canBatchOperationsBePerformed" type="info" size="small">批量拒收</van-button>
     </div>
     
     <div class="distribution_con contentRoll">
       <div class="distribution_box" v-for="item,index in distributionList" :key="index">
         <div class="distribution_ItemBom">
-          <van-checkbox v-if="todayAndTomorrow" :disabled="item.checkboxDisabled  || (item.teamNames&&item.teamNames.indexOf(user.name) == -1)"  v-model="item.prodProcedure.isSelected"  @click="itemChecked" shape="square">
+          <!-- <van-checkbox v-if="todayAndTomorrow" :disabled="item.checkboxDisabled  || (item.teamNames&&item.teamNames.indexOf(user.name) == -1)"  v-model="item.prodProcedure.isSelected"  @click="itemChecked" shape="square"> -->
+          <van-checkbox v-if="todayAndTomorrow" v-model="item.prodProcedure.isSelected"  @click="itemChecked" shape="square">
           </van-checkbox>
           <div class="PlanItem">
             <span>{{item.prodProcedure.seq}}. {{ item.prodProcedure.name }}</span>
@@ -76,23 +83,32 @@
   <div class="formBatch" v-if="todayAndTomorrow">
         <van-checkbox v-model="isAllChecked" :disabled="distributionList.length == 0" @click="allChecked" shape="square" style="padding-left:3vw"></van-checkbox>
         <div style="padding:1vh 2vw">
-        <van-button style="margin-right: 10px;" @click="batchReceive()" :disabled="!isCanAgree || distributionList.length == 0" type="info" size="small">批量接收</van-button>
-        <van-button @click="cancellationReceiveBatch()" :disabled="!isCanAgree || distributionList.length == 0" type="info" size="small">批量取消接收</van-button>
-        <!-- <van-button @click="batchAgree(false)" :disabled="!isCanAgree || distributionList.length == 0" type="danger" size="small" style="margin-left:2vw">批量驳回</van-button> -->
+          <van-button style="margin-right: 10px;" @click="cancelReassignment()" :disabled="doYouWantToCancelTheReassignment" type="info" size="small">取消改派</van-button>
+          <van-button style="margin-right: 10px;" @click="showReassignment()" :disabled="isItPossibleToReassign" type="info" size="small">改派</van-button>
+        <van-button style="margin-right: 10px;" @click="batchReceive()" :disabled="canBatchOperationsBePerformed" type="info" size="small">批量接收</van-button>
+          <van-button @click="cancellationReceiveBatch()" :disabled="canBatchOperationsBePerformed" type="info" size="small">批量取消接收</van-button>
+          
+          <!-- <van-button @click="batchAgree(false)" :disabled="!isCanAgree || distributionList.length == 0" type="danger" size="small" style="margin-left:2vw">批量驳回</van-button> -->
         </div>
   </div>
+  <!-- 弹出层选部门 -->
+  <van-popup v-model="popupDepartmentShow" round position="bottom" :style="{ height: '80%',background: '#F4F4F4' }" >
+    <DepartmentSelection @changeDepartmentSelection="changeDepartmentSelection"></DepartmentSelection>
+  </van-popup>
 </div>
 </template>
 
 <script>
 import ChooseSomeone from '../../../components/chooseSomeone.vue'
+import DepartmentSelection from '../../../components/departmentSelection.vue';
 import collapse from '../../../assets/collapse.js'
-import { CellGroup } from 'vant';
+import { CellGroup, Dialog } from 'vant';
 export default {
   props: {},
   components: {
     collapse,
-    ChooseSomeone
+    ChooseSomeone,
+    DepartmentSelection
   },
   data() {
     return {
@@ -110,6 +126,7 @@ export default {
       departmentId: '', // 部门id
       peopleList: [], // 人员数据
       peopleListId: [], // 回显人员ID
+      popupDepartmentShow: false,
 
       dates: '',
       productSchedulingNum: '',
@@ -123,9 +140,56 @@ export default {
 
       peopleListIdObj: null, // 杯换人的所有信息
 
+      dropDownSelectionList: [
+        { value: 0, text: '自己工位' },
+        { value: 1, text: '其他工位' },
+        { value: 2, text: '全部' },
+      ],
+      dropDownSelectionValue: 0
     };
   },
-  computed: {},
+  computed: {
+    canBatchOperationsBePerformed() { // 是否可以批量操作
+      if(this.distributionList.length == 0 || !this.isCanAgree) {
+        return true
+      }
+      const { id = '', name = '' } = this.user
+      const selectedData = this.distributionList.filter(item => item.prodProcedure && item.prodProcedure.isSelected)
+      for(let i in selectedData) {
+        const { checkboxDisabled, teamNames = '' } = selectedData[i]
+        if(checkboxDisabled || (teamNames && teamNames.indexOf(name) == -1)) {
+          return true
+        }
+      }
+      return false
+    },
+    isItPossibleToReassign() { // 是否可以改派
+      if(this.distributionList.length == 0 || !this.isCanAgree) {
+        return true
+      }
+      const selectedData = this.distributionList.filter(item => item.prodProcedure && item.prodProcedure.isSelected)
+      for(let i in selectedData) {
+          const { prodProcedureTeamList = [] } = selectedData[i]
+          if((prodProcedureTeamList || []).length) {
+            return true
+          }
+      }
+      return false
+    },
+    doYouWantToCancelTheReassignment() { // 是否取消改派
+      if(this.distributionList.length == 0 || !this.isCanAgree) {
+        return true
+      }
+      const selectedData = this.distributionList.filter(item => item.prodProcedure && item.prodProcedure.isSelected)
+      for(let i in selectedData) {
+          const { isTransfer = 0 } = selectedData[i]
+          if(!isTransfer) {
+            return true
+          }
+      }
+      return false
+    }
+  },
   watch: {},
   created() {},
   mounted() {
@@ -147,6 +211,63 @@ export default {
     this.getPeople()
   },
   methods: {
+    cancelReassignment() {
+      const str = (this.distributionList || []).map(item => item.prodProcedure && item.prodProcedure.name).join(',')
+      const ids = (this.distributionList || []).map(item => item.id).join(',')
+      Dialog.confirm({
+        title: '取消改派',
+        message: `确定取消【${str}】改派吗?`,
+      }).then(() => {
+        this.$axios.post(
+          "/plan/planDetailCancelTransfer",
+          {
+            ids: ids,
+          }
+        ).then(res => {
+          if (res.code == "ok") {
+            this.$toast.success('取消改派成功');
+            this.getDistributionList()
+          } else {
+            this.$toast.clear();
+            this.$toast.fail(res.msg);
+          }
+        }).catch(err => { this.$toast.clear(); })
+      }).catch(() => {
+        // on cancel
+      });
+    },
+    changeDepartmentSelection(list) {
+      const { value, label } = list[0] || {}
+      console.log(this.distributionList, label)
+      const str = (this.distributionList || []).map(item => item.prodProcedure && item.prodProcedure.name).join(',')
+      const ids = (this.distributionList || []).map(item => item.id).join(',')
+      Dialog.confirm({
+        title: '改派',
+        message: `确定将【${str}】改派给【${label}】部门吗?`,
+      }).then(() => {
+        this.$axios.post(
+          "/plan/planDetailTransStation",
+          {
+            ids: ids,
+            stationId: value
+          }
+        ).then(res => {
+          if (res.code == "ok") {
+            this.$toast.success('改派成功');
+            this.getDistributionList()
+            this.popupDepartmentShow = false
+          } else {
+            this.$toast.clear();
+            this.$toast.fail(res.msg);
+          }
+        }).catch(err => { this.$toast.clear(); })
+      }).catch(() => {
+        // on cancel
+      });
+    },
+    showReassignment() {
+      this.popupDepartmentShow = true
+    },
     cancellationReceiveBatch(){
       let ids = ''
         console.log(this.user)
@@ -346,9 +467,10 @@ export default {
       }
     },
     getDistributionList() {
-      this.$axios.post('/plan/planDetail', {
+      this.$axios.post('/plan/planDetailWithStation', {
         id: this.id,
-        type: this.type
+        type: this.type,
+        stationType: this.dropDownSelectionValue
       })
       .then(res => {
         if (res.code == "ok") {
@@ -371,6 +493,31 @@ export default {
           this.$toast.fail(res.msg);
         }
       }).catch(err => { this.$toast.clear(); });
+      // this.$axios.post('/plan/planDetail', {
+      //   id: this.id,
+      //   type: this.type
+      // })
+      // .then(res => {
+      //   if (res.code == "ok") {
+      //     res.data.forEach(item => { item.flg = false })
+      //     let arr = res.data
+      //     for(var i in arr) {
+      //       arr[i].checkboxDisabled = true
+      //       if(arr[i].prodProcedureTeamList&&arr[i].prodProcedureTeamList.length > 0) {
+      //         let arrList = arr[i].prodProcedureTeamList.filter(item => (item.status == 0||item.status == 1) && item.userId==this.user.id)
+      //         console.log(arrList, 'arrList')
+      //         if(arrList.length > 0) {
+      //           arr[i].checkboxDisabled = false
+      //         }
+      //       }
+      //     }
+      //     // this.distributionList = res.data
+      //     this.distributionList = arr
+      //   } else {
+      //     this.$toast.clear();
+      //     this.$toast.fail(res.msg);
+      //   }
+      // }).catch(err => { this.$toast.clear(); });
     },
     //换人
     changePeople(item,newPeopleId){
@@ -459,11 +606,18 @@ export default {
   * {
     box-sizing: border-box;
   }
+  .dropDownSelection {
+    position:fixed;
+    top: 46px;
+    left: 0;
+    width: 100%;
+    z-index: 3;
+  }
   .formBatch{
         position: fixed;
         bottom: 0;
         width: 100%;
-        z-index: 2;
+        z-index: 1;
         background-color: #fff;
         display: flex;
         justify-content: space-between;
@@ -485,7 +639,7 @@ export default {
   .distribution {
     width: 100%;
     height: 100%;
-    padding: 54px 15px 15px 15px;
+    padding: 105px 15px 45px 15px;
     display: flex;
     flex-direction: column;
     flex-wrap: wrap;

+ 16 - 1
fhKeeper/formulahousekeeper/timesheet-workshop/src/routes.js

@@ -42,6 +42,9 @@ import quanx from './views/quanx/quanx'
 // 项目表单设置
 import projectForm from './views/project/projectForm'
 
+// 出勤日历
+import attendanceCalendar from './views/attendanceCalendar/index.vue'
+
 Vue.use(Router)
 
 export const fixedRouter = [
@@ -77,7 +80,19 @@ export const allRouters = [
             { path: '/report', component: daily, name: '查看报工' },
         ],
     },
-   
+
+    // 出勤日历
+    {
+        path: '/',
+        component: Home,
+        name: '出勤日历',
+        text: 'navigation.reports',
+        iconCls: 'iconfont firerock-icontianbao1',
+        leaf: true,
+        children: [
+            { path: '/attendanceCalendar', component: attendanceCalendar, name: '出勤日历' },
+        ],
+    },
     //成本统计
     {
         path: '/',

+ 142 - 0
fhKeeper/formulahousekeeper/timesheet-workshop/src/views/attendanceCalendar/attendanceCalendar.vue

@@ -0,0 +1,142 @@
+<template>
+  <div class="calendar" v-loading="refreshDataLoading">
+    <el-calendar v-model="calendarValue">
+      <div slot="dateCell" slot-scope="data" class="calendar-cell">
+        <div>{{ dayjs(data.date).format("DD") }}</div>
+        <div class="calendar-cell-text">周 {{ getDateRow(data.data.day).dayOfWeek }}</div>
+        <div :class="`calendar-cel-leftTop ${specialDay[getDateRow(data.data.day).type]}`">{{ specialDayPositioningText[getDateRow(data.data.day).type] }}</div>
+      </div>
+    </el-calendar>
+  </div>
+</template>
+
+<script>
+export default {
+  watch: {
+    // calendarValue() {
+    //   this.getCalendarData()
+    // },
+    calendarValue(newVal, oldVal) {
+      const news = this.dayjs(new Date(newVal)).format("YYYY-MM")
+      const olds = this.dayjs(new Date(oldVal)).format("YYYY-MM")
+      if(news != olds) {
+        this.getCalendarData()
+      }
+    },
+  },
+  data() {
+    return {
+      calendarValue: this.dayjs().format("YYYY-MM"),
+      calendarData: [],
+      specialDay: ['rest', 'compensatoryLeave',  'goToWork'],
+      specialDayText: ['restText', 'compensatoryLeaveText', 'goToWorkText'],
+      specialDayPositioningText: ['休', '调', '班'],
+      refreshDataLoading: false,
+    };
+  },
+  methods: {
+    refreshData({ dateVal, directRequest = false }) {
+      this.calendarValue = dateVal
+      if (directRequest) {
+        this.getCalendarData()
+      }
+    },
+    getCalendarData() {
+      const dateVal = this.dayjs(new Date(this.calendarValue)).format("YYYY-MM");
+      this.refreshDataLoading = true
+      this.postData(`/attendance/getCalendarByMonth`, { month: dateVal }).then((res) => {
+        this.calendarData = res.data || []
+      }).finally(() => {
+        this.refreshDataLoading = false
+      })
+    },
+    getDateRow(dateVal) {
+      return this.calendarData.find(item => item.date == dateVal) || {}
+    },
+    // 单独封装请求
+    async postData(urls, param) {
+      return new Promise((resolve, reject) => {
+        this.http.post(urls, { ...param },
+          res => {
+            if (res.code == 'ok') {
+              resolve(res)
+            } else {
+              this.$message({
+                message: res.msg,
+                type: 'error'
+              })
+              reject(res)
+            }
+            resolve(res)
+          },
+          error => {
+            this.$message({
+              message: error,
+              type: "error"
+            });
+            reject(error)
+          }
+        )
+      });
+    },
+  },
+  created() { },
+  mounted() { },
+  beforeDestroy() { },
+};
+</script>
+
+<style lang="scss" scoped>
+.calendar {
+  width: 100%;
+  height: 100%;
+
+  .calendar-cell {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    
+    .calendar-cel-leftTop {
+      position: absolute;
+      padding: 8px;
+      top: -8px;
+      left: -8px;
+      color: #fff;
+      font-size: 12px;
+    }
+  }
+
+  .calendar-cell-text {
+    margin-top: 10px;
+  }
+
+  .goToWork {
+    background: #f56c6c;
+  }
+
+  .compensatoryLeave {
+    background: #e6a23c;
+  }
+
+  .rest {
+    background: #85ce61;
+  }
+
+  .goToWorkText {
+    color: #f56c6c;
+  }
+
+  .compensatoryLeaveText {
+    color: #e6a23c;
+  }
+
+  .restText {
+    color: #85ce61;
+  }
+}
+</style>

+ 152 - 0
fhKeeper/formulahousekeeper/timesheet-workshop/src/views/attendanceCalendar/employeeAttendance.vue

@@ -0,0 +1,152 @@
+<template>
+  <div class="calendar">
+    <el-table :data="tableDataList" border style="width: 100%;" height="70vh" v-loading="refreshDataLoading"
+      :span-method="mergeCells">
+      <el-table-column prop="name" label="员工姓名" />
+      <el-table-column prop="jobNumber" label="工号" />
+      <el-table-column prop="deptName" label="部门" />
+      <el-table-column prop="clockStartTime" label="上班打卡" />
+      <el-table-column prop="clockEndTime" label="下班打卡" />
+      <el-table-column prop="workHour" label="打卡时长(h)" />
+    </el-table>
+    <div class="bottomPagination">
+      <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="pageIndex"
+        :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" layout="total, prev, pager, next, sizes"
+        :total="total">
+      </el-pagination>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  watch: {
+    calendarValue() {
+      this.getCalendarData()
+    },
+  },
+  data() {
+    return {
+      calendarValue: this.dayjs().format("YYYY-MM"),
+      tableDataList: [],
+      userId: '',
+      refreshDataLoading: false,
+      pageIndex: 1,
+      pageSize: 10,
+      total: 0,
+    };
+  },
+  methods: {
+    // 合并单元格逻辑
+    mergeCells({ row, column, rowIndex, columnIndex }) {
+      const mergeFields = ['name', 'jobNumber', 'deptName'];
+      const field = column.property;
+
+      if (!mergeFields.includes(field)) return [1, 1];
+
+      const data = this.tableDataList;
+      let rowspan = 1;
+      let colspan = 1;
+
+      for (let i = rowIndex + 1; i < data.length; i++) {
+        const current = data[i];
+        const prev = data[rowIndex];
+        // 如果 name、jobNumber、deptName 相同才继续合并
+        if (
+          prev.name === current.name &&
+          prev.jobNumber === current.jobNumber &&
+          prev.deptName === current.deptName
+        ) {
+          rowspan++;
+        } else {
+          break;
+        }
+      }
+
+      // 如果是起始行,返回 rowspan
+      if (
+        rowIndex === 0 ||
+        data[rowIndex - 1].name !== row.name ||
+        data[rowIndex - 1].jobNumber !== row.jobNumber ||
+        data[rowIndex - 1].deptName !== row.deptName
+      ) {
+        return [rowspan, colspan];
+      } else {
+        // 非起始行返回 [0, 0],即隐藏
+        return [0, 0];
+      }
+    },
+    refreshData({ dateVal, directRequest = false, userId = '' }) {
+      this.calendarValue = dateVal
+      this.userId = userId
+      if (directRequest) {
+        this.pageIndex = 1
+        this.getCalendarData()
+      }
+    },
+    getCalendarData() {
+      const dateVal = this.dayjs(new Date(this.calendarValue)).format("YYYY-MM");
+      this.refreshDataLoading = true
+      this.postData(`/attendance-staff/getListData`, {
+        month: dateVal,
+        userId: this.userId,
+        pageIndex: this.pageIndex,
+        pageSize: this.pageSize
+      }).then((res) => {
+        this.tableDataList = (res.data && res.data.records) || []
+        this.total = (res.data && res.data.total) || 0
+      }).finally(() => {
+        this.refreshDataLoading = false
+      })
+    },
+
+    handleSizeChange(val) {
+      this.pageSize = val
+      this.pageIndex = 1
+      this.getCalendarData()
+    },
+    handleCurrentChange(val) {
+      this.pageIndex = val
+      this.getCalendarData()
+    },
+    // 单独封装请求
+    async postData(urls, param) {
+      return new Promise((resolve, reject) => {
+        this.http.post(urls, { ...param },
+          res => {
+            if (res.code == 'ok') {
+              resolve(res)
+            } else {
+              this.$message({
+                message: res.msg,
+                type: 'error'
+              })
+              reject(res)
+            }
+            resolve(res)
+          },
+          error => {
+            this.$message({
+              message: error,
+              type: "error"
+            });
+            reject(error)
+          }
+        )
+      });
+    },
+  },
+  created() { },
+  mounted() { },
+  beforeDestroy() { },
+};
+</script>
+
+<style lang="scss" scoped>
+.bottomPagination {
+  width: 100%;
+  display: flex;
+  justify-content: flex-end;
+  padding: 20px 0;
+}
+</style>

+ 190 - 0
fhKeeper/formulahousekeeper/timesheet-workshop/src/views/attendanceCalendar/employeeCalendar.vue

@@ -0,0 +1,190 @@
+<template>
+  <div class="calendar" v-loading="refreshDataLoading">
+    <div class="calendar-left">
+      <el-calendar v-model="calendarValue">
+        <div slot="dateCell" slot-scope="data" class="calendar-cell" @click="setRightRow(data.data.day)">
+          <div>{{ dayjs(data.date).format("DD") }}</div>
+          <div class="calendar-cell-text" :style="`color: ${getDateRow(data.data.day).color}`">{{ getDateRow(data.data.day).attendanceTypeName }}</div>
+        </div>
+      </el-calendar>
+    </div>
+    <div class="calendar-right">
+      <template v-if="personnelAttendanceRowList.attendanceTypeName">
+        <div class="list">考勤时间:{{ personnelAttendanceRowList.clockDate }}</div>
+        <div class="list">考勤状态:<span :style="`color: ${personnelAttendanceRowList.color || '#000'}`">{{ personnelAttendanceRowList.attendanceTypeName }}</span></div>
+        <div class="list" v-for="(item, index) in personnelAttendanceRowList.maplist || []" :key="index">
+          {{ item.msg }} <span :style="`color: ${item.color || '#000'}`">{{ item.res }}</span>
+        </div>
+      </template>
+      <template v-else>
+        <template v-if="!personnelAttendanceRowList.attendanceType && personnelAttendanceRowList.attendanceType != 0">
+          <div class="listText">暂无考勤</div>
+        </template>
+        <div class="listText" v-else>
+          请选择日期
+          <div v-if="haveYouViewedAllOfThem" style="font-size: 18px;margin-top:20px;">(也可选择单个员工查看)</div>
+        </div>
+      </template>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  watch: {
+    calendarValue(newVal, oldVal) {
+      const news = this.dayjs(new Date(newVal)).format("YYYY-MM")
+      const olds = this.dayjs(new Date(oldVal)).format("YYYY-MM")
+      if(news != olds) {
+        this.getCalendarData()
+      }
+    },
+  },
+  data() {
+    return {
+      calendarValue: this.dayjs().format("YYYY-MM"),
+      calendarData: [],
+      userId: '',
+      specialDay: ['rest', 'compensatoryLeave', 'goToWork'],
+      specialDayText: ['restText', 'compensatoryLeaveText', 'goToWorkText'],
+      specialDayPositioningText: ['休', '调', '班'],
+      refreshDataLoading: false,
+      personnelAttendanceRowList: {
+        maplist: []
+      },
+      haveYouViewedAllOfThem: false,
+      user: JSON.parse(sessionStorage.getItem('user')),
+    };
+  },
+  methods: {
+    setRightRow(day) {
+      this.personnelAttendanceRowList = this.getDateRow(day)
+    },
+    refreshData({ dateVal, directRequest = false, userId = '' }) {
+      this.calendarValue = dateVal
+      this.userId = userId
+      if (directRequest) {
+        this.getCalendarData()
+      }
+    },
+    getCalendarData() {
+      const dateVal = this.dayjs(new Date(this.calendarValue)).format("YYYY-MM");
+      this.refreshDataLoading = true
+      this.postData(`/attendance-staff/getAttendanceUserData`, { month: dateVal, userId: this.userId }).then((res) => {
+        this.calendarData = res.data || []
+      }).finally(() => {
+        this.refreshDataLoading = false
+      })
+    },
+    getDateRow(dateVal) {
+      return this.calendarData.find(item => item.clockDate == dateVal) || {}
+    },
+    // 单独封装请求
+    async postData(urls, param) {
+      return new Promise((resolve, reject) => {
+        this.http.post(urls, { ...param },
+          res => {
+            if (res.code == 'ok') {
+              resolve(res)
+            } else {
+              this.$message({
+                message: res.msg,
+                type: 'error'
+              })
+              reject(res)
+            }
+            resolve(res)
+          },
+          error => {
+            this.$message({
+              message: error,
+              type: "error"
+            });
+            reject(error)
+          }
+        )
+      });
+    },
+  },
+  created() { },
+  mounted() { 
+    // 查看全部员工
+    const permissionList = this.user.functionList || []
+    const list = permissionList.filter(item => item.name == '查看全部员工')
+    if(list.length) {
+      this.haveYouViewedAllOfThem = true
+    }
+  },
+  beforeDestroy() { },
+};
+</script>
+
+<style lang="scss" scoped>
+.calendar {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: row;
+
+  .calendar-cell {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+  }
+
+  .list {
+    padding: 10px 0;
+  }
+
+  .calendar-right {
+    width: 30%;
+    padding: 20px;
+    overflow-y: auto;
+  }
+
+  .calendar-left {
+    width: 70%;
+  }
+
+  .calendar-cell-text {
+    margin-top: 10px;
+  }
+
+  .goToWork {
+    background: #f56c6c;
+  }
+
+  .compensatoryLeave {
+    background: #e6a23c;
+  }
+
+  .rest {
+    background: #85ce61;
+  }
+
+  .goToWorkText {
+    color: #f56c6c;
+  }
+
+  .compensatoryLeaveText {
+    color: #e6a23c;
+  }
+
+  .restText {
+    color: #85ce61;
+  }
+
+  .listText {
+    text-align: center;
+    font-size: 24px;
+    color: #999;
+    padding-top: 30px;
+    display: flex;
+    flex-direction: column;
+  }
+}
+</style>

+ 482 - 0
fhKeeper/formulahousekeeper/timesheet-workshop/src/views/attendanceCalendar/index copy.vue

@@ -0,0 +1,482 @@
+<template>
+  <div class="calendarAttendance">
+    <!-- 头部 -->
+    <div class="calendarAttendance-title">
+      <div class="calendarAttendance-title-left">
+        <el-date-picker v-model="currentMonth" type="month" placeholder="选择月" size="small" value-format="yyyy-MM"
+          :clearable="false" @change="refreshTabData(false)"></el-date-picker>
+
+        <el-radio-group v-model="tabValue" size="small" style="margin-left: 20px;" @change="refreshTabData(true)">
+          <el-radio-button label="出勤日历"></el-radio-button>
+          <el-radio-button label="员工日历"></el-radio-button>
+          <el-radio-button label="员工考勤"></el-radio-button>
+        </el-radio-group>
+
+        <template v-if="tabValue != '出勤日历' && haveYouViewedAllOfThem">
+          <el-select v-model="userId" placeholder="请选择" clearable filterable @change="refreshTabData(true)" size="small"
+            style="margin-left: 20px;">
+            <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"></el-option>
+          </el-select>
+        </template>
+      </div>
+      <div class="calendarAttendance-title-right">
+        <el-button type="primary" size="small" @click="displayExamRules()">考勤规则</el-button>
+        <el-button type="primary" size="small" @click="importAttendanceShow()">导入考勤</el-button>
+        <el-button type="primary" size="small" @click="displayHighTemperatureDaySettings()">高温日设置</el-button>
+        <el-button type="primary" size="small" @click="displaySpecialDaySettings()">特殊节假日设置</el-button>
+      </div>
+    </div>
+
+    <!-- 主体内容 -->
+    <div class="calendarAttendance-content">
+      <transition name="fade" mode="out-in">
+        <component :is="currentTabComponent" :key="tabValue" ref="tabComponentRef" />
+      </transition>
+    </div>
+
+    <!-- 导入考勤 -->
+    <el-dialog title="导入考勤" :visible.sync="importAttendanceVisable" customClass="customWidth" width="500px">
+      <p>1. 下载
+        <el-link type="primary" style="margin-left:5px;" :underline="false" href="./upload/考勤模板.xlsx"
+          :download="`考勤导入模板.xlsx`">考勤导入模板.xlsx</el-link>
+      </p>
+      <p>1. 导入月份 
+        <el-date-picker v-model="currentMonthExport" type="month" placeholder="选择月" size="small" value-format="yyyy-MM" style="margin-left:5px;"
+          :clearable="false"></el-date-picker>
+      </p>
+      <!-- <p>2. 填写excel模板,请确保模板中的项目和人员已添加到系统中。</p> -->
+      <p style="display: flex;justify-content: center;padding:1em 0">
+        <el-upload ref="upload" action="#" :limit="1" :http-request="batchImportData" :show-file-list="false">
+          <el-button type="primary" :underline="false" :loading="startImportingLoading">开始导入</el-button>
+        </el-upload>
+      </p>
+    </el-dialog>
+
+    <!-- 高温日设置 -->
+    <el-dialog title="高温日设置" :visible.sync="highTemperatureDayVisable" customClass="customWidth" width="900px"
+      top="6.5vh">
+      <div>
+        <el-table ref="highTemperatureDayTable" :data="highTemperatureDayList" style="width: 100%" height="50vh" border
+          @selection-change="highTemperatureSelectionChange">
+          <el-table-column type="selection" width="55"></el-table-column>
+          <el-table-column prop="date" label="日期" width="250">
+            <template slot-scope="scope">
+              {{ scope.row.startDate }} 至 {{ scope.row.endDate }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="remark" label="描述"></el-table-column>
+          <el-table-column label="编辑" width="100">
+            <template slot-scope="scope">
+              <el-button type="primary" size="small" @click="editHighTemperatureDay(scope.row)">编辑</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="bottomOfHighTemperatureDay">
+        <el-button type="danger" size="small" :disabled="!selectHighTemperatureDay.length"
+          @click="batchDeleteHighTemperature()">批量删除</el-button>
+        <div>
+          <el-button size="small" @click="highTemperatureDayVisable = false">关闭</el-button>
+          <el-button type="primary" size="small" @click="editHighTemperatureDay()">新增高温日</el-button>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 新增编辑高温日 -->
+    <el-dialog title="新增编辑高温日" :visible.sync="editHighTemperatureDayVisible" customClass="customWidth" width="900px"
+      top="6.5vh">
+      <div>
+        <el-form ref="form" :model="hotDayForm" label-width="80px">
+          <el-form-item label="时间段">
+            <el-date-picker v-model="hotDayForm.timeSlot" type="daterange" range-separator="至" start-placeholder="开始日期"
+              end-placeholder="结束日期" value-format="yyyy-MM-dd" :clearable="false"></el-date-picker>
+          </el-form-item>
+          <el-form-item label="描述">
+            <el-input type="textarea" :rows="8" v-model="hotDayForm.remark"></el-input>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="bottomOfHighTemperatureDay">
+        <el-button size="small" @click="editHighTemperatureDayVisible = false">关闭</el-button>
+        <el-button type="primary" size="small" @click="saveHighTemperatureDay()" :disabled="!hotDayForm.timeSlot.length">保存</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 特殊节假日设置 -->
+    <el-dialog title="特殊节假日设置" :visible.sync="specialDayVisable" customClass="customWidth" width="900px" top="6.5vh">
+      <div>
+        <el-table ref="specialDayTable" :data="specialDayList" style="width: 100%" height="50vh" border
+          @selection-change="specialDaySelectionChange" v-loading="specialDayLoading">
+          <el-table-column type="selection" width="55"></el-table-column>
+          <el-table-column prop="specialDate" label="日期" width="250"></el-table-column>
+          <el-table-column prop="type" label="类型" width="150">
+            <template slot-scope="scope">
+              {{ scope.row.type === 0 ? '节假日' : '工作日' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="remark" label="描述"></el-table-column>
+          <el-table-column label="编辑" width="100">
+            <template slot-scope="scope">
+              <el-button type="primary" size="small" @click="editSpecialDay(scope.row)">编辑</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="bottomOfHighTemperatureDay">
+        <el-button type="danger" size="small" :disabled="!selectSpecialDay.length"
+          @click="batchDeleteSpecialDay()">批量删除</el-button>
+        <div>
+          <el-button size="small" @click="specialDayVisable = false">关闭</el-button>
+          <el-button type="primary" size="small" @click="editSpecialDay()">新增特殊节假日</el-button>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 新增编辑特殊节假日 -->
+    <el-dialog :title="specialDayForm.id ? '编辑特殊节假日' : '新增特殊节假日'" :visible.sync="editSpecialDayVisible" 
+      customClass="customWidth" width="900px" top="6.5vh">
+      <div>
+        <el-form ref="specialDayFormRef" :model="specialDayForm" label-width="80px">
+          <el-form-item label="日期" required>
+            <el-date-picker v-model="specialDayForm.specialDate" type="date" placeholder="选择日期" 
+              value-format="yyyy-MM-dd" :clearable="false"></el-date-picker>
+          </el-form-item>
+          <el-form-item label="类型" required>
+            <el-select v-model="specialDayForm.type" placeholder="请选择">
+              <el-option label="节假日" :value="0"></el-option>
+              <el-option label="工作日" :value="1"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="描述">
+            <el-input type="textarea" :rows="8" v-model="specialDayForm.remark"></el-input>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="bottomOfHighTemperatureDay">
+        <el-button size="small" @click="editSpecialDayVisible = false">关闭</el-button>
+        <el-button type="primary" size="small" @click="saveSpecialDay()" 
+          :disabled="!specialDayForm.specialDate">保存</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import AttendanceCalendar from './attendanceCalendar.vue';
+import EmployeeCalendar from './employeeCalendar.vue';
+import EmployeeAttendance from './employeeAttendance.vue';
+export default {
+  components: { AttendanceCalendar, EmployeeCalendar, EmployeeAttendance },
+  data() {
+    return {
+      currentMonth: this.dayjs().format("YYYY-MM"),
+      currentMonthExport: '',
+      userId: '',
+      userList: [],
+      highTemperatureDayList: [],
+      selectHighTemperatureDay: [],
+      tabValue: '出勤日历',
+      hotDayForm: {
+        timeSlot: [],
+        remark: ''
+      },
+      importAttendanceVisable: false,
+      highTemperatureDayVisable: false,
+      editHighTemperatureDayVisible: false,
+      highTemperatureDayTableLoading: false,
+      startImportingLoading: false,
+      specialDayVisable: false,
+      specialDayLoading: false,
+      specialDayList: [],
+      selectSpecialDay: [],
+      editSpecialDayVisible: false,
+      specialDayForm: {
+        specialDate: '',
+        type: 0,
+        remark: ''
+      },
+      haveYouViewedAllOfThem: false,
+      user: JSON.parse(sessionStorage.getItem('user')),
+    };
+  },
+  computed: {
+    currentTabComponent() {
+      switch (this.tabValue) {
+        case '出勤日历':
+          return 'AttendanceCalendar';
+        case '员工日历':
+          return 'EmployeeCalendar';
+        case '员工考勤':
+          return 'EmployeeAttendance';
+        default:
+          return 'AttendanceCalendar';
+      }
+    },
+    tabComponentRef() {
+      switch (this.tabValue) {
+        case '出勤日历':
+          return 'attendanceCalendarRef';
+        case '员工日历':
+          return 'employeeCalendarRef';
+        case '员工考勤':
+          return 'employeeAttendanceRef';
+        default:
+          return '';
+      }
+    }
+  },
+  methods: {
+    importAttendanceShow() {
+      this.currentMonthExport = this.currentMonth
+      this.importAttendanceVisable = true
+    },
+    saveHighTemperatureDay() {
+      const formVal = {
+        ...this.hotDayForm,
+        startDate: this.hotDayForm.timeSlot[0],
+        endDate: this.hotDayForm.timeSlot[1],
+      }
+
+      delete formVal.timeSlot;
+
+      this.postData(`/high-temperature-set/saveOrUpdate`, {...formVal}).then((res) => { 
+        this.$message({ type: 'success', message: '保存成功' })
+        this.editHighTemperatureDayVisible = false
+        this.getHighTemperatureDaySettings()
+      })
+    },
+    editHighTemperatureDay(row = false) {
+      if (!row) {
+        this.hotDayForm = {
+          timeSlot: [],
+          remark: ''
+        }
+      } else {
+        this.hotDayForm = {
+          id: row.id,
+          timeSlot: [row.startDate, row.endDate],
+          remark: row.remark
+        }
+      }
+
+      this.editHighTemperatureDayVisible = true
+    },
+    batchDeleteHighTemperature() {
+      this.$confirm('确定删除选中数据吗?', '批量删除', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        const ids = this.selectHighTemperatureDay.map(item => item.id).join(',')
+        this.postData(`/high-temperature-set/deleteHigh`, { ids }).then((res) => {
+          this.$message({ type: 'success', message: '删除成功' })
+          this.getHighTemperatureDaySettings()
+        })
+      })
+
+    },
+    highTemperatureSelectionChange(val) {
+      this.selectHighTemperatureDay = val || []
+    },
+    displayHighTemperatureDaySettings() {
+      this.highTemperatureDayVisable = true
+      this.selectHighTemperatureDay = []
+      this.getHighTemperatureDaySettings()
+    },
+    getHighTemperatureDaySettings() {
+      this.highTemperatureDayTableLoading = true
+      this.postData('/high-temperature-set/list', {}).then(res => {
+        this.selectHighTemperatureDay = []
+        this.highTemperatureDayList = res.data || []
+      }).finally(() => {
+        this.highTemperatureDayTableLoading = false
+      })
+    },
+    getAllPersonnel() {
+      this.postData(`/user/getEmployeeList`, { pageIndex: 1, pageSize: 300, status: 1, departmentId: -1 }).then((res) => {
+        this.userList = res.data.records || []
+      })
+    },
+    batchImportData(item) {
+      let str = item.file.name.split(".");
+      let format = str[str.length - 1];
+      if (format != "xls" && format != "xlsx") {
+        this.$message({
+          message: this.$t('other.PleaseselecttheXLSorXLSXfile'),
+          type: "error"
+        });
+      } else {
+        this.startImportingLoading = true
+        let formData = new FormData();
+        formData.append("file", item.file);
+        formData.append("month", this.currentMonthExport);
+        this.http.uploadFile('/attendance/importAttendanceData', formData,
+          res => {
+            this.startImportingLoading = false
+            this.$refs.upload.clearFiles();
+            if (res.code == "ok") {
+              this.importAttendanceVisable = false
+              this.importResultMsg = `导入成功`
+              this.refreshTabData(true);
+            } else {
+              this.importResultMsg = `导入失败` + res.msg;
+            }
+          },
+          error => {
+            this.startImportingLoading = false
+            this.$refs.upload.clearFiles();
+            this.$message({
+              message: error,
+              type: "error"
+            });
+          });
+      }
+    },
+    // 单独封装请求
+    async postData(urls, param) {
+      return new Promise((resolve, reject) => {
+        this.http.post(urls, { ...param },
+          res => {
+            if (res.code == 'ok') {
+              resolve(res)
+            } else {
+              this.$message({
+                message: res.msg,
+                type: 'error'
+              })
+              reject(res)
+            }
+            resolve(res)
+          },
+          error => {
+            this.$message({
+              message: error,
+              type: "error"
+            });
+            reject(error)
+          }
+        )
+      });
+    },
+    refreshTabData(directRequest = false) {
+      const dateVal = this.currentMonth
+      setTimeout(() => {
+        this.$refs.tabComponentRef.refreshData({ dateVal, directRequest, userId: this.userId });
+      }, 500)
+    },
+    
+    // 特殊节假日设置相关方法
+    displaySpecialDaySettings() {
+      this.specialDayVisable = true
+      this.selectSpecialDay = []
+      this.getSpecialDayList()
+    },
+    getSpecialDayList() {
+      this.specialDayLoading = true
+      this.postData('/special-date-set/list', {}).then(res => {
+        this.specialDayList = res.data || []
+      }).finally(() => {
+        this.specialDayLoading = false
+      })
+    },
+    editSpecialDay(row = false) {
+      if (!row) {
+        this.specialDayForm = {
+          specialDate: '',
+          type: 0,
+          remark: ''
+        }
+      } else {
+        this.specialDayForm = {
+          id: row.id,
+          specialDate: row.specialDate,
+          type: row.type,
+          remark: row.remark
+        }
+      }
+      this.editSpecialDayVisible = true
+    },
+    saveSpecialDay() {
+      this.postData('/special-date-set/saveOrUpdate', this.specialDayForm).then(res => {
+        this.$message({ type: 'success', message: '保存成功' })
+        this.editSpecialDayVisible = false
+        this.getSpecialDayList()
+      })
+    },
+    batchDeleteSpecialDay() {
+      this.$confirm('确定删除选中数据吗?', '批量删除', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        const ids = this.selectSpecialDay.map(item => item.id).join(',')
+        this.postData('/special-date-set/deleteSpecial', { ids }).then(res => {
+          this.$message({ type: 'success', message: '删除成功' })
+          this.getSpecialDayList()
+        })
+      })
+    },
+    specialDaySelectionChange(val) {
+      this.selectSpecialDay = val || []
+    }
+  },
+  created() { },
+  mounted() {
+    this.getAllPersonnel()
+    this.refreshTabData(true)
+    // 查看全部员工
+    const permissionList = this.user.functionList || []
+    const list = permissionList.filter(item => item.name == '查看全部员工')
+    if(list.length) {
+      this.haveYouViewedAllOfThem = true
+    }
+
+    console.log(this.haveYouViewedAllOfThem, '<====== this.haveYouViewedAllOfThem')
+  },
+  beforeDestroy() { },
+};
+</script>
+
+<style lang="scss" scoped>
+.calendarAttendance {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+
+  .calendarAttendance-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 20px 20px;
+    border-bottom: 1px solid #e6e6e6;
+
+    .calendarAttendance-title-left {
+      display: flex;
+      align-items: center;
+    }
+  }
+
+  .calendarAttendance-content {
+    flex: 1;
+    padding: 20px;
+  }
+
+  .bottomOfHighTemperatureDay {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 20px;
+  }
+}
+
+/* 淡入淡出动画 */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.3s ease;
+}
+
+.fade-enter,
+.fade-leave-to {
+  opacity: 0;
+}
+</style>

+ 604 - 0
fhKeeper/formulahousekeeper/timesheet-workshop/src/views/attendanceCalendar/index.vue

@@ -0,0 +1,604 @@
+<template>
+  <div class="calendarAttendance">
+    <!-- 头部 -->
+    <div class="calendarAttendance-title">
+      <div class="calendarAttendance-title-left">
+        <el-date-picker v-model="currentMonth" type="month" placeholder="选择月" size="small" value-format="yyyy-MM"
+          :clearable="false" @change="refreshTabData(false)"></el-date-picker>
+
+        <el-radio-group v-model="tabValue" size="small" style="margin-left: 20px;" @change="refreshTabData(true)">
+          <el-radio-button label="出勤日历"></el-radio-button>
+          <el-radio-button label="员工日历"></el-radio-button>
+          <el-radio-button label="员工考勤"></el-radio-button>
+        </el-radio-group>
+
+        <template v-if="tabValue != '出勤日历' && haveYouViewedAllOfThem">
+          <el-select v-model="userId" placeholder="请选择" clearable filterable @change="refreshTabData(true)" size="small"
+            style="margin-left: 20px;">
+            <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"></el-option>
+          </el-select>
+        </template>
+      </div>
+      <div class="calendarAttendance-title-right">
+        <el-button type="primary" size="small" @click="displayExamRules()">考勤规则</el-button>
+        <el-button type="primary" size="small" @click="importAttendanceShow()">导入考勤</el-button>
+        <el-button type="primary" size="small" @click="displayHighTemperatureDaySettings()">高温日设置</el-button>
+        <el-button type="primary" size="small" @click="displaySpecialDaySettings()">特殊节假日设置</el-button>
+      </div>
+    </div>
+
+    <!-- 主体内容 -->
+    <div class="calendarAttendance-content">
+      <transition name="fade" mode="out-in">
+        <component :is="currentTabComponent" :key="tabValue" ref="tabComponentRef" />
+      </transition>
+    </div>
+
+    <!-- 导入考勤 -->
+    <el-dialog title="导入考勤" :visible.sync="importAttendanceVisable" customClass="customWidth" width="500px">
+      <p>1. 下载
+        <el-link type="primary" style="margin-left:5px;" :underline="false" href="./upload/考勤模板.xlsx"
+          :download="`考勤导入模板.xlsx`">考勤导入模板.xlsx</el-link>
+      </p>
+      <p>1. 导入月份 
+        <el-date-picker v-model="currentMonthExport" type="month" placeholder="选择月" size="small" value-format="yyyy-MM" style="margin-left:5px;"
+          :clearable="false"></el-date-picker>
+      </p>
+      <!-- <p>2. 填写excel模板,请确保模板中的项目和人员已添加到系统中。</p> -->
+      <p style="display: flex;justify-content: center;padding:1em 0">
+        <el-upload ref="upload" action="#" :limit="1" :http-request="batchImportData" :show-file-list="false">
+          <el-button type="primary" :underline="false" :loading="startImportingLoading">开始导入</el-button>
+        </el-upload>
+      </p>
+    </el-dialog>
+
+    <!-- 高温日设置 -->
+    <el-dialog title="高温日设置" :visible.sync="highTemperatureDayVisable" customClass="customWidth" width="900px"
+      top="6.5vh">
+      <div>
+        <el-table ref="highTemperatureDayTable" :data="highTemperatureDayList" style="width: 100%" height="50vh" border
+          @selection-change="highTemperatureSelectionChange">
+          <el-table-column type="selection" width="55"></el-table-column>
+          <el-table-column prop="date" label="日期" width="250">
+            <template slot-scope="scope">
+              {{ scope.row.startDate }} 至 {{ scope.row.endDate }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="remark" label="描述"></el-table-column>
+          <el-table-column label="编辑" width="100">
+            <template slot-scope="scope">
+              <el-button type="primary" size="small" @click="editHighTemperatureDay(scope.row)">编辑</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="bottomOfHighTemperatureDay">
+        <el-button type="danger" size="small" :disabled="!selectHighTemperatureDay.length"
+          @click="batchDeleteHighTemperature()">批量删除</el-button>
+        <div>
+          <el-button size="small" @click="highTemperatureDayVisable = false">关闭</el-button>
+          <el-button type="primary" size="small" @click="editHighTemperatureDay()">新增高温日</el-button>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 新增编辑高温日 -->
+    <el-dialog title="新增编辑高温日" :visible.sync="editHighTemperatureDayVisible" customClass="customWidth" width="900px"
+      top="6.5vh">
+      <div>
+        <el-form ref="form" :model="hotDayForm" label-width="80px">
+          <el-form-item label="时间段">
+            <el-date-picker v-model="hotDayForm.timeSlot" type="daterange" range-separator="至" start-placeholder="开始日期"
+              end-placeholder="结束日期" value-format="yyyy-MM-dd" :clearable="false"></el-date-picker>
+          </el-form-item>
+          <el-form-item label="描述">
+            <el-input type="textarea" :rows="8" v-model="hotDayForm.remark"></el-input>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="bottomOfHighTemperatureDay">
+        <el-button size="small" @click="editHighTemperatureDayVisible = false">关闭</el-button>
+        <el-button type="primary" size="small" @click="saveHighTemperatureDay()" :disabled="!hotDayForm.timeSlot.length">保存</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 特殊节假日设置 -->
+    <el-dialog title="特殊节假日设置" :visible.sync="specialDayVisable" customClass="customWidth" width="900px" top="6.5vh">
+      <div>
+        <el-table ref="specialDayTable" :data="specialDayList" style="width: 100%" height="50vh" border
+          @selection-change="specialDaySelectionChange" v-loading="specialDayLoading">
+          <el-table-column type="selection" width="55"></el-table-column>
+          <el-table-column prop="specialDate" label="日期" width="250"></el-table-column>
+          <el-table-column prop="type" label="类型" width="150">
+            <template slot-scope="scope">
+              {{ scope.row.type === 0 ? '节假日' : '工作日' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="remark" label="描述"></el-table-column>
+          <el-table-column label="编辑" width="100">
+            <template slot-scope="scope">
+              <el-button type="primary" size="small" @click="editSpecialDay(scope.row)">编辑</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="bottomOfHighTemperatureDay">
+        <el-button type="danger" size="small" :disabled="!selectSpecialDay.length"
+          @click="batchDeleteSpecialDay()">批量删除</el-button>
+        <div>
+          <el-button size="small" @click="specialDayVisable = false">关闭</el-button>
+          <el-button type="primary" size="small" @click="editSpecialDay()">新增特殊节假日</el-button>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 新增编辑特殊节假日 -->
+    <el-dialog :title="specialDayForm.id ? '编辑特殊节假日' : '新增特殊节假日'" :visible.sync="editSpecialDayVisible" 
+      customClass="customWidth" width="900px" top="6.5vh">
+      <div>
+        <el-form ref="specialDayFormRef" :model="specialDayForm" label-width="80px">
+          <el-form-item label="日期" required>
+            <el-date-picker v-model="specialDayForm.specialDate" type="date" placeholder="选择日期" 
+              value-format="yyyy-MM-dd" :clearable="false"></el-date-picker>
+          </el-form-item>
+          <el-form-item label="类型" required>
+            <el-select v-model="specialDayForm.type" placeholder="请选择">
+              <el-option label="节假日" :value="0"></el-option>
+              <el-option label="工作日" :value="1"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="描述">
+            <el-input type="textarea" :rows="8" v-model="specialDayForm.remark"></el-input>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="bottomOfHighTemperatureDay">
+        <el-button size="small" @click="editSpecialDayVisible = false">关闭</el-button>
+        <el-button type="primary" size="small" @click="saveSpecialDay()" 
+          :disabled="!specialDayForm.specialDate">保存</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 考勤规则 -->
+    <el-dialog title="考勤规则" :visible.sync="attendanceRulesVisible" customClass="customWidth" width="900px" top="6.5vh">
+      <div>
+        <el-table ref="attendanceRulesTable" :data="attendanceRulesList" style="width: 100%" height="50vh" border
+          v-loading="attendanceRulesLoading">
+          <el-table-column type="index" label="序号" width="60"></el-table-column>
+          <el-table-column prop="name" label="考勤状态" width="150"></el-table-column>
+          <el-table-column label="上班打卡时间范围">
+            <template slot-scope="scope">
+              {{ scope.row.beginWorkStartTime }} - {{ scope.row.beginWorkEndTime }}
+            </template>
+          </el-table-column>
+          <el-table-column label="下班打卡时间范围">
+            <template slot-scope="scope">
+              {{ scope.row.endWorkStartTime }} - {{ scope.row.endWorkEndTime }}
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="100">
+            <template slot-scope="scope">
+              <el-button type="primary" size="small" @click="editAttendanceRule(scope.row)">编辑</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="bottomOfHighTemperatureDay">
+        <div></div>
+        <el-button size="small" @click="attendanceRulesVisible = false">关闭</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 编辑考勤规则 -->
+    <el-dialog :title="'编辑考勤规则 - ' + attendanceRuleForm.name" :visible.sync="editAttendanceRuleVisible" 
+    customClass="customWidth" width="900px" top="6.5vh">
+      <div>
+        <el-form ref="attendanceRuleFormRef" :model="attendanceRuleForm" label-width="120px" :rules="attendanceRuleRules">
+          <el-form-item label="上班开始时间" prop="beginWorkStartTime" required>
+            <el-time-picker v-model="attendanceRuleForm.beginWorkStartTime" format="HH:mm" value-format="HH:mm" :clearable="false"></el-time-picker>
+          </el-form-item>
+          <el-form-item label="上班结束时间" prop="beginWorkEndTime" required>
+            <el-time-picker v-model="attendanceRuleForm.beginWorkEndTime" format="HH:mm" value-format="HH:mm" :clearable="false"></el-time-picker>
+          </el-form-item>
+          <el-form-item label="下班开始时间" prop="endWorkStartTime" required>
+            <el-time-picker v-model="attendanceRuleForm.endWorkStartTime" format="HH:mm" value-format="HH:mm" :clearable="false"></el-time-picker>
+          </el-form-item>
+          <el-form-item label="下班结束时间" prop="endWorkEndTime" required>
+            <el-time-picker v-model="attendanceRuleForm.endWorkEndTime" format="HH:mm" value-format="HH:mm" :clearable="false"></el-time-picker>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="bottomOfHighTemperatureDay">
+        <el-button size="small" @click="editAttendanceRuleVisible = false">关闭</el-button>
+        <el-button type="primary" size="small" @click="saveAttendanceRule()">保存</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import AttendanceCalendar from './attendanceCalendar.vue';
+import EmployeeCalendar from './employeeCalendar.vue';
+import EmployeeAttendance from './employeeAttendance.vue';
+export default {
+  components: { AttendanceCalendar, EmployeeCalendar, EmployeeAttendance },
+  data() {
+    return {
+      currentMonth: this.dayjs().format("YYYY-MM"),
+      currentMonthExport: '',
+      userId: '',
+      userList: [],
+      highTemperatureDayList: [],
+      selectHighTemperatureDay: [],
+      tabValue: '出勤日历',
+      hotDayForm: {
+        timeSlot: [],
+        remark: ''
+      },
+      importAttendanceVisable: false,
+      highTemperatureDayVisable: false,
+      editHighTemperatureDayVisible: false,
+      highTemperatureDayTableLoading: false,
+      startImportingLoading: false,
+      specialDayVisable: false,
+      specialDayLoading: false,
+      specialDayList: [],
+      selectSpecialDay: [],
+      editSpecialDayVisible: false,
+      specialDayForm: {
+        specialDate: '',
+        type: 0,
+        remark: ''
+      },
+      attendanceRulesVisible: false,
+      attendanceRulesLoading: false,
+      attendanceRulesList: [],
+      editAttendanceRuleVisible: false,
+      attendanceRuleForm: {
+        id: '',
+        name: '',
+        beginWorkStartTime: '',
+        beginWorkEndTime: '',
+        endWorkStartTime: '',
+        endWorkEndTime: ''
+      },
+      
+      haveYouViewedAllOfThem: false,
+      user: JSON.parse(sessionStorage.getItem('user')),
+    };
+  },
+  computed: {
+    currentTabComponent() {
+      switch (this.tabValue) {
+        case '出勤日历':
+          return 'AttendanceCalendar';
+        case '员工日历':
+          return 'EmployeeCalendar';
+        case '员工考勤':
+          return 'EmployeeAttendance';
+        default:
+          return 'AttendanceCalendar';
+      }
+    },
+    tabComponentRef() {
+      switch (this.tabValue) {
+        case '出勤日历':
+          return 'attendanceCalendarRef';
+        case '员工日历':
+          return 'employeeCalendarRef';
+        case '员工考勤':
+          return 'employeeAttendanceRef';
+        default:
+          return '';
+      }
+    }
+  },
+  methods: {
+    // 显示考勤规则
+    displayExamRules() {
+      this.attendanceRulesVisible = true
+      this.getAttendanceRules()
+    },
+    
+    // 获取考勤规则
+    getAttendanceRules() {
+      this.attendanceRulesLoading = true
+      this.postData('/attendance-rule/getAllList', {}).then(res => {
+        this.attendanceRulesList = (res.data || []).map(item => {
+          return {
+            ...item,
+            beginWorkStartTime: item.beginWorkStartTime && item.beginWorkStartTime.slice(0, 5),
+            beginWorkEndTime: item.beginWorkEndTime && item.beginWorkEndTime.slice(0, 5),
+            endWorkStartTime: item.endWorkStartTime && item.endWorkStartTime.slice(0, 5),
+            endWorkEndTime: item.endWorkEndTime && item.endWorkEndTime.slice(0, 5),
+          }
+        })
+      }).finally(() => {
+        this.attendanceRulesLoading = false
+      })
+    },
+    
+    // 编辑考勤规则
+    editAttendanceRule(row) {
+      this.attendanceRuleForm = {
+        id: row.id,
+        name: row.name,
+        beginWorkStartTime: row.beginWorkStartTime,
+        beginWorkEndTime: row.beginWorkEndTime,
+        endWorkStartTime: row.endWorkStartTime,
+        endWorkEndTime: row.endWorkEndTime
+      }
+      setTimeout(() => {
+        this.$refs.attendanceRuleFormRef.clearValidate()
+      }, 0)
+      this.editAttendanceRuleVisible = true
+    },
+    
+    // 保存考勤规则
+    saveAttendanceRule() {
+      this.postData('/attendance-rule/updateAttendanceRule', {
+        id: this.attendanceRuleForm.id,
+        beginWorkStartTime: this.attendanceRuleForm.beginWorkStartTime + ':00',
+        beginWorkEndTime: this.attendanceRuleForm.beginWorkEndTime + ':00',
+        endWorkStartTime: this.attendanceRuleForm.endWorkStartTime + ':00',
+        endWorkEndTime: this.attendanceRuleForm.endWorkEndTime + ':00'
+      }).then(res => {
+        this.$message({ type: 'success', message: '保存成功' })
+        this.editAttendanceRuleVisible = false
+        this.getAttendanceRules()
+      })
+    },
+    importAttendanceShow() {
+      this.currentMonthExport = this.currentMonth
+      this.importAttendanceVisable = true
+    },
+    saveHighTemperatureDay() {
+      const formVal = {
+        ...this.hotDayForm,
+        startDate: this.hotDayForm.timeSlot[0],
+        endDate: this.hotDayForm.timeSlot[1],
+      }
+
+      delete formVal.timeSlot;
+
+      this.postData(`/high-temperature-set/saveOrUpdate`, {...formVal}).then((res) => { 
+        this.$message({ type: 'success', message: '保存成功' })
+        this.editHighTemperatureDayVisible = false
+        this.getHighTemperatureDaySettings()
+      })
+    },
+    editHighTemperatureDay(row = false) {
+      if (!row) {
+        this.hotDayForm = {
+          timeSlot: [],
+          remark: ''
+        }
+      } else {
+        this.hotDayForm = {
+          id: row.id,
+          timeSlot: [row.startDate, row.endDate],
+          remark: row.remark
+        }
+      }
+
+      this.editHighTemperatureDayVisible = true
+    },
+    batchDeleteHighTemperature() {
+      this.$confirm('确定删除选中数据吗?', '批量删除', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        const ids = this.selectHighTemperatureDay.map(item => item.id).join(',')
+        this.postData(`/high-temperature-set/deleteHigh`, { ids }).then((res) => {
+          this.$message({ type: 'success', message: '删除成功' })
+          this.getHighTemperatureDaySettings()
+        })
+      })
+
+    },
+    highTemperatureSelectionChange(val) {
+      this.selectHighTemperatureDay = val || []
+    },
+    displayHighTemperatureDaySettings() {
+      this.highTemperatureDayVisable = true
+      this.selectHighTemperatureDay = []
+      this.getHighTemperatureDaySettings()
+    },
+    getHighTemperatureDaySettings() {
+      this.highTemperatureDayTableLoading = true
+      this.postData('/high-temperature-set/list', {}).then(res => {
+        this.selectHighTemperatureDay = []
+        this.highTemperatureDayList = res.data || []
+      }).finally(() => {
+        this.highTemperatureDayTableLoading = false
+      })
+    },
+    getAllPersonnel() {
+      this.postData(`/user/getEmployeeList`, { pageIndex: 1, pageSize: 300, status: 1, departmentId: -1 }).then((res) => {
+        this.userList = res.data.records || []
+      })
+    },
+    batchImportData(item) {
+      let str = item.file.name.split(".");
+      let format = str[str.length - 1];
+      if (format != "xls" && format != "xlsx") {
+        this.$message({
+          message: this.$t('other.PleaseselecttheXLSorXLSXfile'),
+          type: "error"
+        });
+      } else {
+        this.startImportingLoading = true
+        let formData = new FormData();
+        formData.append("file", item.file);
+        formData.append("month", this.currentMonthExport);
+        this.http.uploadFile('/attendance/importAttendanceData', formData,
+          res => {
+            this.startImportingLoading = false
+            this.$refs.upload.clearFiles();
+            if (res.code == "ok") {
+              this.importAttendanceVisable = false
+              this.importResultMsg = `导入成功`
+              this.refreshTabData(true);
+            } else {
+              this.importResultMsg = `导入失败` + res.msg;
+            }
+          },
+          error => {
+            this.startImportingLoading = false
+            this.$refs.upload.clearFiles();
+            this.$message({
+              message: error,
+              type: "error"
+            });
+          });
+      }
+    },
+    // 单独封装请求
+    async postData(urls, param) {
+      return new Promise((resolve, reject) => {
+        this.http.post(urls, { ...param },
+          res => {
+            if (res.code == 'ok') {
+              resolve(res)
+            } else {
+              this.$message({
+                message: res.msg,
+                type: 'error'
+              })
+              reject(res)
+            }
+            resolve(res)
+          },
+          error => {
+            this.$message({
+              message: error,
+              type: "error"
+            });
+            reject(error)
+          }
+        )
+      });
+    },
+    refreshTabData(directRequest = false) {
+      const dateVal = this.currentMonth
+      setTimeout(() => {
+        this.$refs.tabComponentRef.refreshData({ dateVal, directRequest, userId: this.userId });
+      }, 500)
+    },
+    
+    // 特殊节假日设置相关方法
+    displaySpecialDaySettings() {
+      this.specialDayVisable = true
+      this.selectSpecialDay = []
+      this.getSpecialDayList()
+    },
+    getSpecialDayList() {
+      this.specialDayLoading = true
+      this.postData('/special-date-set/list', {}).then(res => {
+        this.specialDayList = res.data || []
+      }).finally(() => {
+        this.specialDayLoading = false
+      })
+    },
+    editSpecialDay(row = false) {
+      if (!row) {
+        this.specialDayForm = {
+          specialDate: '',
+          type: 0,
+          remark: ''
+        }
+      } else {
+        this.specialDayForm = {
+          id: row.id,
+          specialDate: row.specialDate,
+          type: row.type,
+          remark: row.remark
+        }
+      }
+      this.editSpecialDayVisible = true
+    },
+    saveSpecialDay() {
+      this.postData('/special-date-set/saveOrUpdate', this.specialDayForm).then(res => {
+        this.$message({ type: 'success', message: '保存成功' })
+        this.editSpecialDayVisible = false
+        this.getSpecialDayList()
+      })
+    },
+    batchDeleteSpecialDay() {
+      this.$confirm('确定删除选中数据吗?', '批量删除', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        const ids = this.selectSpecialDay.map(item => item.id).join(',')
+        this.postData('/special-date-set/deleteSpecial', { ids }).then(res => {
+          this.$message({ type: 'success', message: '删除成功' })
+          this.getSpecialDayList()
+        })
+      })
+    },
+    specialDaySelectionChange(val) {
+      this.selectSpecialDay = val || []
+    }
+  },
+  created() { },
+  mounted() {
+    this.getAllPersonnel()
+    this.refreshTabData(true)
+    // 查看全部员工
+    const permissionList = this.user.functionList || []
+    const list = permissionList.filter(item => item.name == '查看全部员工')
+    if(list.length) {
+      this.haveYouViewedAllOfThem = true
+    }
+
+    console.log(this.haveYouViewedAllOfThem, '<====== this.haveYouViewedAllOfThem')
+  },
+  beforeDestroy() { },
+};
+</script>
+
+<style lang="scss" scoped>
+.calendarAttendance {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+
+  .calendarAttendance-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 20px 20px;
+    border-bottom: 1px solid #e6e6e6;
+
+    .calendarAttendance-title-left {
+      display: flex;
+      align-items: center;
+    }
+  }
+
+  .calendarAttendance-content {
+    flex: 1;
+    padding: 20px;
+  }
+
+  .bottomOfHighTemperatureDay {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 20px;
+  }
+}
+
+/* 淡入淡出动画 */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.3s ease;
+}
+
+.fade-enter,
+.fade-leave-to {
+  opacity: 0;
+}
+</style>

+ 3 - 2
fhKeeper/formulahousekeeper/timesheet-workshop/src/views/plan/planComponent.vue

@@ -19,12 +19,13 @@
               @change="getTableData(hasChooseDept)" placeholder="请输入内容" clearable="true"></el-input>
           </div>
           <div class="OutSide" style="padding-bottom: 0;">
-              <el-input placeholder="请输入内容"  v-model="searchValue" class="input-with-select" size="small">
-              <el-select v-model="searchType" slot="prepend" placeholder="请选择">
+              <el-input placeholder="请输入内容"  v-model="searchValue" class="input-with-select" size="small" style="width: 400px;">
+              <el-select v-model="searchType" slot="prepend" placeholder="请选择" style="width: 120px">
                 <el-option label="排产工单号" value="1"></el-option>
                 <el-option label="项目名称" value="2"></el-option>
                 <el-option label="产品名称" value="3"></el-option>
                 <el-option label="订单号" value="4"></el-option>
+                <el-option label="工序" value="5"></el-option>
               </el-select>
               <el-button slot="append" icon="el-icon-search" @click="getTableData()"></el-button>
             </el-input>