浏览代码

Merge branch 'hbchen'

hbchen 1 年之前
父节点
当前提交
905340165f

+ 40 - 0
src/router/modules/businessRoutes.js

@@ -0,0 +1,40 @@
+//出差管理路由模块
+import Home from '@/layouts/index.vue'
+
+export default [
+    {
+		path: '/',
+		component: Home,
+		name: 'businessTripManage',
+		meta: {
+			title: "出差管理",
+		},
+		icon_path: require('@/assets/img/home/business_trip.png'),
+		children: [
+			{
+				path: "businessTripCalendar",
+				name: "businessTripCalendar",
+				component: () => import('@/views/business_trip_manage/businessCalendar.vue'),
+				meta: {
+					title:'出差日历',
+				}
+			},
+			{
+				path: "businessTripApplication",
+				name: "businessTripApplication",
+				component: () => import('@/views/business_trip_manage/businessApplication.vue'),
+				meta: {
+					title:'出差申请',
+				}
+			},
+			{
+				path: 'businessTripApproval',
+				name: 'businessTripApproval',
+				component: () => import('@/views/business_trip_manage/businessApproval.vue'),
+				meta: {
+					title:'出差审批',
+				}
+			}
+		]
+	},
+]

+ 259 - 0
src/views/business_trip_manage/businessApplication.vue

