QuYueTing 23 часов назад
Родитель
Сommit
c35b32102e
17 измененных файлов с 469 добавлено и 50 удалено
  1. 28 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java
  2. 6 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/ExceptionInfosSum.java
  3. 8 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/ReportService.java
  4. 40 4
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ExceptionInfosServiceImpl.java
  5. 95 8
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java
  6. 4 4
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java
  7. 26 3
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/demo_index.html
  8. 7 3
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.css
  9. 1 1
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.js
  10. 7 0
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.json
  11. BIN
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.ttf
  12. BIN
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.woff
  13. BIN
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.woff2
  14. 15 0
      fhKeeper/formulahousekeeper/timesheet/src/routes.js
  15. 126 2
      fhKeeper/formulahousekeeper/timesheet/src/views/corpreport/list.vue
  16. 30 17
      fhKeeper/formulahousekeeper/timesheet_h5/src/views/edit/weekEdit.vue
  17. 76 8
      fhKeeper/formulahousekeeper/timesheet_h5/src/views/review/index.vue

+ 28 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java

@@ -3640,5 +3640,33 @@ public class ReportController {
     }
 
 
+    //项目工时排名前十
+    @RequestMapping("/getTop10ProjectReport")
+    public HttpRespMsg getTop10ProjectReport(String ymonth) {
+        User user = userMapper.selectById(request.getHeader("TOKEN"));
+        return reportService.getTop10ProjectReport(user.getCompanyId(), ymonth);
+    }
+
+    //项目工时排名前三部门分配表
+    @RequestMapping("/getTop3ProjectReportGroupByDept")
+    public HttpRespMsg getTop3ProjectReportGroupByDept(String ymonth) {
+        User user = userMapper.selectById(request.getHeader("TOKEN"));
+        return reportService.getTop3ProjectReportGroupByDept(user.getCompanyId(), ymonth);
+    }
+
+    //各部门总工时和人均工时表
+    @RequestMapping("/getProjectReportGroupByDept")
+    public HttpRespMsg getProjectReportGroupByDept(String ymonth) {
+        User user = userMapper.selectById(request.getHeader("TOKEN"));
+        return reportService.getProjectReportGroupByDept(user.getCompanyId(), ymonth);
+    }
+
+    //各部门参与项目数量表
+    @RequestMapping("/getDeptProjectCount")
+    public HttpRespMsg getDeptProjectCount(String ymonth) {
+        User user = userMapper.selectById(request.getHeader("TOKEN"));
+        return reportService.getDeptProjectCount(user.getCompanyId(), ymonth);
+    }
+
 
 }

+ 6 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/ExceptionInfosSum.java

@@ -10,6 +10,7 @@ import lombok.experimental.Accessors;
 
 import java.io.Serializable;
 import java.time.LocalDate;