@@ -0,0 +1,259 @@
+<script setup>
+import { ref,reactive } from "vue";
+import { ElMessageBox,ElMessage } from 'element-plus'
+
+
+import {businessTripInterence} from "@/api/api.js"
+import { interactiveInterface } from '@/api/api'
+import tripApplicationDia from './components/tripApplicationDia.vue'
+import mPage from "@/components/mPage.vue";
+
+const reasonList=["路演","调研","培训","会议"]
+const statusList=["待审批","已通过","已撤回","已驳回","已过期","已关闭"]
+
+const searchParams=reactive({
+  PageSize:10,
+  CurrentIndex:1,
+  Reason:'',
+  Status:''
+})
+const tableData=ref([])
+const userList=ref([])
+const reapplyItem=ref({})
+const total=ref(0)
+const applyDiaShow=ref(false)
+
+/* 获取所有列表 */
+const getAllUser=()=>{
+  interactiveInterface.allUserList()
+  .then(res => {
+    if(res.Ret !== 200) return
+    userList.value = res.Data.List||[]
+  })
+}
+
+const searchList=()=>{
+  searchParams.CurrentIndex=1
+  getApplication()
+}
+// 修改页码
+const pageChange=(page_no)=>{
+  searchParams.CurrentIndex=page_no
+  getApplication()
+}
+const addApproval=()=>{
+  reapplyItem.value={}
+  applyDiaShow.value=true
+  if(userList.value?.length>0) return
+  getAllUser()
+}
+const application=()=>{
+  getApplication()
+}
+// 撤回申请
+const revocationApplication=(row)=>{
+  ElMessageBox.confirm("确定要撤回该活动申请吗?", "撤回", {
+    type: "warning",
+  }).then(() => {
+    businessTripInterence.withdrawTripApplication({BusinessApplyId:row.BusinessApplyId}).then(res=>{
+      if(res.Ret == 200){
+        ElMessage.success('撤回成功')
+        getApplication();
+      }
+    })
+  }).catch(()=>{})
+}
+// 驳回理由
+const rejectReason=(row,type)=>{
+  let title = '驳回理由'
+  let text = row.RefuseReason
+  if(type == 'close'){
+    title = '关闭理由'
+    text = row.CloseReason
+  }
+  ElMessageBox.alert(text, title, {
+    confirmButtonText: '知道了',
+  }).catch(()=>{})
+}
+// 重新申请
+const reapply=(row)=>{
+  reapplyItem.value=row
+  applyDiaShow.value=true
+  if(userList.value?.length>0) return
+  getAllUser()
+}
+// 删除申请
+const deleteApplication=(row)=>{
+  ElMessageBox.confirm("确定要删除该活动申请吗?", "操作提示", {
+    type: "warning",
+  }).then(() => {
+    businessTripInterence.deleteTripApplication({BusinessApplyId:row.BusinessApplyId}).then(res=>{
+      if(res.Ret == 200){
+        ElMessage.success('删除成功')
+        getApplication();
+      }
+    })
+  }).catch(()=>{})
+}
+// 关闭
+const closeApprove=(row)=>{
+  ElMessageBox.prompt('活动申请已审批通过,确定关闭该活动吗?若关闭,请填写关闭原因!', '操作提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    inputPlaceholder:'关闭理由',
+    inputValidator:(value)=>{
+      if(row.ApplyAdminId===Number(localStorage.getItem('AdminId'))&&!value){
+        return '请输入关闭理由'
+      }
+      return true
+    }
+  }).then(({ value }) => {
+    businessTripInterence.tripApproveClose({BusinessApplyId:row.BusinessApplyId,
+      CloseReason:value||''})
+    .then(res=>{
+      if(res.Ret ==200){
+        ElMessage.success('关闭成功')
+        getApplication()
+      }
+    })
+  }).catch(() => {});
+}
+
+// 获取申请列表
+const getApplication=()=>{
+  businessTripInterence.getTripApplicationList(searchParams).then(res=>{
+    tableData.value = res.Data.List || []
+    total.value = res.Data.Paging.Totals || 0
+  })
+}
+
+getApplication()
+</script>
+
+<template>
+  <div class="trip-application-container">
+    <div class="trip-application-top">
+      <div class="trip-application-search">
+        <el-select v-model="searchParams.Reason" placeholder="请选择出差事由" clearable 
+        style="width: 240px;margin-right: 16px;" @change="searchList" size="large">
+          <el-option :label="item" :value="item" v-for="item in reasonList" :key="item"></el-option>
+        </el-select>
+        <el-select v-model="searchParams.Status" placeholder="请选择申请状态" clearable 
+        style="width: 240px;" @change="searchList" size="large">
+          <el-option :label="item" :value="item" v-for="item in statusList" :key="item"></el-option>
+        </el-select>
+      </div>
+      <el-button type="primary" @click="addApproval" style="height: 40px;width: 100px;">添加申请</el-button>
+    </div>
+    <el-table :data="tableData" border >
+      <el-table-column label="到达日期" prop="ArriveDate" align="center" width="100">
+        <template #default="{row}">
+          {{ row.ArriveDate }}
+        </template>
+      </el-table-column>
+      <el-table-column label="返回日期" prop="ReturnDate" align="center" width="100">
+        <template #default="{row}">
+          {{ row.ReturnDate }}
+        </template>
+      </el-table-column>
+      <el-table-column label="目的地" prop="City" align="center">
+        <template #default="{row}">
+          {{ row.City }}
+        </template>
+      </el-table-column>
+      <el-table-column label="出差事由" prop="Reason" align="center" width="100">
+        <template #default="{row}">
+          {{ row.Reason }}
+        </template>
+      </el-table-column>
+      <el-table-column label="交通工具" prop="Transportation" align="center">
+        <template #default="{row}">
+          {{ row.Transportation.indexOf('其他')!=-1?row.Transportation.substring(row.Transportation.indexOf('-')+1):row.Transportation }}
+        </template>
+      </el-table-column>
+      <el-table-column label="同行人" prop="PeerPeopleName" align="center" show-overflow-tooltip>
+        <template #default="{row}">
+          {{ row.PeerPeopleName || '无' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="申请状态" prop="Status" align="center" width="100">
+        <template #default="{row}">
+          <span :style="{color:row.Status=='待审批'|| row.Status=='已驳回'?'#C54322':'#606266'}">
+            {{ row.Status }}
+          </span>
+          
+        </template>
+      </el-table-column>
+      <el-table-column label="提交时间" prop="CreateTime" align="center">
+        <template #default="{row}">
+          {{ row.CreateTime }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作"  align="center">
+        <template #default="{row}">
+          <div style="margin-right: -10px;">
+            <span class="table-operation-button" v-if="row.Status == '待审批'" @click="revocationApplication(row)">撤回</span>
+            <span class="table-operation-button" v-if="row.Status == '已驳回'" @click="rejectReason(row,'refuse')">驳回理由</span>
+            <span class="table-operation-button" v-if="row.Status == '已关闭'" @click="rejectReason(row,'close')">关闭理由</span>
+            <span class="table-operation-button" v-if="row.Status == '已驳回' || row.Status == '已撤回'" @click="reapply(row)">重新申请</span>
+            <span class="table-operation-button" style="color: #C54322;" v-if="row.Status == '已撤回'" @click="deleteApplication(row)">删除</span>
+            <span class="table-operation-button" v-if="row.IsClose" @click="closeApprove(row)">关闭</span>
+          </div>
+        </template>
+      </el-table-column>
+      <div slot="empty" class="table-noData">
+        <img src="~@/assets/img/cus_m/nodata.png">
+        <span>暂无数据</span>
+      </div>
+    </el-table>
+    <!-- 页数选择器 -->
+    <m-page
+      :page_no="searchParams.CurrentIndex"
+      :pageSize="searchParams.PageSize"
+      :total="total"
+      style="position: absolute;right: 50px;bottom: 40px;"
+      @handleCurrentChange="pageChange"
+    />
+    <tripApplicationDia :userList="userList" :reapply="reapplyItem"
+    v-model:dialogShow="applyDiaShow" @applicationSuccess="application"></tripApplicationDia>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+  .trip-application-container{
+    min-height: calc(100vh - 110px);
+    height: auto;
+    box-sizing: border-box;
+    background-color: #fff;
+    box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.05);
+    border-radius: 4px;
+    padding: 30px 30px 70px;
+    .trip-application-top{
+      display: flex;
+      justify-content: space-between;
+      margin-bottom: 20px;
+    }
+    .table-operation-button{
+      color: #409EFF;
+      cursor: pointer;
+      margin-right: 10px;
+      white-space: nowrap;
+    }
+    .table-noData{
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      margin: 18vh 0;
+      img{
+        height: 110px;
+        width: 136px;
+      }
+      span{
+        font-weight: 400;
+        font-size: 16px;
+        color: #999999;
+      }
+    }
+  }
+</style>

+ 195 - 0
src/views/business_trip_manage/businessApproval.vue

@@ -0,0 +1,195 @@
+<script setup>
+import { ref,reactive } from "vue";
+import {ElMessage,ElMessageBox} from "element-plus"
+
+import {businessTripInterence} from "@/api/api.js"
+import tripApproveDia from "./components/tripApproveDia.vue";
+import mPage from "@/components/mPage.vue";
+
+const statusList=[{label:"全部",value:''},{label:"待审批",value:'待审批'},
+      {label:"已通过",value:'已通过'},{label:"已驳回",value:'已驳回'},{label:"已关闭",value:'已关闭'}]
+const searchParams=reactive({
+  PageSize:10,
+  CurrentIndex:1,
+  Status:'待审批'
+})
+const tableData=ref([])
+const approveItem=ref({})
+const total=ref(0)
+const approvalDiaShow=ref(false)
+
+// 获取审批列表
+const getApproveList=()=>{
+  businessTripInterence.getTripApproveList(searchParams).then(res=>{
+    tableData.value = res.Data.List || []
+    total.value = res.Data.Paging.Totals || 0
+  })
+}
+const searchList=()=>{
+  searchParams.CurrentIndex=1
+  getApproveList()
+}
+// 修改页码
+const pageChange=(page_no)=>{
+  searchParams.CurrentIndex=page_no
+  getApproveList()
+}
+const approve=()=>{
+  getApproveList()
+}
+// 审批
+const revocationApprove=(row)=>{
+  approvalDiaShow.value =true
+  approveItem.value=row
+}
+// 关闭
+const closeApprove=(row)=>{
+  ElMessageBox.prompt('活动申请已审批通过,确定关闭该活动吗?若关闭,请填写关闭原因!', '操作提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    inputPlaceholder:'关闭理由'
+  }).then(({ value }) => {
+    businessTripInterence.tripApproveClose({BusinessApplyId:row.BusinessApplyId,
+      CloseReason:value||''})
+    .then(res=>{
+      if(res.Ret ==200){
+        ElMessage.success('关闭成功')
+        getApproveList()
+      }
+    })
+  }).catch(() => {});
+}
+// 驳回理由
+const rejectReason=(row,type)=>{
+  let title = '驳回理由'
+  let text = row.RefuseReason
+  if(type == 'close'){
+    title = '关闭理由'
+    text = row.CloseReason
+  }
+  ElMessageBox.alert(text, title, {
+    confirmButtonText: '知道了',
+  }).catch(() => {});
+}
+
+getApproveList()
+</script>
+
+<template>
+  <div class="trip-approve-container">
+    <div class="trip-approve-top">
+      <el-select v-model="searchParams.Status" placeholder="请选择审批状态" style="width: 240px;" @change="searchList">
+        <el-option :label="item.label" :value="item.value" v-for="item in statusList" :key="item" ></el-option>
+      </el-select>
+    </div>
+    <el-table :data="tableData" border >
+      <el-table-column label="申请人" prop="ApplyRealName" align="center" width="100">
+        <template #default="{row}">
+          {{ row.ApplyRealName }}
+        </template>
+      </el-table-column>
+      <el-table-column label="到达日期" prop="ArriveDate" align="center" width="100">
+        <template #default="{row}">
+          {{ row.ArriveDate }}
+        </template>
+      </el-table-column>
+      <el-table-column label="返回日期" prop="ReturnDate" align="center" width="100">
+        <template #default="{row}">
+          {{ row.ReturnDate }}
+        </template>
+      </el-table-column>
+      <el-table-column label="目的地" prop="City" align="center">
+        <template #default="{row}">
+          {{ row.City }}
+        </template>
+      </el-table-column>
+      <el-table-column label="出差事由" prop="Reason" align="center" width="100">
+        <template #default="{row}">
+          {{ row.Reason }}
+        </template>
+      </el-table-column>
+      <el-table-column label="交通工具" prop="Transportation" align="center">
+        <template #default="{row}">
+          {{ row.Transportation.indexOf('其他')!=-1?row.Transportation.substring(row.Transportation.indexOf('-')+1):row.Transportation }}
+        </template>
+      </el-table-column>
+      <el-table-column label="同行人" prop="PeerPeopleName" align="center" show-overflow-tooltip>
+        <template #default="{row}">
+          {{ row.PeerPeopleName || '无' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="申请状态" prop="Status" align="center" width="100">
+        <template #default="{row}">
+          <span :style="{color:row.Status=='待审批'|| row.Status=='已驳回'?'#C54322':'#606266'}">
+            {{ row.Status }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="提交时间" prop="CreateTime" align="center">
+        <template #default="{row}">
+          {{ row.CreateTime }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作"  align="center">
+        <template #default="{row}">
+          <div style="margin-right: -10px;">
+            <span class="table-operation-button" v-if="row.Status == '待审批'" @click="revocationApprove(row)">审批</span>
+            <span class="table-operation-button" v-if="row.Status == '已驳回'" @click="rejectReason(row,'refuse')">驳回理由</span>
+            <span class="table-operation-button" v-if="row.IsClose" @click="closeApprove(row)">关闭</span>
+            <span class="table-operation-button" v-if="row.Status == '已关闭'" @click="rejectReason(row,'close')">关闭理由</span>
+          </div>
+        </template>
+      </el-table-column>
+      <div slot="empty" class="table-noData">
+        <img src="~@/assets/img/cus_m/nodata.png">
+        <span>暂无数据</span>
+      </div>
+    </el-table>
+    <!-- 页数选择器 -->
+    <m-page
+      :page_no="searchParams.CurrentIndex"
+      :pageSize="searchParams.PageSize"
+      :total="total"
+      style="position: absolute;right: 50px;bottom: 40px;"
+      @handleCurrentChange="pageChange"
+    />
+    <tripApproveDia v-model:dialogShow="approvalDiaShow" :approveItem="approveItem" @approveSuccess="approve"></tripApproveDia>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+  .trip-approve-container{
+    min-height: calc(100vh - 110px);
+    height: auto;
+    box-sizing: border-box;
+    background-color: #fff;
+    box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.05);
+    border-radius: 4px;
+    padding: 30px 30px 70px;
+    .trip-approve-top{
+      margin-bottom: 20px;
+    }
+    .table-operation-button{
+      color: #409EFF;
+      cursor: pointer;
+      margin-right: 10px;
+      white-space: nowrap;
+    }
+    .table-noData{
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      margin: 18vh 0;
+      img{
+        height: 110px;
+        width: 136px;
+      }
+      span{
+        font-weight: 400;
+        font-size: 16px;
+        color: #999999;
+      }
+    }
+  }
+</style>

+ 403 - 0
src/views/business_trip_manage/businessCalendar.vue

@@ -0,0 +1,403 @@
+
+<script setup>
+import { ref,nextTick } from "vue";
+import { router } from '@/router'
+import { ArrowLeft,ArrowRight } from '@element-plus/icons-vue'
+import moment from "moment";
+import $ from "jquery"
+
+import { interactiveInterface } from '@/api/api'
+import { businessTripInterence } from "@/api/api.js";
+import {bussinessTripH} from './hooks/bussinessTripH'
+import tripApproveDia from "./components/tripApproveDia.vue";
+import tripApplicationDia from './components/tripApplicationDia.vue'
+
+const bussinessTripCom=bussinessTripH()
+
+const userList = ref([])
+const users = ref([])
+
+const selectUser=(value)=>{
+  getTableData()
+}
+
+
+const daysArr=['周一','周二','周三','周四','周五','周六','周日']
+const tableDayColumns=ref('')
+const datalist=ref([])
+
+const getWeekDays=(weeknum,dateList)=>{
+  // dateList 日期数组  weeknum 周数
+  let arrList=[]
+  for (let i = 0; i < weeknum; i++) {
+    arrList[i]=[]
+    if(dateList && dateList.length>0){
+      // 有数据返回 能拿到后端返回的日期数组
+      for (let j = 0; j < 7; j++) {
+        let momentItem=moment(dateList[(i*7+j)])
+        arrList[i].push(momentItem.format('MM/DD')+daysArr[momentItem.weekday()])
+      }
+    }
+  }
+  return arrList;
+}
+//去往研究员日历
+const toResearchCalendar=(date,researcher)=>{
+  let params={date:date,researcherId:researcher.AdminId,researcherName:researcher.RealName,}
+  sessionStorage.setItem('businessToResearcherParams',JSON.stringify(params))
+  let routeData = router.resolve({ path: '/researcherCalendar'});
+  window.open(routeData.href, '_blank');
+}
+
+const detailItem=ref({})
+//详情弹窗
+const approvalDiaShow=ref(false)
+//申请弹窗
+const detailDiaShow=ref(false)
+//审批弹窗
+const applicationDiaShow=ref(false)
+
+// 出差详情
+const businessDetail=(data)=>{
+  if(data.Status=='' || data.BusinessApplyId==0) return
+  businessTripInterence.getTripDetail({BusinessApplyId:data.BusinessApplyId}).then(res=>{
+    if(res.Ret == 200){
+      detailItem.value=res.Data || {}
+      if(data.Status=='待审批'){
+        approvalDiaShow.value=true
+      }else if(data.Status=='已通过'){
+        detailDiaShow.value=true
+      }
+    }
+  })
+}
+
+const approve=()=>{
+  getTableData();
+}
+const application=()=>{
+  getTableData();
+}
+
+/* 获取所有用户列表 */
+const getAllUser=()=>{
+  interactiveInterface.allUserList().then(res => {
+    if(res.Ret !== 200) return
+    userList.value = res.Data.List||[]
+  })
+}
+/* 获取表格数据 */
+const getTableData=()=>{
+  let param={
+    AdminId:users.value ? users.value.join(',') : '',
+    WeekQuery:bussinessTripCom.weekQuery.value,
+    BaseQueryDate:bussinessTripCom.BaseDate.value
+  }
+  businessTripInterence.getTripCalendar(param).then((res) => {
+    if (res.Ret !== 200) return;
+    bussinessTripCom.BaseDate.value = res.Data.BaseDate
+    const Data = res.Data.GroupList || []
+
+    //处理数据结构
+    let tempData=Data.filter(it => it.AdminList && it.AdminList.length > 0)
+    tempData.forEach((item)=>{
+      item.showDetail=true
+    })
+    // 取出出差表所有的日期
+    let weekDateList =  tempData[0]?tempData[0].AdminList[0].BusinessTripList.map(item => item.WeekDate):[]
+    // 获取表格头文本
+    tableDayColumns.value= getWeekDays(2,weekDateList)
+    datalist.value = tempData;
+    nextTick(()=>{
+      $("table")
+        .find("td")
+        .css({ width: '6.25%' });
+      $("table")
+        .find(".thead-rs")
+        .css({ width: '6.25%' });
+    })
+  });
+}
+
+getAllUser()
+getTableData()
+</script>
+
+<template>
+  <div class="trip-container">
+    <div class="trip-top">
+      <el-cascader
+        v-model="users" :options="userList" collapse-tags
+        size="large"
+        :props="{
+					label: 'RealName',
+					value: 'AdminId', 
+					children: 'ChildrenList',
+					emitPath: false,
+					multiple: true
+				}"
+        style="width: 240px;"
+				:show-all-levels="false" clearable filterable
+        @change="selectUser" placeholder="请选择用户"
+      >
+      </el-cascader>
+      <div class="center">
+        <el-button type="primary" @click="bussinessTripCom.toogelDate(1,getTableData)" style="width: 140px;" size="large">
+          <el-icon :size="14" style="margin-right: 4px;"><ArrowLeft /></el-icon>
+          上两周
+        </el-button>
+        <el-button type="primary" @click="bussinessTripCom.toogelDate(0,getTableData)" style="width: 140px;" size="large">本期</el-button>
+        <el-button type="primary" @click="bussinessTripCom.toogelDate(2,getTableData)" style="width: 140px;" size="large">
+          下两周
+          <el-icon :size="14" style="margin-left: 4px;"><ArrowRight /></el-icon>
+        </el-button>
+      </div>
+      <el-button type="primary" @click="applicationDiaShow=true" style="height: 40px;width: 100px;">添加申请</el-button>
+    </div>
+    <div class="table-cont">
+      <div class="table-body-wrapper" style="max-height: calc(100vh - 240px);overflow: auto;">
+				<table>
+          <thead>
+            <tr>
+              <td :rowspan="2" class="thead-rs">分组</td>
+              <td :rowspan="2" class="thead-rs">用户</td>
+              <td :colspan="7" v-for="item in bussinessTripCom.tableTheadColumns.value" :key="item" class="head-column">
+                {{ item }}
+              </td>
+            </tr>
+            <tr>
+              <template v-for="item in [0,1]">
+                <td :key="item +'_'+ ind" v-for="(it,ind) in tableDayColumns[item]" >
+                  {{ it }}
+                </td>
+              </template>
+            </tr>
+          </thead>
+          <template v-if="datalist.length>0">
+            <tbody v-for="item in datalist" :key="item.GroupId">
+              <template v-if="item.AdminList.length">
+                <tr>
+                  <td :rowspan="item.AdminList.length + 1" class="thead-rs" style="cursor: pointer;"
+                  @click="item.showDetail = !item.showDetail" >{{ item.GroupName }}
+                    <i :class="item.showDetail?'el-icon-arrow-down':'el-icon-arrow-up'" v-show="item.AdminList.length>1"></i>
+                  </td>
+                </tr>
+                <tr v-for="rs in item.showDetail?item.AdminList:item.AdminList.slice(0,1)" :key="rs.AdminId">
+                  <td class="thead-rs">{{ rs.RealName }}</td>
+                    <td :key="data_key+'_0'" v-for="(data,data_key) in rs.BusinessTripList.filter(it => it.WeekType == 'current')"
+                      style="position: relative;">
+                      <img src="../../assets/img/icons/television.png" v-if="item.GroupId==1 && data.City&&data.Reason==='路演'" 
+                      @click="toResearchCalendar(data.WeekDate,rs)"
+                      class="view-researcher-icon"/>
+                      <span style="cursor: pointer;" :style="{color:data.Status=='待审批'?'#FF8A00':'#666666'}"
+                      @click="businessDetail(data)">{{ data.City }}</span>
+                      <img src="../../assets/img/approve_wait.png" v-show="data.Status=='待审批'" @click="businessDetail(data)"
+                      class="wait-approve-icon"/>
+                    </td>
+                    <td :key="data_key+'_1'" v-for="(data,data_key) in rs.BusinessTripList.filter(it => it.WeekType == 'next')" 
+                      style="position: relative;">
+                      <img src="../../assets/img/icons/television.png" v-if="item.GroupId==1 && data.City&&data.Reason==='路演'" 
+                      @click="toResearchCalendar(data.WeekDate,rs)"
+                      class="view-researcher-icon"/>
+                      <span style="cursor: pointer;" :style="{color:data.Status=='待审批'?'#FF8A00':'#666666'}"
+                      @click="businessDetail(data)">{{ data.City }}</span>
+                        <img src="../../assets/img/approve_wait.png" v-show="data.Status=='待审批'" @click="businessDetail(data)"
+                        class="wait-approve-icon" />
+                    </td>
+                </tr>
+              </template>
+            </tbody>
+          </template>
+        </table>
+        <template v-if="datalist.length==0">
+            <div class="table-data-empty">
+              暂无信息
+            </div>
+          </template>
+      </div>
+    </div>
+    <el-dialog
+      title="出差详情"
+      :modal-append-to-body="false"
+      v-model="detailDiaShow"
+      width="380px"
+    >
+      <div class="detail-box">
+        <div class="detail-date">
+          {{ moment(detailItem.ArriveDate).format('MM.DD') }}({{daysArr[moment(detailItem.ArriveDate).weekday()]}})--
+          {{ moment(detailItem.ReturnDate).format('MM.DD') }}({{daysArr[moment(detailItem.ReturnDate).weekday()]}})
+        </div>
+        <div class="detail-item">
+          <div class="detail-item-label">申请人:</div>
+          {{ detailItem.ApplyRealName }}
+        </div>        
+        <div class="detail-item">
+          <div class="detail-item-label">目的地:</div>
+          {{ detailItem.Province+' '+detailItem.City }}
+        </div>        
+        <div class="detail-item">
+          <div class="detail-item-label"> 出差事由:</div>
+          {{ detailItem.Reason }}
+        </div>
+        <div class="detail-item">
+          <div class="detail-item-label">交通工具:</div>
+          {{ detailItem.Transportation && detailItem.Transportation.indexOf('其他')!=-1?
+          detailItem.Transportation.substring(detailItem.Transportation.indexOf('-')+1):detailItem.Transportation }}
+        </div>        
+        <div class="detail-item" style="line-height: 20px;" v-show="detailItem.PeerPeopleName">
+          <div class="detail-item-label">同行人:</div>
+          {{ detailItem.PeerPeopleName }}
+        </div>
+      </div>
+    </el-dialog>
+    <tripApproveDia :approveItem="detailItem" v-model:dialogShow="approvalDiaShow" @approveSuccess="approve"></tripApproveDia>
+    <tripApplicationDia v-model:dialogShow="applicationDiaShow" :reapply="{}" :userList="JSON.parse(JSON.stringify(userList))" @applicationSuccess="application"></tripApplicationDia>
+  </div>
+</template>
+
+
+<style lang="scss" scoped>
+* {
+  box-sizing: border-box;
+}
+.trip-container {
+  min-height: calc(100vh - 110px);
+  height: auto;
+	background-color: #fff;
+	box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.05);
+	border-radius: 4px;
+	padding: 30px;
+  .trip-top {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 20px;
+    gap: 30px;
+    .center{
+      display: flex;
+      flex-wrap: nowrap;
+    }
+  }
+	.frequency-cont {
+		display: flex;
+		align-items: center;
+		margin-bottom: 30px;
+		.frequency-ul {
+			display: flex;
+			align-items: center;
+			li {
+				width: 110px;
+				height: 40px;
+				text-align: center;
+				line-height: 40px;
+				border: 1px solid #B3D8FF;
+				background: #ECF5FF;
+				color: #409EFF;
+				border-radius: 4px;
+				cursor: pointer;
+				margin-right: 20px;
+				&.act {
+					background: #409EFF;
+					color: #fff;
+				}
+			}
+		}
+	}
+
+	.table-cont {
+		.table-body-wrapper {
+			max-height: calc(100vh - 340px);
+			overflow-y: scroll;
+			overflow-x: auto;
+			border-bottom: 1px solid #dcdfe6;
+			border-top: 1px solid #dcdfe6;
+      .table-data-empty{
+        color: #666666;
+        height: 530px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        border-left: 1px solid #DCDFE6;
+        border-right: 1px solid #DCDFE6;
+      }
+		}
+
+		table {
+			width: 100%;
+			font-size: 14px;
+			color: #666;
+			thead{
+				position: sticky;
+				top: 0;
+        z-index: 1;
+				left: 0;
+				border-left: 1px solid #dcdfe6;
+				border-right: 1px solid #dcdfe6;
+				td{
+					border: none;
+					outline-color: #dcdfe6;
+					outline-style: solid;
+					outline-width: 0.5px;
+          background-color:#EBEEF5!important ;
+          color: #333333;
+          font-weight: 500;
+				}
+			}
+			td,
+			th {
+				min-width: 35px;
+				// word-break: break-all;
+				border: 1px solid #dcdfe6;
+				height: 45px;
+				text-align: center;
+				background-color: #fff;
+			}
+	
+			.head-column {
+				background-color: #F0F2F5;
+			}
+	
+			.data-cell{
+				color: #409EFF;
+				cursor: pointer;
+			}
+
+			.thead-sticky {
+				position: sticky;
+				top: 0;
+			}
+      .view-researcher-icon{
+        height: 12px;
+        position: absolute;
+        cursor: pointer;
+        left: 4px;
+        top: 4px;
+      }
+      .wait-approve-icon{
+        height: 35px;
+        position: absolute;
+        cursor: pointer;
+        right: 0;
+        top: 0;
+      }
+		}
+	}
+  .detail-box{
+    padding:0 0 15px 40px;
+    .detail-item{
+      font-size: 16px;
+      color: #333333;
+      margin-bottom: 10px;
+      display: flex;
+      .detail-item-label{
+        min-width: 80px;
+        text-align: right;
+      }
+    }
+    .detail-date{
+      margin-bottom: 14px;
+      font-size: 16px;
+      color: #333333;
+    }
+  }
+}
+</style>

+ 296 - 0
src/views/business_trip_manage/components/tripApplicationDia.vue

@@ -0,0 +1,296 @@
+<script setup>
+import { businessTripInterence } from "@/api/api.js"
+import { reactive,ref,nextTick,watch } from "vue";
+import { ElMessage } from 'element-plus'
+import moment from "moment";
+
+import searchDistPicker from '@/components/searchDistPicker.vue';
+
+const props = defineProps({
+    dialogShow:{
+      type:Boolean,
+      default:false
+    },
+    userList:{
+      type:Array,
+      required:true
+    },
+    reapply:{
+      type:Object,
+      default:()=>{
+        return {}
+      }
+    }
+})
+const emits = defineEmits(["update:dialogShow","applicationSuccess"])
+
+const vehicleArr=['飞机','火车','汽车','其他']
+const tripReasonArr=['路演','调研','培训','会议']
+
+const dataForm=reactive({
+  BusinessApplyId:'',
+  arriveTime:'',
+  backTime:'',
+  province:'',
+  city:'',
+  tripReason:'',
+  vehicle:'',
+  companion:'',
+  companionName:'',
+  otherVehicle:''
+})
+const companionSelectList=ref([])
+
+const adminId=localStorage.getItem('AdminId')||0
+
+const rules={
+  arriveTime:{required:true,message:"到达日期不能为空",trigger:'change'},
+  backTime:{required:true,message:"返程日期不能为空",trigger:'change'},
+  city:{required:true,message:"目的地不能为空",trigger:'change'},
+  tripReason:{required:true,message:"出差事由不能为空",trigger:'change'},
+  vehicle:[{required:true,message:"请选择交通工具",trigger:'change'},
+    {
+      validator:(rule, value, callback)=>{
+        if(!dataForm.otherVehicle&&value=='其他'){
+          callback(new Error('请输入其他交通工具'))
+          return
+        }
+        callback()
+      },
+      trigger:['blur']
+  }]
+}
+
+watch(() => props.dialogShow, (value) => {
+  if(value && props.reapply.BusinessApplyId){
+    dataForm.BusinessApplyId=props.reapply.BusinessApplyId,
+    dataForm.arriveTime=props.reapply.ArriveDate,
+    dataForm.backTime=props.reapply.ReturnDate,
+    dataForm.province=props.reapply.Province,
+    dataForm.city=props.reapply.City,
+    dataForm.tripReason=props.reapply.Reason,
+    dataForm.vehicle=props.reapply.Transportation.indexOf('其他')!=-1?'其他':props.reapply.Transportation,
+    dataForm.companion=props.reapply.PeerPeopleId,
+    dataForm.companionName=props.reapply.PeerPeopleName,
+    dataForm.otherVehicle=props.reapply.Transportation.indexOf('其他')!=-1?props.reapply.Transportation.substring(props.reapply.Transportation.indexOf('-')+1):''
+
+    companionSelectList.value = props.reapply.PeerPeopleId.split(',')
+  }
+})
+
+watch(() => props.userList, (value) => {
+  setUserDisable(value)
+})
+
+// 递归用户列表,设置置灰项 - 不能选择自己
+const setUserDisable=(list)=>{
+  for (let i = 0; i < list.length; i++) {
+    const element = list[i];
+    if(element.ChildrenList){
+      setUserDisable(element.ChildrenList)
+    }else{
+      if(element.AdminId == adminId){
+        element.disabled=true
+        break
+      }
+    }
+  }
+}
+
+// 工作位置选择
+const selectRegion=(data)=>{
+  dataForm.province = data.province.value;
+  dataForm.city = data.city.value =='市'?'':data.city.value;
+}
+
+// 选择交通工具
+const vehicleChange=(value)=>{
+  if(value!='其他'){
+    dataForm.otherVehicle=''
+  }
+}
+
+// 选择同行人
+const selectCompanions=(value)=>{
+  // 取出同行人Id
+  dataForm.companion = value.join(',')
+  nextTick(()=>{
+    getCompanionsName()
+  })
+}
+
+const companionCascader=ref(null)
+
+// 取得同行人姓名
+const getCompanionsName=()=>{
+  let checkNodes = companionCascader.value.getCheckedNodes()
+  let valueLength = checkNodes?checkNodes.length:0
+  let companionNameList=[]
+  dataForm.companionName=''
+  for (let i = 0; i < valueLength; i++) {
+    const element = checkNodes[i];
+    if(element.hasChildren){
+      continue
+    }
+    companionNameList.push(element.label)
+  }
+  dataForm.companionName = companionNameList.join(',')
+}
+
+const applicationFormRef=ref(null)
+
+const diaClose=()=>{
+  dataForm.BusinessApplyId='',
+  dataForm.arriveTime='',
+  dataForm.backTime='',
+  dataForm.province='',
+  dataForm.city='',
+  dataForm.tripReason='',
+  dataForm.vehicle='',
+  dataForm.companion='',
+  dataForm.companionName='',
+  dataForm.otherVehicle=''
+
+  companionSelectList.value=[]
+    setTimeout(()=>{
+      applicationFormRef.value.clearValidate()
+    },0)
+  emits("update:dialogShow",false)
+}
+
+const approveSubmit=()=>{
+  applicationFormRef.value.validate(valid=>{
+    if(valid){
+      if(new Date(dataForm.arriveTime).getTime()<new Date(moment(new Date()).format('YYYY-MM-DD')).getTime()){
+        ElMessage.warning('到达日期不能小于当前日期')
+        return
+      }
+      if(new Date(dataForm.arriveTime).getTime()>new Date(dataForm.backTime).getTime()){
+        ElMessage.warning('返程日期不得早于到达日期')
+        return
+      }
+      const params={
+        ArriveDate:dataForm.arriveTime,
+        ReturnDate:dataForm.backTime,
+        Province:dataForm.province,
+        City:dataForm.city,
+        Reason:dataForm.tripReason,
+        Transportation:dataForm.vehicle=='其他'?`${dataForm.vehicle}-${dataForm.otherVehicle}`:dataForm.vehicle,
+        PeerPeopleId:dataForm.companion,
+        PeerPeopleName:dataForm.companionName,
+      }
+      if(dataForm.BusinessApplyId){
+        // 重新申请
+        params.BusinessApplyId = dataForm.BusinessApplyId
+        businessTripInterence.editTripApplication(params).then(res=>{
+          if(res.Ret == 200){
+            ElMessage.success('重新申请成功')
+            emits("applicationSuccess")
+            diaClose()
+          }
+        })
+      }else{
+        // 第一次申请
+        businessTripInterence.addTripApplication(params).then(res=>{
+          if(res.Ret == 200){
+            ElMessage.success('申请成功')
+            emits("applicationSuccess")
+            diaClose()
+          }
+        })
+      }
+    }
+  })
+}
+</script>
+
+<template>
+  <el-dialog
+    title="出差申请"
+    :model-value="props.dialogShow"
+    :modal-append-to-body="false"
+    :close-on-click-modal="false"
+    @closed="diaClose"
+    width="580px"
+  >
+    <el-form :model="dataForm" ref="applicationFormRef" :rules="rules" id="Form" label-width="90px" size="large">
+      <el-form-item label="到达日期" prop="arriveTime">
+        <el-date-picker v-model="dataForm.arriveTime" style="width: 100%;"
+        placeholder="请选择到达日期" value-format="YYYY-MM-DD" :clearable="false">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="返程日期" prop="backTime">
+        <el-date-picker v-model="dataForm.backTime" placeholder="请选择返程日期" :clearable="false"
+        value-format="YYYY-MM-DD" style="width: 100%;">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="目的地" prop="city">
+        <search-dist-picker 
+            :provinceInfo="dataForm.province"
+            :cityInfo="dataForm.city"
+            @selected="selectRegion"/>
+      </el-form-item>
+      <el-form-item label="出差事由" prop="tripReason">
+        <el-select v-model="dataForm.tripReason" placeholder="请选择出差事由" style="width: 100%;">
+          <el-option :label="item" :value="item" v-for="item in tripReasonArr" :key="item"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="交通工具" prop="vehicle">
+        <div class="vehicle-form-item">
+          <el-select v-model="dataForm.vehicle" placeholder="请选择交通工具" @change="vehicleChange" style="flex-grow: 1;">
+            <el-option :label="item" :value="item" v-for="item in vehicleArr" :key="item"></el-option>
+          </el-select>
+          <el-input v-model="dataForm.otherVehicle" style="min-width: 148px;margin-left: 6px;"
+          placeholder="请输入交通工具" v-if="dataForm.vehicle=='其他'"></el-input>
+        </div>
+      </el-form-item>
+      <el-form-item label="同行人" prop="companion" >
+        <el-cascader :options="userList" v-model="companionSelectList" filterable 
+        ref="companionCascader"
+        :props="{
+					label: 'RealName',
+					value: 'AdminId', 
+					children: 'ChildrenList',
+					emitPath: false,
+					multiple: true
+				}" placeholder="请选择同行人"
+				:show-all-levels="false" @change="selectCompanions"></el-cascader>
+      </el-form-item>
+    </el-form>
+    <div slot="footer" class="dialog-footer">
+      <el-button type="primary" @click="approveSubmit" style="width: 120px;height: 40px;">确定</el-button>
+      <el-button @click="diaClose" style="width: 120px;height: 40px;margin-right: 26px;">取消</el-button>
+    </div>
+  </el-dialog>	
+</template>
+
+<style lang="scss" scoped>
+.vehicle-form-item{
+  display: flex;
+  width: 100%;
+}
+.dialog-footer{
+  text-align: center;
+  padding:60px 40px 20px ;
+}
+</style>
+
+<style lang="scss">
+@import "../style/trip-dialog";
+</style>
+<style lang="scss">
+#Form{
+  .el-cascader{
+    width: 100%;
+  }
+  .el-cascader{
+    .el-input--suffix{
+      width: 100%;
+    }
+  }
+  .el-input{
+    width: 100%;
+  }
+}
+
+</style>