+import java.util.List;
 
 /**
  * <p>
@@ -29,6 +30,9 @@ public class ExceptionInfosSum {
     @TableField(exist = false)
     private String departmentName;
 
+    @TableField(exist = false)
+    private String createDate;
+
     //迟到次数
     @TableField(exist = false)
     private Integer lateCount = 0;
@@ -45,5 +49,7 @@ public class ExceptionInfosSum {
     @TableField(exist = false)
     private Integer absenteeismCount = 0;
 
+    List<ExceptionInfosSum> childList;
+
 
 }

+ 8 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/ReportService.java

@@ -230,4 +230,12 @@ public interface ReportService extends IService<Report> {
     List<HashMap> getExtraWorkHoursList(String startDate, String endDate, Integer departmentId, String userId, HttpServletRequest request);
 
     HttpRespMsg exportExtraWorkHoursList(String startDate, String endDate, Integer departmentId, String userId, HttpServletRequest request);
+
+    HttpRespMsg getTop10ProjectReport(Integer companyId, String ymonth);
+
+    HttpRespMsg getTop3ProjectReportGroupByDept(Integer companyId, String ymonth);
+
+    HttpRespMsg getProjectReportGroupByDept(Integer companyId, String ymonth);
+
+    HttpRespMsg getDeptProjectCount(Integer companyId, String ymonth);
 }

+ 40 - 4
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ExceptionInfosServiceImpl.java

@@ -78,14 +78,33 @@ public class ExceptionInfosServiceImpl extends ServiceImpl<ExceptionInfosMapper,
         }
 
         if (finalList.size() > 0) {
-            for (ExceptionInfosSum exceptionInfos : finalList) {
-                User user = userList.stream().filter(ul -> ul.getCorpwxUserid().equals(exceptionInfos.getCorpwxUserid())).findFirst().orElse(null);
+            for (ExceptionInfosSum sumItem : finalList) {
+                User user = userList.stream().filter(ul -> ul.getCorpwxUserid().equals(sumItem.getCorpwxUserid())).findFirst().orElse(null);
                 if (user != null) {
-                    exceptionInfos.setUserName(user.getName());
+                    sumItem.setUserName(user.getName());
                     if (user.getDepartmentId() != null && user.getDepartmentId() != 0) {
-                        departmentList.stream().filter(dl -> dl.getDepartmentId().equals(user.getDepartmentId())).findFirst().ifPresent(department -> exceptionInfos.setDepartmentName(department.getDepartmentName()));
+                        departmentList.stream().filter(dl -> dl.getDepartmentId().equals(user.getDepartmentId())).findFirst().ifPresent(department -> sumItem.setDepartmentName(department.getDepartmentName()));
                     }
                 }
+                //按人员获取异常列表,作为childList
+                List<ExceptionInfosSum> childList = new ArrayList<>();
+                LocalDate lastCreateDate = null;
+                for (int i=0; i<list.size(); i++) {
+                    ExceptionInfos exceptionInfo = list.get(i);
+                    if (exceptionInfo.getCorpwxUserid().equals(sumItem.getCorpwxUserid())) {
+                        if (lastCreateDate == null || !lastCreateDate.equals(exceptionInfo.getCreateDate())) {
+                            lastCreateDate = exceptionInfo.getCreateDate();
+                            ExceptionInfosSum childItem = new ExceptionInfosSum();
+                            addExceptionCount(exceptionInfo, childItem);
+                            childItem.setCreateDate(exceptionInfo.getCreateDate().toString());
+                            childItem.setUserName(exceptionInfo.getUserName());
+                            childList.add(childItem);
+                        } else {
+                            addExceptionCount(exceptionInfo, childList.get(childList.size() - 1));
+                        }
+                    }
+                }
+                sumItem.setChildList(childList);
             }
         }
         httpRespMsg.setData(finalList);
@@ -130,6 +149,23 @@ public class ExceptionInfosServiceImpl extends ServiceImpl<ExceptionInfosMapper,
                     String.valueOf(item.getAbsenteeismCount()),
                     String.valueOf(total)
             ));
+            //childList
+            List<ExceptionInfosSum> childList = item.getChildList();
+            if (childList != null && childList.size() > 0) {
+                for (ExceptionInfosSum childItem : childList) {
+                    total = childItem.getLateCount() + childItem.getEarlyLeaveCount()
+                            + childItem.getMissPunchCount() + childItem.getAbsenteeismCount();
+                    exportList.add(Arrays.asList(
+                            childItem.getUserName() == null ? "" : (wxCorpInfo.getSaasSyncContact() == 1 ? ("$userName=" + childItem.getUserName() + "$") : childItem.getUserName()),
+                            childItem.getCreateDate() == null ? "" : childItem.getCreateDate(),
+                            String.valueOf(childItem.getLateCount()),
+                            String.valueOf(childItem.getEarlyLeaveCount()),
+                            String.valueOf(childItem.getMissPunchCount()),
+                            String.valueOf(childItem.getAbsenteeismCount()),
+                            String.valueOf(total)
+                    ));
+                }
+            }
         }
 
         String fileName = "考勤异常报表_" + ymonth + "_" + System.currentTimeMillis();

+ 95 - 8
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java

@@ -3815,24 +3815,24 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                             LocalDate date = entry1.getKey();
                             List<Report> dateReport = entry1.getValue();
 
-                            //获取项目名称,填写工作内容是否有项目名称
-                            boolean findProjectName = false;
+                            //获取项目编号,填写工作内容是否有项目编号
+                            boolean findProjectCode = false;
                             for (Report report : dateReport) {
                                 String content = report.getContent();
                                 Project project = projectList.stream().filter(pro->pro.getId().equals(report.getProjectId())).findFirst().get();
-                                String projectName = project.getProjectName();
-                                if (projectName.length() > 3) {
+                                String projectCode = project.getProjectCode();
+                                if (projectCode.length() > 3) {
                                     //只要前三个字
-                                    projectName = projectName.substring(0, 3);
+                                    projectCode = projectCode.substring(0, 3);
                                 }
                                 if (!StringUtils.isEmpty(content)) {
-                                    if (content.contains(projectName)) {
-                                        findProjectName = true;
+                                    if (content.contains(projectCode)) {
+                                        findProjectCode = true;
                                         break;
                                     }
                                 }
                             }
-                            if (!findProjectName) {
+                            if (!findProjectCode) {
                                 boolean timeMisMatch = false;
                                 //获取合计上班工时数
                                 double sum = dateReport.stream().mapToDouble(Report::getWorkingTime).sum();
@@ -15954,6 +15954,93 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
         return msg;
     }
 
+    @Override
+    public HttpRespMsg getTop10ProjectReport(Integer companyId, String ymonth) {
+        HttpRespMsg msg = new HttpRespMsg();
+        List<Project> projectList = projectMapper.selectList(new QueryWrapper<Project>().select("id, project_code, project_name").eq("company_id", companyId));
+        List<Report> list = reportMapper.selectList(new QueryWrapper<Report>().select("sum(working_time) as working_time, sum(overtime_hours) as overtime_hours, project_id").eq("state", 1).eq("company_id", companyId).eq("date_format(create_date, '%Y-%m')", ymonth).groupBy("project_id").orderByDesc("sum(working_time)").last("limit 10"));
+        list.forEach(report->{
+            Project proj = projectList.stream().filter(p->p.getId().equals(report.getProjectId())).findFirst().get();
+            report.setProjectName(proj.getProjectName());
+            report.setContent(proj.getProjectCode());
+        });
+        List<HashMap> resultList = new ArrayList<>();
+        list.forEach(report->{
+            HashMap<String, Object> map = new HashMap<>();
+            map.put("projectCode", report.getContent());
+            map.put("projectName", report.getProjectName());
+            map.put("workingTime", report.getWorkingTime());
+            map.put("overtimeHours", report.getOvertimeHours());
+            resultList.add(map);
+        });
+        msg.data = resultList;
+        return msg;
+    }
+
+    @Override
+    public HttpRespMsg getTop3ProjectReportGroupByDept(Integer companyId, String ymonth) {
+        HttpRespMsg msg = new HttpRespMsg();
+        List<Report> list = reportMapper.selectList(new QueryWrapper<Report>().select("project_id").eq("company_id", companyId).eq("state", 1).eq("date_format(create_date, '%Y-%m')", ymonth).groupBy("project_id").orderByDesc("sum(working_time)").last("limit 3"));
+        List<Integer> pids = list.stream().map(Report::getProjectId).collect(Collectors.toList());
+        if (!pids.isEmpty()) {
+            List<Report> deptReportList = reportMapper.selectList(new QueryWrapper<Report>().select("dept_id, sum(working_time) as working_time, sum(overtime_hours) as overtime_hours").eq("company_id", companyId).eq("state", 1).eq("date_format(create_date, '%Y-%m')", ymonth).groupBy("dept_id").in("project_id", pids));
+            List<Department> departmentList = departmentMapper.selectList(new QueryWrapper<Department>().select("department_id, department_name").eq("company_id", companyId));
+            List<HashMap> resultList = new ArrayList<>();
+            deptReportList.forEach(report->{
+                HashMap<String, Object> map = new HashMap<>();
+                Department curDept = departmentList.stream().filter(dept->dept.getDepartmentId().equals(report.getDeptId())).findFirst().get();
+                map.put("departmentName", curDept.getDepartmentName());
+                map.put("workingTime", report.getWorkingTime());
+                map.put("overtimeHours", report.getOvertimeHours());
+                resultList.add(map);
+            });
+            msg.data = resultList;
+        }
+
+        return msg;
+    }
+
+    @Override
+    public HttpRespMsg getProjectReportGroupByDept(Integer companyId, String ymonth) {
+        HttpRespMsg msg = new HttpRespMsg();
+        List<Report> deptReportList = reportMapper.selectList(new QueryWrapper<Report>().select("dept_id, sum(working_time) as working_time,count(distinct creator_id) as project_id, sum(overtime_hours) as overtime_hours").eq("company_id", companyId).eq("state", 1).eq("date_format(create_date, '%Y-%m')", ymonth).groupBy("dept_id"));
+        List<Department> departmentList = departmentMapper.selectList(new QueryWrapper<Department>().select("department_id, department_name").eq("company_id", companyId));
+        List<HashMap> resultList = new ArrayList<>();
+        deptReportList.forEach(report->{
+            HashMap<String, Object> map = new HashMap<>();
+            Department curDept = departmentList.stream().filter(dept->dept.getDepartmentId().equals(report.getDeptId())).findFirst().get();
+            map.put("departmentName", curDept.getDepartmentName());
+            map.put("workingTime", report.getWorkingTime());
+            map.put("overtimeHours", report.getOvertimeHours());
+            int membCount = report.getProjectId();
+            //计算人均工时和加班工时
+            double avgWorkingTime = report.getWorkingTime() / membCount;
+            double avgOvertimeHours = report.getOvertimeHours() / membCount;
+            map.put("avgWorkingTime", avgWorkingTime);
+            map.put("avgOvertimeHours", avgOvertimeHours);
+            resultList.add(map);
+        });
+        msg.data = resultList;
+        return msg;
+    }
+
+    @Override
+    public HttpRespMsg getDeptProjectCount(Integer companyId, String ymonth) {
+        HttpRespMsg msg = new HttpRespMsg();
+        List<Report> deptReportList = reportMapper.selectList(new QueryWrapper<Report>().select("dept_id, count(distinct project_id) as project_id").eq("company_id", companyId).eq("state", 1).eq("date_format(create_date, '%Y-%m')", ymonth).groupBy("dept_id"));
+        List<Department> departmentList = departmentMapper.selectList(new QueryWrapper<Department>().select("department_id, department_name").eq("company_id", companyId));
+        List<HashMap> resultList = new ArrayList<>();
+        deptReportList.forEach(report->{
+            HashMap<String, Object> map = new HashMap<>();
+            Department curDept = departmentList.stream().filter(dept->dept.getDepartmentId().equals(report.getDeptId())).findFirst().get();
+            map.put("departmentName", curDept.getDepartmentName());
+            map.put("projectCount", report.getProjectId());
+            resultList.add(map);
+        });
+        msg.data = resultList;
+        return msg;
+    }
+
     public String getWeek(DayOfWeek dayOfWeek){
         //获取中文形式的星期几
         String dayOfWeekChinese = "";

+ 4 - 4
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java

@@ -1186,10 +1186,10 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
                         time = 0;
                     } else {
                         //盛立
-                        if (corpInfo.getCompanyId() == 481) {
-                            time = DateTimeUtil.getHalfHoursFromDouble(time);
-                        } else {
+                        if (corpInfo.getCompanyId() == 469) {
                             time = DateTimeUtil.getHoursFromDouble(time);
+                        } else {
+                            time = DateTimeUtil.getHalfHoursFromDouble(time);
                         }
                     }
                     userCorpwxTime.setWorkHours(time);
@@ -2203,7 +2203,7 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
 
     private void saveException(UserCorpwxTime userCorpwxTime,JSONArray exceptionInfos) {
         //删除当日的异常
-        exceptionInfosMapper.delete(new QueryWrapper<ExceptionInfos>().eq("corpwxtime_id", userCorpwxTime.getId()));
+        exceptionInfosMapper.delete(new QueryWrapper<ExceptionInfos>().eq("create_date", userCorpwxTime.getCreateDate()).eq("corpwx_userid", userCorpwxTime.getCorpwxUserid()).eq("company_id", userCorpwxTime.getCompanyId()));
         if (exceptionInfos != null && exceptionInfos.size() > 0) {
             //重新保存
             for (int i = 0; i < exceptionInfos.size(); i++) {

+ 26 - 3
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/demo_index.html

@@ -54,6 +54,12 @@
       <div class="content unicode" style="display: block;">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+              <span class="icon iconfont">&#xe669;</span>
+                <div class="name">data-analysis</div>
+                <div class="code-name">&amp;#xe669;</div>
+              </li>
+          
             <li class="dib">
               <span class="icon iconfont">&#xe619;</span>
                 <div class="name">bug</div>
@@ -528,9 +534,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1766996481311') format('woff2'),
-       url('iconfont.woff?t=1766996481311') format('woff'),
-       url('iconfont.ttf?t=1766996481311') format('truetype');
+  src: url('iconfont.woff2?t=1779780858773') format('woff2'),
+       url('iconfont.woff?t=1779780858773') format('woff'),
+       url('iconfont.ttf?t=1779780858773') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -556,6 +562,15 @@
       <div class="content font-class">
         <ul class="icon_lists dib-box">
           
+          <li class="dib">
+            <span class="icon iconfont firerock-icondata-analysis"></span>
+            <div class="name">
+              data-analysis
+            </div>
+            <div class="code-name">.firerock-icondata-analysis
+            </div>
+          </li>
+          
           <li class="dib">
             <span class="icon iconfont firerock-iconbug"></span>
             <div class="name">
@@ -1267,6 +1282,14 @@
       <div class="content symbol">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#firerock-icondata-analysis"></use>
+                </svg>
+                <div class="name">data-analysis</div>
+                <div class="code-name">#firerock-icondata-analysis</div>
+            </li>
+          
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#firerock-iconbug"></use>

+ 7 - 3
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 2390497 */
-  src: url('iconfont.woff2?t=1766996481311') format('woff2'),
-       url('iconfont.woff?t=1766996481311') format('woff'),
-       url('iconfont.ttf?t=1766996481311') format('truetype');
+  src: url('iconfont.woff2?t=1779780858773') format('woff2'),
+       url('iconfont.woff?t=1779780858773') format('woff'),
+       url('iconfont.ttf?t=1779780858773') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,10 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.firerock-icondata-analysis:before {
+  content: "\e669";
+}
+
 .firerock-iconbug:before {
   content: "\e619";
 }

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.js