+ 126 - 0
src/views/business_trip_manage/components/tripApproveDia.vue

@@ -0,0 +1,126 @@
+<script setup>
+import { computed, reactive,ref } from "vue";
+import { ElMessage } from 'element-plus'
+
+import {businessTripInterence} from "@/api/api.js"
+
+const props = defineProps({
+    dialogShow:{
+      type:Boolean,
+      default:false
+    },
+    approveItem:{
+      type:Object,
+      required:true
+    }
+})
+const emits = defineEmits(["update:dialogShow","approveSuccess"])
+const approveFormRef=ref(null)
+
+const dataForm=reactive({
+  approveResult:'',
+  rejectReason:''
+})
+
+
+//非审批人隐藏审批相关按钮
+const canApprove = computed(() => {
+  const {ApproveId} = props.approveItem
+  const userId = localStorage.getItem('AdminId')
+  return Number(userId) === ApproveId
+})
+
+const diaClose=()=>{
+  dataForm.approveResult=''
+  dataForm.rejectReason=''
+  approveFormRef.value.clearValidate()
+  emits("update:dialogShow",false)
+}
+
+const approveSubmit=()=>{
+  approveFormRef.value.validate(valid=>{
+    if(valid){
+      const params={
+        BusinessApplyId:props.approveItem.BusinessApplyId,
+        ApproveStatus:dataForm.approveResult,
+        RefuseReason:dataForm.rejectReason
+      }
+      businessTripInterence.tripApprove(params).then(res=>{
+        if(res.Ret == 200){
+          ElMessage.success('审批成功')
+          emits("approveSuccess")
+          diaClose()
+        }
+      })
+    }
+  })
+}
+
+</script>
+
+<template>
+  <el-dialog
+    title="出差审批"
+    :model-value="dialogShow"
+    :modal-append-to-body="false"
+    :close-on-click-modal="false"
+    @closed="diaClose"
+    width="500px"
+  >
+    <el-form :model="dataForm" ref="approveFormRef" label-width="90px"
+    id="Form" size="small">
+      <el-form-item label="申请人">
+        {{ approveItem.ApplyRealName }}
+      </el-form-item>
+      <el-form-item label="到达日期">
+        {{ approveItem.ArriveDate }}
+      </el-form-item>
+      <el-form-item label="返程日期">
+        {{ approveItem.ReturnDate }}
+      </el-form-item>
+      <el-form-item label="目的地">
+        {{ approveItem.Province+' '+ approveItem.City}}
+      </el-form-item>
+      <el-form-item label="出差事由">
+        {{ approveItem.Reason }}
+      </el-form-item>
+      <el-form-item label="交通工具">
+        {{ approveItem.Transportation&&approveItem.Transportation.indexOf('其他')!=-1?
+        approveItem.Transportation.substring(approveItem.Transportation.indexOf('-')+1):
+        approveItem.Transportation }}
+      </el-form-item>
+      <el-form-item label="同行人" v-show="approveItem.PeerPeopleName">
+        {{ approveItem.PeerPeopleName }}
+      </el-form-item>
+        <template v-if="canApprove">
+            <el-form-item label="审批状态" prop="approveResult" 
+            :rules="[{required:true,message:'请选择审评状态',trigger:'change'}]">
+                <el-radio v-model="dataForm.approveResult" :label="1">通过</el-radio>
+                <el-radio v-model="dataForm.approveResult" :label="2">驳回</el-radio>
+            </el-form-item>
+            <el-form-item label="驳回理由" prop="rejectReason" v-if="dataForm.approveResult==2"
+            :rules="[{required:true,message:'驳回理由不能为空',trigger:'blur'}]">
+                <el-input v-model="dataForm.rejectReason" placeholder="请输入驳回理由" type="textarea"></el-input>
+            </el-form-item>
+        </template>
+    </el-form>
+    <template v-if="canApprove">
+        <div slot="footer" class="dialog-footer">
+            <el-button type="primary" @click="approveSubmit" style="width: 120px;height: 40px;">确定</el-button>
+            <el-button @click="diaClose"  style="width: 120px;height: 40px;margin-left: 26px;">取消</el-button>
+        </div>        
+    </template>
+  </el-dialog>	
+</template>
+
+
+<style lang="scss" scoped>
+.dialog-footer{
+  text-align: center;
+  padding:60px 40px 20px ;
+}
+</style>
+
+<style lang="scss">
+@import "../style/trip-dialog.scss";
+</style>