+ 7 - 0
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.json

@@ -5,6 +5,13 @@
   "css_prefix_text": "firerock-icon",
   "description": "",
   "glyphs": [
+    {
+      "icon_id": "21823759",
+      "name": "data-analysis",
+      "font_class": "data-analysis",
+      "unicode": "e669",
+      "unicode_decimal": 58985
+    },
     {
       "icon_id": "5524676",
       "name": "bug",

BIN
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.ttf


BIN
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.woff


BIN
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.woff2


+ 15 - 0
fhKeeper/formulahousekeeper/timesheet/src/routes.js

@@ -56,6 +56,9 @@ import profession from './views/profession/list';
 //企业报表
 import corpReport from './views/corpreport/list';
 
+// 工时看板
+import dashboard from './views/dashboard/index';
+
 //审批流程
 import workflow from './views/workflow/report';
 // 权限管理
@@ -181,6 +184,18 @@ export const fixedRouter = [
     },
 ];
 export const allRouters = [//组织架构
+    // 工时看板
+    {
+        path: '/',
+        component: Home,
+        name: '工时看板',
+        iconCls: 'iconfont firerock-icondata-analysis',
+        leaf: true,
+        children: [
+            { path: '/dashboard', component: dashboard, name: '工时看板' },
+        ],
+        meta: { text: '工时看板' }
+    },
     //工时报告
     {
         path: '/',

+ 126 - 2
fhKeeper/formulahousekeeper/timesheet/src/views/corpreport/list.vue

@@ -6793,7 +6793,111 @@
                 :height="+tableHeight"
                 style="width: 100%"
                 :max-height="+tableHeight + 50"
+                row-key="rowKey"
+                :expand-row-keys="exceptionExpandedRows"
+                @expand-change="handleExceptionExpandChange"
               >
+                <el-table-column type="expand">
+                  <template slot-scope="scope">
+                    <el-table
+                      v-if="scope.row.childList && scope.row.childList.length > 0"
+                      :data="scope.row.childList"
+                      border
+                      size="small"
+                      :show-header="false"
+                      style="width: 100%; margin: 8px 0;"
+                    >
+                      <!-- 占位列,撑开左侧空间,与主表的序号+姓名+部门列对齐 -->
+                      <el-table-column
+                        label="序号"
+                        width="47"
+                        align="center"
+                      ></el-table-column>
+                      <el-table-column
+                        label="序号"
+                        width="60"
+                        align="center"
+                      ></el-table-column>
+                      <el-table-column
+                        prop="userName"
+                        align="center"
+                        label="姓名"
+                      >
+                        <template slot-scope="scope">
+                          <span v-if="user.userNameNeedTranslate == '1'">
+                            <TranslationOpenDataText
+                              type="userName"
+                              :openid="scope.row.corpwxUserid"
+                            ></TranslationOpenDataText>
+                          </span>
+                          <span v-if="user.userNameNeedTranslate != '1'">
+                            {{ scope.row.userName }}
+                          </span>
+                        </template>
+                      </el-table-column>
+                      <el-table-column
+                        prop="createDate"
+                        align="center"
+                        label="日期"
+                      ></el-table-column>
+                      <el-table-column
+                        prop="lateCount"
+                        align="center"
+                        label="迟到次数"
+                        width="100"
+                      >
+                        <template slot-scope="childScope">
+                          {{ childScope.row.lateCount || 0 }}
+                        </template>
+                      </el-table-column>
+                      <el-table-column
+                        prop="earlyLeaveCount"
+                        align="center"
+                        label="早退次数"
+                        width="100"
+                      >
+                        <template slot-scope="childScope">
+                          {{ childScope.row.earlyLeaveCount || 0 }}
+                        </template>
+                      </el-table-column>
+                      <el-table-column
+                        prop="missPunchCount"
+                        align="center"
+                        label="缺卡次数"
+                        width="100"
+                      >
+                        <template slot-scope="childScope">
+                          {{ childScope.row.missPunchCount || 0 }}
+                        </template>
+                      </el-table-column>
+                      <el-table-column
+                        prop="absenteeismCount"
+                        align="center"
+                        label="旷工次数"
+                        width="100"
+                      >
+                        <template slot-scope="childScope">
+                          {{ childScope.row.absenteeismCount || 0 }}
+                        </template>
+                      </el-table-column>
+                      <el-table-column
+                        align="center"
+                        label="合计异常次数"
+                        width="120"
+                      >
+                        <template slot-scope="childScope">
+                          {{
+                            (childScope.row.lateCount || 0) +
+                            (childScope.row.earlyLeaveCount || 0) +
+                            (childScope.row.missPunchCount || 0) +
+                            (childScope.row.absenteeismCount || 0)
+                          }}
+                        </template>
+                      </el-table-column>
+                    </el-table>
+                    <div v-else style="text-align: center; color: #999; padding: 10px;">暂无日期明细数据</div>
+                  </template>
+                </el-table-column>
                 <el-table-column
                   type="index"
                   label="序号"
@@ -8219,6 +8323,7 @@ export default {
       // 考勤异常表
       exceptionReportList: [],
       exceptionReportLoading: false,
+      exceptionExpandedRows: [], // 考勤异常表展开行的 rowKey 列表
     };
   },
   computed: {},
@@ -12363,7 +12468,7 @@ export default {
       if (!picStr) {
         return;
       }
-      var str = picStr.replace("@", ",");
+      var str = picStr.replace(/@/g, ",");
       var attachments = JSON.parse(str);
 
       // 使用viewer控件显示图片
@@ -12677,6 +12782,7 @@ export default {
     // 获取考勤异常表
     getExceptionReport() {
       this.exceptionReportLoading = true;
+      this.exceptionExpandedRows = []; // 重置展开状态
       let parameter = {
         ymonth: this.monthPersonnel || this.dayjs(new Date()).format("YYYY-MM"),
       };
@@ -12685,13 +12791,31 @@ export default {
       }
       this.postData(`/exception-infos/getExceptionReport`, parameter)
         .then((res) => {
-          this.exceptionReportList = Array.isArray(res.data) ? res.data : [];
+          const list = Array.isArray(res.data) ? res.data : [];
+          // 为每条记录添加唯一 rowKey
+          this.exceptionReportList = list.map((item, index) => ({
+            ...item,
+            rowKey: item.userId ? String(item.userId) : `row_${index}`,
+          }));
         })
         .finally(() => {
           this.exceptionReportLoading = false;
         });
     },
 
+    // 考勤异常表展开/收起行处理
+    handleExceptionExpandChange(row, expandedRows) {
+      const key = row.rowKey;
+      const idx = this.exceptionExpandedRows.indexOf(key);
+      if (idx > -1) {
+        // 已展开,点击收起
+        this.exceptionExpandedRows.splice(idx, 1);
+      } else {
+        // 未展开,点击展开
+        this.exceptionExpandedRows.push(key);
+      }
+    },
+
     // 获取施工进度表
     getConstructionStage() {
       this.constructionStageLoading = true;

+ 30 - 17
fhKeeper/formulahousekeeper/timesheet_h5/src/views/edit/weekEdit.vue

@@ -28,12 +28,12 @@
                     :disabled="canSelect(item)" v-if="user.timeType.enableNewWeeklyfill == 1">{{ weekText(dateRange[index]) }}</van-button>
                 </van-grid-item>
                 <!-- 周总结 -->
-                <van-grid-item v-if="user.timeType.enableNewWeeklyfill == 1" :class="999 == inbtn ? 'inbtn' : ''">
+                <van-grid-item v-if="user.timeType.enableNewWeeklyfill == 1 || user.timeType.enableNewWeeklyfill == 2" :class="999 == inbtn ? 'inbtn' : ''">
                     <van-button class="selectgxbtn"
                     type="default"
                     size="mini" @click="switchWeekly(999)">{{ user.companyId == wuqiId ? '周报' : '周总结' }}</van-button>
                 </van-grid-item>
-                <van-grid-item v-if="weekIndex != 7 && user.timeType.enableNewWeeklyfill != 1">
+                <van-grid-item v-if="weekIndex != 7 && user.timeType.enableNewWeeklyfill == 0">
                     <van-button type="default" size="mini" class="selectgxbtn" @click="addWeekIndex()" icon="plus"></van-button>
                 </van-grid-item>
                 <div class="kaoqin2" v-if="kaoqinText"><span>当日考勤:</span><span>{{kaoqinText}}</span></div>
@@ -231,20 +231,20 @@
                         <van-popup v-model="item.showPickDegree" position="bottom" @click-overlay="overlayPopup(index, 'showPickDegree')">
                             <div class="chooseSomeone">
                                 <div class="chooseSomeoneFlex1">
-                                    <!-- 选 -->
-                                    <van-radio-group v-model="item.radioPickDegree" v-if="!user.timeType.customDegreeMultiple" @change="$forceUpdate()">
+                                    <!-- 选 -->
+                                    <van-radio-group v-model="currentPickDegree" v-if="!user.timeType.customDegreeMultiple">
                                         <van-radio v-for="uitem in item.wuduList" :key="uitem.id" :name="uitem.id" style="padding:10px">
                                             {{ uitem.name ? uitem.name : '' }}
                                         </van-radio>
                                     </van-radio-group>
                                     <!-- 多选 -->
-                                    <van-checkbox-group v-model="item.radioPickDegree" v-if="user.timeType.customDegreeMultiple" @change="$forceUpdate()">
+                                    <van-checkbox-group v-model="currentPickDegree" v-if="user.timeType.customDegreeMultiple">
                                         <van-checkbox v-for="uitem in item.wuduList" :key="uitem.id" :name="uitem.id" style="padding:10px">
                                             {{ uitem.name ? uitem.name : '' }}
                                         </van-checkbox>
                                     </van-checkbox-group>
                                 </div>
-                                <van-button @click="choseProjects(item.radioPickDegree, item.wuduList)">
+                                <van-button @click="choseProjects(currentPickDegree, item.wuduList)">
                                     确定
                                 </van-button>
                             </div>
@@ -463,7 +463,7 @@
             
 
             <!-- 周总结 -->
-            <div v-if="user.timeType.enableNewWeeklyfill == 1 && inbtn == 999">
+            <div v-if="(user.timeType.enableNewWeeklyfill == 1 ||user.timeType.enableNewWeeklyfill == 2)  && inbtn == 999">
                 <div class="van-cell-group__title">{{ user.companyId == wuqiId ? '周报' : '周总结'}}</div>
                 <van-field
                     v-model.trim="summary"
@@ -687,6 +687,7 @@
                 wuqiId: '1071',
                 doYouWantToDisplayTheWorkOrder: true,
                 doYouWantToDisplayTheWorkOrderGongdan: true,
+                currentPickDegree: '', // 用于中转相关维度弹窗的选中值(响应式)
                 
                 // 人员选择器相关数据
                 employeeSelector: {
@@ -1972,7 +1973,16 @@
                 const { state } = item
                 if(state < 1) return
                 this.clickIndex = i;
-                this.currentForm.domains[this.clickIndex].showPickDegree = true;
+                // 将当前已选值同步到响应式的 currentPickDegree,确保弹窗打开时显示正确的选中状态
+                const existingValue = item.radioPickDegree
+                if(this.user.timeType.customDegreeMultiple) {
+                    // 多选:确保是数组
+                    this.currentPickDegree = Array.isArray(existingValue) ? existingValue.slice() : (existingValue ? [existingValue] : [])
+                } else {
+                    // 单选:确保是基本值
+                    this.currentPickDegree = (existingValue !== undefined && existingValue !== null) ? existingValue : ''
+                }
+                this.$set(this.currentForm.domains[this.clickIndex], 'showPickDegree', true);
                 var proId = this.currentForm.domains[this.clickIndex].projectId
                 this.dimension(proId)
             },
@@ -2033,7 +2043,10 @@
                         this.currentForm.domains[this.clickIndex].degreeId = list.map(item => item.id).join(',');
                     }
                 }
-                this.currentForm.domains[this.clickIndex].showPickDegree = false;
+                // 将 currentPickDegree 的值同步回 item.radioPickDegree,保持数据一致
+                this.$set(this.currentForm.domains[this.clickIndex], 'radioPickDegree', 
+                    Array.isArray(value) ? value.slice() : value);
+                this.$set(this.currentForm.domains[this.clickIndex], 'showPickDegree', false);
 
                 console.log(this.currentForm.domains[this.clickIndex], '再次')
                 this.$forceUpdate();
@@ -2177,7 +2190,11 @@
                         // if(flag) {
                         //     this.clickIndex = newClickIndex
                         // }
-                        this.currentForm.domains[this.clickIndex].wuduList = res.data;
+                        this.$set(this.currentForm.domains[this.clickIndex], 'wuduList', res.data);
+                        // 确保 radioPickDegree 是响应式的
+                        if(this.currentForm.domains[this.clickIndex].radioPickDegree === undefined) {
+                            this.$set(this.currentForm.domains[this.clickIndex], 'radioPickDegree', this.user.timeType.customDegreeMultiple ? [] : '');
+                        }
                         // let degreeId = this.currentForm.domains[this.clickIndex].degreeId
                         // for(let i in degreeId) {
                         //     degreeId[i] = +degreeId[i]
@@ -2384,7 +2401,6 @@
                         return
                     }
                 }
-                console.log(workContentState)
                 // 判断工作事项是否为必填
                 if(workContentState == '1') {
                     const { error, next } = this.judgeWorkContent(this.form)
@@ -2460,7 +2476,7 @@
                 }
                 let formData = new URLSearchParams()
                 // 周总结
-                if(enableNewWeeklyfill == 1) {
+                if(enableNewWeeklyfill == 1 || enableNewWeeklyfill == 2) {
                     formData.append("summary", this.summary);
                 }
                 for(let formIndex=0;formIndex<this.form.length;formIndex++){
@@ -2628,7 +2644,7 @@
                         } else {
                             formData.append("customText", "-");
                         }
-                        
+
                         if (this.form[formIndex].domains[i].id) {
                             formData.append("id", this.form[formIndex].domains[i].id);
                         } else {
@@ -2686,8 +2702,6 @@
                             formData.append("basecostId", this.form[formIndex].domains[i].basecostId);
                         }
                         
-
-
                         //项目专业进度
                         if (this.form[formIndex].domains[i].professionProgress) {
                             let m = JSON.stringify(this.form[formIndex].domains[i].professionProgress);
@@ -2743,7 +2757,6 @@
                                 formData.append("content", '-');
                             }
                         }
-
                         //审核人
                         if (this.form[formIndex].domains[i].projectAuditorId) {
                             formData.append("projectAuditorId", this.form[formIndex].domains[i].projectAuditorId);
@@ -2768,7 +2781,6 @@
                                 this.$toast.fail('请选择'+this.weekArr[formIndex]+'投入项目的审核人');
                                 return;
                             }
-                            return;
                         }
                         if (this.user.timeType.reportWorkflow) {
                             //校验部门节点是否有审核人
@@ -2780,6 +2792,7 @@
                                 }
                             }   
                         }