+ 47 - 0
src/views/business_trip_manage/hooks/bussinessTripH.js

@@ -0,0 +1,47 @@
+import { ref,onMounted,nextTick } from 'vue'
+import moment from "moment";
+
+export function bussinessTripH() {
+  //周数之差
+  const weekDiff=ref(0)
+  const tableTheadColumns=ref(['本周','下一周'])
+  const BaseDate=ref('')
+  const weekQuery=ref(0)
+
+  const toogelDate=(type,callback)=>{
+    weekQuery.value=type
+    if(weekQuery.value==0){
+      BaseDate.value=''
+      tableTheadColumns.value=['本周','下一周']
+      weekDiff.value=0
+    }else{
+      let prePeriodDate,nextPeriodDate;
+      weekDiff.value=weekQuery.value==1?weekDiff.value-2:weekDiff.value+2
+      if(weekDiff.value==0){
+        // 为0代表本期
+        prePeriodDate='本周'
+        nextPeriodDate='下一周'
+      }else if(weekQuery.value==1){
+        // 往前推两周
+        prePeriodDate = moment(BaseDate.value).subtract(14,'days').format('YYYY-MM-DD')+
+        '——'+moment(BaseDate.value).subtract(8,'days').format('YYYY-MM-DD')
+        nextPeriodDate = moment(BaseDate.value).subtract(7,'days').format('YYYY-MM-DD')+
+        '——'+moment(BaseDate.value).subtract(1,'days').format('YYYY-MM-DD')
+      }else{
+        // 往后推两周
+        prePeriodDate = moment(BaseDate.value).add(14,'days').format('YYYY-MM-DD')+
+        '——'+moment(BaseDate.value).add(20,'days').format('YYYY-MM-DD')
+        nextPeriodDate = moment(BaseDate.value).add(21,'days').format('YYYY-MM-DD')+
+        '——'+moment(BaseDate.value).add(27,'days').format('YYYY-MM-DD')
+      }
+      tableTheadColumns.value=[prePeriodDate,nextPeriodDate]
+    }
+    callback()
+  }
+  return {
+    tableTheadColumns,
+    BaseDate,
+    weekQuery,
+    toogelDate
+  }
+}

+ 11 - 0
src/views/business_trip_manage/style/trip-dialog.scss

@@ -0,0 +1,11 @@
+
+#Form{
+  padding: 0 40px;
+  .el-form-item__label,.el-form-item__content,.el-radio__label,textarea{
+    font-size: 16px;
+    color: #333333;
+  }
+  .el-form-item__error{
+    font-size: 14px;
+  }
+}