+                        
                         if(reportExtraField4Name || reportExtraField5Name) {
                             formData.append("extraField4", this.form[formIndex].domains[i].extraField4 || '');
                             formData.append("extraField5", this.form[formIndex].domains[i].extraField5 || '');

+ 76 - 8
fhKeeper/formulahousekeeper/timesheet_h5/src/views/review/index.vue

@@ -190,15 +190,24 @@
                         <van-button size="small" type="info" @click="approve(item.userId, item)" style="margin-right: 4vw;">通过</van-button>
                         <van-button size="small" type="danger" @click="showDenyDialog(item.userId,0,item.dateStr, item)">驳回</van-button>
                     </div>
-                    <van-popup v-model="imgShow" position="bottom" closeable >
-                        <van-swipe class="my-swipe"  indicator-color="white">
-                        <van-swipe-item v-for="(picItem, index) in tmpPics" :key="index">
-                            <img :src="picItem" style="width:100%;" />
-                        </van-swipe-item>
-                        </van-swipe>
-                    </van-popup>
                 </van-panel>
             </van-skeleton>
+            <van-popup v-model="imgShow" position="bottom" closeable >
+                <div class="img-swipe-wrapper">
+                    <div class="img-swipe-indicator">{{ currentImgIndex + 1 }}/{{ tmpPics.length }}</div>
+                    <van-swipe class="my-swipe" ref="imgSwipe" :initial-swipe="currentImgIndex" indicator-color="white" @change="onSwipeChange">
+                        <van-swipe-item v-for="(picItem, idx) in tmpPics" :key="idx">
+                            <img :src="picItem" style="width:100%;" />
+                        </van-swipe-item>
+                    </van-swipe>
+                    <div class="img-swipe-prev" @click="prevImg" v-if="tmpPics.length > 1">
+                        <van-icon name="arrow-left" />
+                    </div>
+                    <div class="img-swipe-next" @click="nextImg" v-if="tmpPics.length > 1">
+                        <van-icon name="arrow" />
+                    </div>
+                </div>
+            </van-popup>
             <van-popup v-model="denyReasonDialog" position="bottom" closeable >
                 <van-cell>请输入原因</van-cell>
                 <van-field class="form_input"
@@ -234,6 +243,7 @@
                 denyReasonDialog:false,
                 tmpPics:[],
                 imgShow: false,
+                currentImgIndex: 0,
                 user: JSON.parse(localStorage.userInfo),
                 minDate: new Date(2010, 0, 1),
                 maxDate: new Date(new Date().getFullYear(),new Date().getMonth(),new Date().getDate()),
@@ -453,8 +463,27 @@
                 }).catch(err=> {this.$toast.clear();});
             },
             showLargeImg(item, index) {
-                this.imgShow = true;
                 this.tmpPics = item;
+                this.currentImgIndex = (index !== undefined && index !== null) ? index : 0;
+                this.imgShow = true;
+                this.$nextTick(() => {
+                    if (this.$refs.imgSwipe) {
+                        this.$refs.imgSwipe.swipeTo(this.currentImgIndex, { immediate: true });
+                    }
+                });
+            },
+            onSwipeChange(index) {
+                this.currentImgIndex = index;
+            },
+            prevImg() {
+                if (this.$refs.imgSwipe) {
+                    this.$refs.imgSwipe.prev();
+                }
+            },
+            nextImg() {
+                if (this.$refs.imgSwipe) {
+                    this.$refs.imgSwipe.next();
+                }
             },
             // 返回
             back() {
@@ -735,6 +764,45 @@
             line-height: 30px;
         }
     }
+    .img-swipe-wrapper {
+        position: relative;
+        background: #000;
+    }
+    .img-swipe-indicator {
+        position: absolute;
+        top: 10px;
+        left: 50%;
+        transform: translateX(-50%);
+        z-index: 10;
+        background: rgba(0, 0, 0, 0.5);
+        color: #fff;
+        font-size: 14px;
+        padding: 2px 12px;
+        border-radius: 12px;
+    }
+    .img-swipe-prev,
+    .img-swipe-next {
+        position: absolute;
+        top: 50%;
+        transform: translateY(-50%);
+        z-index: 10;
+        width: 36px;
+        height: 36px;
+        background: rgba(0, 0, 0, 0.4);
+        border-radius: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        color: #fff;
+        font-size: 18px;
+        cursor: pointer;
+    }
+    .img-swipe-prev {
+        left: 8px;
+    }
+    .img-swipe-next {
+        right: 8px;
+    }
 </style>
 <style lang="less">
     .van-nav-bar .van-icon , .van-nav-bar__text {