Bladeren bron

AI解析优化,增加提示词

QuYueTing 4 uur geleden
bovenliggende
commit
397aa67ce7

+ 14 - 0
fhKeeper/formulahousekeeper/management-platform/pom.xml

@@ -269,6 +269,20 @@
             <version>2.0.1.RELEASE</version>
         </dependency>
 
+        <!-- 阿里云通义千问SDK -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>dashscope-sdk-java</artifactId>
+            <version>2.12.0</version>
+        </dependency>
+
+        <!-- FuzzyWuzzy 模糊匹配库 -->
+        <dependency>
+            <groupId>me.xdrop</groupId>
+            <artifactId>fuzzywuzzy</artifactId>
+            <version>1.4.0</version>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 7 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java

@@ -51,6 +51,8 @@ import java.util.stream.Collectors;
 @RestController
 @RequestMapping("/report")
 public class ReportController {
+    @Resource
+    private UserRecentReportService userRecentReportService;
     @Autowired
     private ReportService reportService;
     @Resource
@@ -277,7 +279,6 @@ public class ReportController {
      * @param exportType 导出类型
      * @param projectId 项目ID
      * @param stateKey 状态键 0-全部 1-待审核 2-已通过 3-已驳回
-     * @param departmentIds 部门ID列表,多个用逗号分隔
      * @param plate 板块
      * @param pageIndex 页码,从1开始
      * @param pageSize 每页大小
@@ -315,7 +316,7 @@ public class ReportController {
      * @param exportType 导出类型
      * @param projectId 项目ID
      * @param stateKey 状态键 0-全部 1-待审核 2-已通过 3-已驳回
-     * @param departmentIds 部门ID列表,多个用逗号分隔
+     * @param departmentId 部门ID列表
      * @param plate 板块
      * @return Excel文件下载地址
      */
@@ -2414,6 +2415,10 @@ public class ReportController {
             List<Report> updateList = setWorktimeFinance(comTimeType, latestReportList);
             reportService.updateBatchById(updateList);
         }
+        //保存最近填报的记录,方便AI解析使用; 异步执行
+        if (company.getEnableAi()) {
+            userRecentReportService.addReport(user.getId(), company.getId(), reportList);
+        }
         return httpRespMsg;
     }
 

+ 7 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/Company.java

@@ -163,6 +163,13 @@ public class Company extends Model<Company> {
     @TableField("project_progress_mode")
     private Integer projectProgressMode;
 
+    /**
+     * 是否开启AI解析功能
+     */
+    @TableField("enable_ai")
+    private Boolean enableAi;
+
+
 
     @Override
     protected Serializable pkVal() {

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

@@ -218,4 +218,5 @@ public interface ReportService extends IService<Report> {
     HttpRespMsg getReportGroupByDay(String startDate, String endDate, String userId, Integer departmentId, Integer projectId, HttpServletRequest request);
 
     HttpRespMsg exportReportGroupByDay(String startDate, String endDate, String userId, Integer departmentId, Integer projectId, HttpServletRequest request);
+
 }

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

@@ -502,6 +502,14 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                     }
                 }
             }
+
+            //针对柘中,增加重新设置customText
+            if (companyId == 4811) {
+                for (Map map : nameList) {
+                    List<Map<String, Object>> reportList = (List<Map<String, Object>>) map.get("data");
+                    changeCustomText(reportList);
+                }
+            }
             if (timeType.getEnableNewWeeklyfill() == 1) {
                 //新版按周填报有周总结
                 List<Integer> batchIds = new ArrayList<>();
@@ -2292,6 +2300,10 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                     }
                 }
             }
+            //针对柘中,增加重新设置customText
+            if (companyId == 4811) {
+                changeCustomText(auditReportList);
+            }
             //抽取姓名,进行分组
             List<Map<String, Object>> nameList = new ArrayList<Map<String, Object>>();
             Map<String, Object> lastName = null;
@@ -2515,6 +2527,37 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
         return httpRespMsg;
     }
 
+    private void changeCustomText(List<Map<String, Object>> reportList) {
+        for (Map<String, Object> mapItem : reportList) {
+            String customText = (String)mapItem.get("customText");
+            if (customText != null) {
+                String customTextShow = "";
+                if (customText.contains(",")) {
+                    // customText格式: id-name,多个用中文逗号隔开; 需要把id-去掉,直接使用name拼接
+                    String[] split = customText.split(",");
+                    for (int i = 0; i < split.length; i++) {
+                        if (split[i].contains("-")) {
+                            split[i] = split[i].substring(split[i].indexOf("-") + 1);
+                        }
+                        customTextShow += split[i];
+                        if (i < split.length - 1) {
+                            customTextShow += ",";
+                        }
+                    }
+                    mapItem.put("customText", customTextShow);
+                } else {
+                    //去掉前面的id-
+                    if (customText.contains("-")) {
+                        customText = customText.substring(customText.indexOf("-") + 1);
+                    } else {
+                        customText = customText;
+                    }
+                    mapItem.put("customText", customText);
+                }
+            }
+        }
+    }
+
     //审核通过某天某人的某报告
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -13557,7 +13600,7 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                 return null;
             }
             
-            TimeType timeType = timeTypeMapper.selectById(companyId);
+//            TimeType timeType = timeTypeMapper.selectById(companyId);
             DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
             LocalDate localStart = LocalDate.parse(startDate, dtf);
             LocalDate localEnd = LocalDate.parse(endDate, dtf);
@@ -13565,7 +13608,7 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
             // 获取该用户在指定时间段内的日报记录
             List<Report> reportList = reportMapper.selectList(new QueryWrapper<Report>()
                     .select("id,create_date,creator_id, state")
-                    .eq("creator_id", userId)
+                    .eq("creator_id", userId).ne("state", 2).ne("state", 3)//状态不是驳回和待提交的, 也就是提交了待审核或者已通过的才算有效的
                     .between("create_date", localStart, localEnd));
             
             // 获取请假数据,请假的不算漏填
@@ -13578,7 +13621,7 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                     .lt("start_date", localEnd));
             
             // 获取节假日设置
-            List<HolidaySetting> holidaySettingList = holidaySettingMapper.selectList(new QueryWrapper<HolidaySetting>().eq("company_id", companyId));
+//            List<HolidaySetting> holidaySettingList = holidaySettingMapper.selectList(new QueryWrapper<HolidaySetting>().eq("company_id", companyId));
             
             // 企微请假查询
             CompanyDingding companyDingding = companyDingdingMapper.selectOne(new QueryWrapper<CompanyDingding>().eq("company_id", companyId));
@@ -13608,11 +13651,11 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                 }
                 
                 // 去掉非工作日
-                Boolean workDay = timeTypeService.isWorkDay(companyId, date, timeType, holidaySettingList);
-                if (!workDay) {
-                    continue;
-                }
-                
+//                Boolean workDay = timeTypeService.isWorkDay(companyId, date, timeType, holidaySettingList);
+//                if (!workDay) {
+//                    continue;
+//                }
+
                 // 检查是否请假
                 boolean leaveOnTheDay = leaveSheetList.stream().anyMatch(leaveSheet -> 
                     leaveSheet.getOwnerId().equals(targetUser.getId()) &&

+ 9 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/UserServiceImpl.java

@@ -653,6 +653,14 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
             }
             moduleList.add(findIndex,centerManageModule);
         }
+        //定制菜单:陕西柘中建设工程有限公司
+        if (company.getId() == 10 || company.getId() == 4811) {
+            SysModule proModule = new SysModule();
+            proModule.setName("人员管理");
+            proModule.setPath("/employeeManagement");
+            proModule.setId(0);
+            moduleList.add(proModule);
+        }
         //组装层级关系,默认只有两级
         List<SysModule> menuList = new ArrayList<>();
         for (SysModule module : moduleList) {
@@ -2843,7 +2851,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
         System.out.println("tableName==="+tableName);
         ReportForm reportForm = reportFormMapper.selectOne(new QueryWrapper<ReportForm>().eq("report_form_name", tableName));
         List<SysFunction> sysFunctionList = sysFunctionMapper.selectList(new QueryWrapper<SysFunction>().eq("report_form_id", reportForm.getId()));
-        String[] allowTables = new String[]{"工时成本预警表","日报待审核","客户项目利润表","异常工时表","餐补表","工时日报表","日报明细表","项目薪资成本表", "部门成本表"};
+        String[] allowTables = new String[]{"日报待审核统计","工时成本预警表","日报待审核","客户项目利润表","异常工时表","餐补表","工时日报表","日报明细表","项目薪资成本表", "部门成本表"};
         String allName = sysFunctionList.stream().filter(sl -> sl.getName().contains("全公司")||sl.getName().contains("全部")|| Arrays.stream(allowTables).anyMatch(sl.getName()::equals)).findFirst().get().getName();
         String deptName = sysFunctionList.stream().filter(sl -> sl.getName().contains("负责部门")||sl.getName().contains("负责")|| Arrays.stream(allowTables).anyMatch(sl.getName()::equals)).findFirst().get().getName();
         List<SysRichFunction> functionAllList = sysFunctionMapper.getRoleFunctions(user.getRoleId(),allName);

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

@@ -1120,6 +1120,10 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
                             } else {
                                 restTime = 1.0 * (workRules.getJSONObject(1).getIntValue("work_sec") - workRules.getJSONObject(0).getIntValue("off_work_sec")) / 3600;
                             }
+                            //特殊处理,艾棣维欣
+                            if (corpInfo.getCompanyId() == 8607) {
+                                restTime = 0.0;
+                            }
                         }
                     }
                     UserCorpwxTime userCorpwxTime=new UserCorpwxTime();
@@ -1493,8 +1497,13 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
                         baseAfternoonEnd = "18:00";
                     } else if (workRules.size() == 1) {
                         baseMorningStart = DateTimeUtil.getTimeFromSeconds(workRules.getJSONObject(0).getIntValue("work_sec"));
-                        baseMorningEnd = "12:00";
-                        baseAfternoonStart = "13:00";
+                        if (corpInfo.getCompanyId() == 8607) {
+                            baseMorningEnd = "12:00";
+                            baseAfternoonStart = "12:00";
+                        } else {
+                            baseMorningEnd = "12:00";
+                            baseAfternoonStart = "13:00";
+                        }
                         baseAfternoonEnd = DateTimeUtil.getTimeFromSeconds(workRules.getJSONObject(0).getIntValue("off_work_sec"));
                     } else {
                         baseMorningStart = DateTimeUtil.getTimeFromSeconds(workRules.getJSONObject(0).getIntValue("work_sec"));
@@ -1508,6 +1517,10 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
                     } else {
                         restTime = 1.0 * (workRules.getJSONObject(1).getIntValue("work_sec") - workRules.getJSONObject(0).getIntValue("off_work_sec")) / 3600;
                     }
+                    //针对 艾棣维欣
+                    if (corpInfo.getCompanyId() == 8607) {
+                        restTime = 0.0;
+                    }
                     LocalDate localDate = LocalDateTime.ofInstant(Instant.ofEpochSecond(time), ZoneId.systemDefault()).toLocalDate();
                     UserCorpwxTime ct = new UserCorpwxTime();
                     ct.setCompanyId(corpInfo.getCompanyId());
@@ -1789,7 +1802,7 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
                                 }
                             }
                         }
-                        if (corpInfo.getCompanyId() == 469 || corpInfo.getCompanyId() == 7) {
+                        if (corpInfo.getCompanyId() == 469 || corpInfo.getCompanyId() == 7 || corpInfo.getCompanyId() == 8607) {
                             needRecaculate = true;//赛元微电子,都需要重新计算
                         }
                         double timeDelta = 0;
@@ -1927,6 +1940,7 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
                             timeDelta = DateTimeUtil.getHoursFromDouble(timeDelta);
                         }
                         if (needRecaculate) {
+                            System.out.println("timeDelta==========" + timeDelta+", leaveTIme==" + ct.getAskLeaveTime());
                             workHours = timeDelta - ct.getAskLeaveTime();
                         } else {
                             workHours = timeDelta;

+ 2 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/CompanyMapper.xml

@@ -27,11 +27,12 @@
         <result column="reg_from" property="regFrom" />
         <result column="non_project_simple" property="nonProjectSimple" />
         <result column="project_progress_mode" property="projectProgressMode" />
+        <result column="enable_ai" property="enableAi" />
     </resultMap>
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, company_name, staff_count_max, expiration_date, set_meal, package_worktime, package_project, package_contract, package_oa, package_etimecard, package_expense, package_customer, package_engineering, package_simple, package_finance, package_provider, package_project_approval, package_device, is_international, create_date, reg_from, non_project_simple, project_progress_mode
+        id, company_name, staff_count_max, expiration_date, set_meal, package_worktime, package_project, package_contract, package_oa, package_etimecard, package_expense, package_customer, package_engineering, package_simple, package_finance, package_provider, package_project_approval, package_device, is_international, create_date, reg_from, non_project_simple, project_progress_mode, enable_ai
     </sql>
 
 </mapper>

+ 0 - 4
fhKeeper/formulahousekeeper/timesheet/src/main.js

@@ -165,10 +165,8 @@ router.beforeEach((to, from, next) => {
                     } else {
                         var customMenu = filterRouter.filter(r=>{return r.name == '自定义数值统计'});
                         if (customMenu.length > 0) {    
-                            console.log(customMenu,'111');
                             customMenu[0].children[0].name = user.timeType.customDataName + '统计';
                         }
-                        
                     }
                     if (user.timeType.customDegreeActive == 0) {
                         filterRouter = filterRouter.filter(r=>{return r.name != '研究中心管理'});
@@ -245,11 +243,9 @@ router.beforeEach((to, from, next) => {
                     //检查是否开启了自定义审批流
                     if (user.timeType.reportWorkflow == 0) {
                         getRoutes = getRoutes.filter(r=>{return r.name != '审批流设置'});
-                        console.log(getRoutes);
                     }
                     global.antRouter = fixedRouter.concat(getRoutes);
                     router.addRoutes(fixedRouter.concat(getRoutes));
-                    console.log(router);
                     router.options.routes = fixedRouter.concat(getRoutes);
                     router.push({ path: to.path })
                 }

+ 9 - 1
fhKeeper/formulahousekeeper/timesheet/src/permissions.js

@@ -123,6 +123,7 @@ const StringUtil = {
         reportAbnormal: false, //异常工时表
         allowance: false, //餐补表
         reportDailyWorkHours: false, //工时日报表
+        reportDashboard: false, // 工时分析报告
         // 请假模块
         leaveFil : false, // 请假填报 // 
         leaveAudit : false, // 请假审核 //
@@ -161,6 +162,10 @@ const StringUtil = {
         // 用户分组管理
         userGroupManage: false,
 
+        // 人员管理
+        employeeManagement: false,
+        employeeGroupManage: false,
+
         // 设备管理
         equipmentInformationManagement: false,
         equipmentCostManagement: false,
@@ -306,6 +311,8 @@ const StringUtil = {
         arr[i] == '审核立项申请' ? obj.projectApprovalCheck = true : ''
 
         arr[i] == '用户分组管理' ? obj.userGroupManage = true : ''
+        arr[i] == '人员管理' ? obj.employeeManagement = true : ''
+        arr[i] == '员工分组管理' ? obj.employeeGroupManage = true : ''
         
         arr[i] == '设备信息管理' ? obj.equipmentInformationManagement = true : ''
         arr[i] == '设备成本管理' ? obj.equipmentCostManagement = true : ''
@@ -323,10 +330,11 @@ const StringUtil = {
         arr[i] == '异常工时表' ? obj.reportAbnormal = true : ''
         arr[i] == '餐补表' ? obj.allowance = true : ''
         arr[i] == '工时日报表' ? obj.reportDailyWorkHours = true : ''
+        arr[i] == '工时分析报告' ? obj.reportDashboard = true : ''
         arr[i] == '日报明细表' ? obj.reportDailyDetail = true : ''
         arr[i] == '项目薪资成本表' ? obj.reportProjectSalaryCost = true : ''
         arr[i] == '部门成本表' ? obj.reportDepartmentCost = true : ''
-        
+        arr[i] == '员工出勤表' ? obj.employeeAttendance = true : ''
         
     }
     return obj

+ 16 - 23
fhKeeper/formulahousekeeper/timesheet/src/routes.js

@@ -104,6 +104,9 @@ import userGrouping from './views/userGrouping/userGrouping.vue'
 // 设备管理
 import deviceManagement from './views/deviceManagement/deviceManagement'
 
+// 人员管理
+import employeeManagement from './views/employeeManagement.vue'
+
 Vue.use(Router)
 
 export const fixedRouter = [
@@ -492,29 +495,6 @@ export const allRouters = [//组织架构
         // 其他信息
         meta: { text: 'navigation.caiwushenhe' } 
     },
-    //设置时间类型
-    // {
-        
-    //     path: '/',
-    //     component: Home,
-    //     name: '',
-    //     iconCls: 'iconfont firerock-iconsetting',
-    //     leaf: true,//只有一个节点
-    //     children: [
-    //         { path: '/timetype', component: timetype, name: '系统基础设置' },
-    //     ]
-    // },
-    // {
-    //     path: '/',
-    //     component: Home,
-    //     name: '基础数据管理',
-    //     iconCls: 'iconfont firerock-iconsetting',
-    //     leaf: false,//只有一个节点
-    //     children: [
-    //         { path: '/timetype', component: infrastructure, name: '系统基础设置', iconCls: 'iconfont firerock-iconsetting' },
-    //         { path: '/quanx', component: quanx, name: '角色权限管理', iconCls: 'iconfont firerock-iconsetting' }
-    //     ]
-    // },
     {
         path: '/',
         component: Home,
@@ -543,6 +523,19 @@ export const allRouters = [//组织架构
         // 其他信息
         meta: { text: 'navigation.deviceManagement' } 
     },
+    //人员管理
+    {
+        path: '/',
+        component: Home,
+        name: '人员管理',
+        iconCls: 'iconfont firerock-iconquanxian1',
+        leaf: true,
+        children: [
+            { path: '/employeeManagement', component: employeeManagement, name: '人员管理' },
+        ],
+        // 其他信息
+        meta: { text: '人员管理' } 
+    },
     {
         path: '/404',
         component: NotFound,

File diff suppressed because it is too large
+ 65 - 9
fhKeeper/formulahousekeeper/timesheet/src/views/corpreport/list.vue


+ 298 - 26
fhKeeper/formulahousekeeper/timesheet/src/views/workReport/daily.vue

@@ -388,6 +388,14 @@
         <!-- 填写日报的dialog -->
         <el-dialog :title="isSubstitude?$t('textLink.helpToFillIn'):editTitle[isBatch]" :visible.sync="dialogVisible" width="60%" :close-on-click-modal="false" @closed="guanbi()" :top="'5.5vh'" custom-class="editReportDialog" ref="editReportDialog">
             <div style="height: 65vh;overflow: auto;">
+                <div style="margin:0 120px 10px 120px;position:relative;" v-if="user.company.enableAi && this.workForm.domains.filter(item=>!item.canEdit).length == 0 && !isSubstitude && !isBatch">
+                    <div style="display:flex;align-items:flex-start;gap:10px;">
+                        <el-input v-model="userInputMsg" type="textarea" :rows="2" placeholder="样例:今天/昨天/本周三... 我在[A项目]上投入3小时,做了产品的需求分析;在[B项目]上投入5小时,进行技术文档编写。(按Ctrl+Enter快速提交) 当前免费体验" clearable 
+                                style="flex:1;" @keydown.native="handleAIInputKeydown"></el-input>
+                        <el-button size="mini" type="primary" icon="el-icon-magic-stick" class="ai-parse-btn" @click="handleAIParse" :loading="aiParsing">AI解析</el-button>
+                    </div>
+                    <div style="margin-top:5px;"><el-tag size="mini" effect="plain" v-for="word in quickWords" @click="fillWord(word)">{{ word }}</el-tag></div>
+                </div>
                 <el-form ref="workForm" :model="workForm" :rules="workRules" label-width="120px">
                     <el-form-item :label="$t('screening.selectPeople')" v-if="isSubstitude">
                         <el-input v-if="user.userNameNeedTranslate != 1" @focus="showChooseMembTree" v-model="workForm.userNames"
@@ -755,13 +763,21 @@
                         </el-form-item>
 
                         <!-- 相关数值 -->
-                        <el-form-item :label="yonghuUser.customDataName" v-if="yonghuUser.customDataActive == 1" :prop="'domains.' + index + '.customData'" :rules="user.timeType.customDataStatus == 1 ? { type: 'number', required: true, message: $t('defaultText.pleaseFillOut') + yonghuUser.customDataName, trigger: ['change','blur'] } : null">
+                        <el-form-item :label="yonghuUser.customDataName" v-if="yonghuUser.customDataActive == 1 && !(user.companyId == 4811)" :prop="'domains.' + index + '.customData'" :rules="user.timeType.customDataStatus == 1 ? { type: 'number', required: true, message: $t('defaultText.pleaseFillOut') + yonghuUser.customDataName, trigger: ['change','blur'] } : null">
                             <el-input-number :id="'numberData_'+index" :disabled="!domain.canEdit" v-model="domain.customData" style="width:200px;" @keyup.native="restrictNumber('numberData_'+index)" :max="user.timeType.customDataMaxStatus == 1 ? user.timeType.customDataMaxValue : 'infinity'"></el-input-number>
                         </el-form-item>
                         <!-- 自定义文本 -->
-                        <el-form-item :label="yonghuUser.customTextName" v-if="yonghuUser.customTextActive == 1" :prop="'domains.' + index + '.customText'" :rules="user.timeType.customTextStatus == 1 ? { required: true, message: $t('defaultText.pleaseFillOut') + yonghuUser.customTextName, trigger: ['change','blur'] } : null">
+                        <el-form-item  :label="yonghuUser.customTextName" v-if="yonghuUser.customTextActive == 1 && user.companyId != 4811" :prop="'domains.' + index + '.customText'" :rules="user.timeType.customTextStatus == 1 ? { required: true, message: $t('defaultText.pleaseFillOut') + yonghuUser.customTextName, trigger: ['change','blur'] } : null">
                             <el-input :disabled="!domain.canEdit" v-model="domain.customText" type="textarea" :rows="1" style="width:75%;margin-right:7%" maxlength="1000" show-word-limit></el-input>
                         </el-form-item>
+                        <!-- 额外人员的选择, 目前针对柘中开放-->
+                        <el-form-item label="人员" v-if="user.companyId == 4811" :prop="'domains.' + index + '.customText'" rules="{ required: true, message: '请选择人员', trigger: ['change','blur'] }">
+                            <el-input :disabled="!domain.canEdit" v-model="domain.customTextShow" style="width:75%;margin-right:7%" @click.native="showEmployeeSelector(index)" readonly placeholder="请选择人员"></el-input>
+                        </el-form-item>
+                        <!-- 针对柘中,相关数值放到额外人员的下面-->
+                         <el-form-item :label="yonghuUser.customDataName" v-if="yonghuUser.customDataActive == 1 && (user.companyId == 4811)" :prop="'domains.' + index + '.customData'" :rules="user.timeType.customDataStatus == 1 ? { type: 'number', required: true, message: $t('defaultText.pleaseFillOut') + yonghuUser.customDataName, trigger: ['change','blur'] } : null">
+                            {{domain.customData}}
+                        </el-form-item>
                         <!--按比例填报-->
                         <el-form-item v-if="reportTimeType.type == 3" :label="$t('lable.percentageOfTime')" :prop="'domains.' + index + '.'+timeFields[reportTimeType.type]"
                             :rules="{ required: true, message: $t('defaultText.pleaseSetTheTimeRatio'), trigger: 'blur' }">
@@ -1311,15 +1327,24 @@
                     </el-select>
                 </div>
                 <!-- 自定义数值 -->
-                <div class="zhoFel" v-if="user.timeType.customDataActive">
+                <div class="zhoFel" v-if="user.timeType.customDataActive && !(user.companyId == 4811)">
                     <p>{{user.timeType.customDataName}}</p>
                     <el-input-number :id="'weekData_num'" v-model="zhoBao.customData" style="width:200px;margin-right:155px;" @keyup.native="restrictNumber('weekData_num')" :max="user.timeType.customDataMaxStatus == 1 ? user.timeType.customDataMaxValue : 'infinity'"></el-input-number>
                 </div>
                 <!-- 自定义文本 -->
-                <div class="zhoFel" v-if="user.timeType.customTextActive == 1">
+                <div class="zhoFel" v-if="user.timeType.customTextActive == 1 && user.companyId != 4811">
                     <p>{{user.timeType.customTextName}}</p>
                     <el-input v-model="zhoBao.customText" type="textarea" :rows="1" style="width:355px;" maxlength="1000" show-word-limit></el-input>
                 </div>
+                <!-- 额外人员的选择, 目前针对柘中开放-->
+                <div class="zhoFel" v-if="user.companyId == 4811">
+                    <p>人员</p>
+                    <el-input v-model="zhoBao.customTextShow" style="width:355px;" @click.native="showEmployeeSelector('zhoBao')" readonly placeholder="请选择人员"></el-input>
+                </div>
+                <!-- 针对柘中,相关数值放到额外人员的下面-->
+                 <div class="zhoFel" v-if="user.timeType.customDataActive && (user.companyId == 4811)">
+                    <p>{{user.timeType.customDataName}}</p>{{zhoBao.customData}}
+                </div>
                 <!-- 选择任务 -->
                 <div class="zhoFel" v-if="user.company.packageProject==1 && !user.timeType.hideTask && user.company.nonProjectSimple==0">
                     <p>{{ $t('renWuLiChengBei') }}</p>
@@ -2146,7 +2171,7 @@
           <el-alert style="position:absolute;bottom:0;z-index:10;" v-if="isMore" :title="$t('message.noMoreData')" type="warning" center show-icon></el-alert>
 
           <!-- <div slot="title" class="dialog-title selectworktime_title">
-            <label style="font-size: 16px">员每日已填报工时数</label>
+            <label style="font-size: 16px">员每日已填报工时数</label>
             <el-link
               type="primary"
               style="float: right; margin-right: 60px"
@@ -2561,6 +2586,30 @@
       </el-dialog>
         <!-- 威派格定制 -->
         <TransferWorkingHours v-model="transferWorkingHoursVisable" :projectList="projectList" :userList="usersList"></TransferWorkingHours>
+        
+        <!-- 人员选择弹窗 -->
+        <el-dialog title="选择人员" :visible.sync="employeeSelectDialog" width="600px" :close-on-click-modal="false">
+            <div style="height:500px;overflow:auto;">
+                <el-tree
+                    ref="employeeTree"
+                    :data="employeeGroupList"
+                    show-checkbox
+                    node-key="treeNodeKey" 
+                    :props="{children: 'employeeList', label: 'groupName'}"
+                    @check-change="handleEmployeeCheckChange"
+                    :default-checked-keys="getDefaultCheckedKeys()"
+                    v-loading="employeeGroupLoading">
+                    <span class="custom-tree-node" slot-scope="{ node, data }">
+                        <span>{{ data.groupName || data.name }}</span>
+                        <span v-if="data.employeeList" style="color:#999;font-size:12px;margin-left:5px;">({{ data.employeeList.length }}人)</span>
+                    </span>
+                </el-tree>
+            </div>
+            <span slot="footer" class="dialog-footer">
+                <el-button @click="employeeSelectDialog = false">取消</el-button>
+                <el-button type="primary" @click="confirmEmployeeSelection">确定</el-button>
+            </span>
+        </el-dialog>
     </section>
 </template>
 
@@ -2599,6 +2648,7 @@
         },
         data() {
             return {
+                quickWords: [],//用于AI解析快速输入的历史日报填报关键字
                 remindering: false,
                 onlyHaveAttendance: false,
                 notFullData:[],
@@ -2852,7 +2902,8 @@
                 isPass: true,
                 monthWorkData: [],
                 monthNotWorkDate: [],
-
+                userInputMsg:null,
+                aiParsing: false,
                 monthWorkDataS1: [],
                 monthnotTotal: 0,
                 monthnotTotalPage: 0,
@@ -2947,7 +2998,14 @@
                 customConfigurationRow: {
                     subUserCustomList: []
                 },
-                missingCardTimeUserListData: []
+                missingCardTimeUserListData: [],
+                
+                // 人员选择弹窗相关数据
+                employeeSelectDialog: false,
+                employeeGroupList: [],
+                selectedEmployees: [],
+                employeeGroupLoading: false,
+                currentDomainIndex: 0,
             };
         },
         watch: {
@@ -6954,6 +7012,73 @@
                     });
                 
             },
+            handleAIInputKeydown(event) {
+                // 监听Ctrl+Enter组合键
+                if (event.ctrlKey && event.keyCode === 13) {
+                    event.preventDefault();
+                    this.handleAIParse();
+                }
+            },
+            handleAIParse() {
+                if (!this.userInputMsg || this.userInputMsg.trim() === '') {
+                    this.$message({
+                        message: '请输入工作描述',
+                        type: 'warning'
+                    });
+                    return;
+                }
+                
+                this.aiParsing = true;
+                this.http.post('/ai/parseWorkTime', {
+                    content: this.userInputMsg,
+                    timeType: this.user.timeType.type
+                }, res => {
+                    this.aiParsing = false;
+                    if (res.code == "ok") {
+                        var aiResultMap = res.data;
+                        this.workForm.createDate = aiResultMap.createDate;
+                        this.workForm.totalDuration = aiResultMap.totalDuration;
+                        this.workForm.domains = [];//先清空
+                        var reports = aiResultMap.reports;
+                        for (var i=0;i<reports.length; i++) {
+                            this.workForm.domains.push(
+                                {id: null,
+                                    projectId: reports[i].projectId,
+                                    workingTime: reports[i].workingTime,
+                                    overtimeHours: reports[i].overtimeHours,
+                                    content: reports[i].content,
+                                    progress:100,
+                                    state: 2,
+                                    timeType:0,
+                                    multiWorktime: this.reportTimeType.multiWorktime,
+                                    worktimeList:[{}],
+                                    canEdit: true,
+                                    auditorFirst: '',
+                                    auditorSec: '',
+                                    auditorThird: '',
+                                    startTime:  reports[i].startTime,
+                                    endTime:  reports[i].endTime,
+                                    ccUserid: ''}
+                            );
+                            if (reports[i].overtimeHours > 0) {
+                                this.$set(this.workForm.domains[i],'isOvertime', true);
+                            }
+                            this.selectProject(this.workForm.domains[i],i);
+                        }
+                    } else {
+                        this.$message({
+                            message: res.msg || 'AI解析失败',
+                            type: 'error'
+                        });
+                    }
+                }, err => {
+                    this.aiParsing = false;
+                    this.$message({
+                        message: err || 'AI解析失败',
+                        type: 'error'
+                    });
+                });
+            },
             getAIReport(createDate) {
                 this.http.post('/report/getAIReport', {},
                 res => {
@@ -7007,6 +7132,8 @@
                                     projectAuditorName: aiReportData[i].projectAuditorName,
                                     overtimeHours: aiReportData[i].overtimeHours,
                                     customText: aiReportData[i].customText,
+                                    customTextShow: (this.user.companyId == 4811) && aiReportData[i].customText && aiReportData[i].customText !== '-' ? aiReportData[i].customText.split(/[,,]/).map(item => { let parts = item.split('-'); return parts.length > 1 ? parts[1] : item; }).join(',') : (aiReportData[i].customText === '-' ? '' : aiReportData[i].customText),
+                                    selectedEmployees: (this.user.companyId == 4811) && aiReportData[i].customText && aiReportData[i].customText !== '-' ? aiReportData[i].customText.split(/[,,]/).map(item => { let parts = item.split('-'); return parts.length > 1 ? { id: parseInt(parts[0]), name: parts[1] } : { id: null, name: item }; }).filter(emp => emp.id !== null && !isNaN(emp.id)) : [],
                                     basecostId: aiReportData[i].basecostId,
                                     auditorFirst: aiReportData[i].auditorSetting && aiReportData[i].auditorSetting.auditorFirst ? aiReportData[i].auditorSetting.auditorFirst : '',
                                     auditorSec: aiReportData[i].auditorSetting && aiReportData[i].auditorSetting.auditorSec ? aiReportData[i].auditorSetting.auditorSec : '',
@@ -7038,6 +7165,24 @@
                     });
                 });
             },
+            //获取最近填写的日报的关键字,用于AI解析的快速输入
+            getUserRecentReportWords() {
+                this.http.post('/user-recent-report/getRecentReportWords', {},
+                res => {
+                    if (res.code == "ok") {
+                        this.quickWords = res.data;
+                    }
+                },
+                 error => {
+                    this.$message({
+                        message: error,
+                        type: "error"
+                    });
+                });
+            },
+            fillWord(word) {
+                this.userInputMsg = this.userInputMsg?this.userInputMsg + word + ',':word + ',';
+            },
             // 获取个人某天的日报 111111
             getReport() {
                 this.http.post( this.port.report.getPort, {
@@ -7115,6 +7260,8 @@
                                     auditUserList: list.report[i].auditUserList,
                                     overtimeHours: list.report[i].overtimeHours,
                                     customText: list.report[i].customText,
+                                    customTextShow: (this.user.companyId == 4811) && list.report[i].customText && list.report[i].customText !== '-' ? list.report[i].customText.split(/[,,]/).map(item => { let parts = item.split('-'); return parts.length > 1 ? parts[1] : item; }).join(',') : (list.report[i].customText === '-' ? '' : list.report[i].customText),
+                                    selectedEmployees: (this.user.companyId == 4811) && list.report[i].customText && list.report[i].customText !== '-' ? list.report[i].customText.split(/[,,]/).map(item => { let parts = item.split('-'); return parts.length > 1 ? { id: parseInt(parts[0]), name: parts[1] } : { id: null, name: item }; }).filter(emp => emp.id !== null && !isNaN(emp.id)) : [],
                                     basecostId: list.report[i].basecostId,
                                     auditorFirst: list.report[i].auditorSetting && list.report[i].auditorSetting.auditorFirst ? list.report[i].auditorSetting.auditorFirst : '',
                                     auditorSec: list.report[i].auditorSetting && list.report[i].auditorSetting.auditorSec ? list.report[i].auditorSetting.auditorSec : '',
@@ -7247,10 +7394,10 @@
                                 }
                             }
                             if(this.user.timeType.type == 1) {
-                                    var shuzhi = this.user.timeType.allday + ''
-                                    // console.log('执行一次', shuzhi.indexOf('.'))
-                                    this.workForm.domains[0].workingTime = shuzhi.indexOf('.') == '-1' ? shuzhi + '.0' : shuzhi
-                                }
+                                var shuzhi = this.user.timeType.allday + ''
+                                // console.log('执行一次', shuzhi.indexOf('.'))
+                                this.workForm.domains[0].workingTime = shuzhi.indexOf('.') == '-1' ? shuzhi + '.0' : shuzhi
+                            }
                             if (this.timeBasecostList && this.timeBasecostList.length > 0) {
                                 //默认给第一个,必填字段
                                 this.workForm.domains[0].basecostId = this.timeBasecostList[0].id;
@@ -7354,6 +7501,9 @@
                     this.seleChn();
                 }
                 this.dialogVisible = true;
+                if (this.user.company.enableAi && i == -1 && !isBatch) {
+                    this.getUserRecentReportWords();
+                }
             },
 
             //按周填报的弹窗口中编辑单项目相关数据
@@ -7657,7 +7807,7 @@
                 if(this.user.timeType.customDataStatus == 1 && !this.zhoBao.customData){
                     errtips += this.user.timeType.customDataName + '、'
                 }
-                if(this.user.timeType.customTextStatus == 1 && !this.zhoBao.customText){
+                if(this.user.timeType.customTextStatus == 1 && (!this.zhoBao.customText || this.zhoBao.customText === '-')){
                     errtips += this.user.timeType.customTextName + '、'
                 }
                 if(!this.user.timeType.hideContent && this.user.timeType.workContentState == 1 && !this.zhoBao.content){
@@ -8403,11 +8553,11 @@
                                     }else if(this.user.timeType.customDataActive){
                                         formData.append('customData',0)
                                     }
-                                    if(this.user.timeType.customTextActive && zhoD[j].customText){
-                                        formData.append('customText',zhoD[j].customText)
-                                    }else if(this.user.timeType.customTextActive){
-                                        formData.append('customText','-')
-                                    }
+                                        if(this.user.timeType.customTextActive && zhoD[j].customText && zhoD[j].customText.trim() !== ''){
+                                            formData.append('customText',zhoD[j].customText)
+                                        }else if(this.user.timeType.customTextActive){
+                                            formData.append('customText','-')
+                                        }
 
                                     if(reportExtraField4Name || reportExtraField5Name) {
                                         formData.append('extraField4', zhoD[j].extraField4 || '')
@@ -8501,11 +8651,11 @@
                                         }else if(this.user.timeType.customDataActive){
                                             formData.append('customData',0)
                                         }
-                                        if(this.user.timeType.customTextActive && zhoD[j].customText){
-                                            formData.append('customText',zhoD[j].customText)
-                                        }else if(this.user.timeType.customTextActive){
-                                            formData.append('customText','-')
-                                        }
+                                    if(this.user.timeType.customTextActive && zhoD[j].customText && zhoD[j].customText.trim() !== ''){
+                                        formData.append('customText',zhoD[j].customText)
+                                    }else if(this.user.timeType.customTextActive){
+                                        formData.append('customText','-')
+                                    }
 
                                         if(reportExtraField4Name || reportExtraField5Name) {
                                             formData.append('extraField4', zhoD[j].extraField4 || '')
@@ -8601,7 +8751,7 @@
                                         }else if(this.user.timeType.customDataActive){
                                             formData.append('customData',0)
                                         }
-                                        if(this.user.timeType.customTextActive && zhoD[j].customText){
+                                        if(this.user.timeType.customTextActive && zhoD[j].customText && zhoD[j].customText.trim() !== ''){
                                             formData.append('customText',zhoD[j].customText)
                                         }else if(this.user.timeType.customTextActive){
                                             formData.append('customText','-')
@@ -9307,7 +9457,7 @@
                             } else {
                                 formData.append("customData", 0);
                             }
-                            if(this.workForm.domains[i].customText) {
+                            if(this.workForm.domains[i].customText && this.workForm.domains[i].customText.trim() !== '') {
                                 formData.append("customText", this.workForm.domains[i].customText);
                             } else {
                                 formData.append("customText", '-');
@@ -10099,6 +10249,113 @@
                     })
                 })
             },
+            // 员工选择相关方法
+            showEmployeeSelector(index) {
+                this.currentDomainIndex = index;
+                this.employeeSelectDialog = true;
+                if (this.employeeGroupList.length === 0) {
+                    this.getEmployeeGroupList();
+                } else {
+                    this.$nextTick(() => {
+                        var keys = this.getDefaultCheckedKeys();
+                        console.log(keys);
+                        this.$refs.employeeTree.setCheckedKeys(keys);
+                    });
+                }
+            },
+            getEmployeeGroupList() {
+                this.employeeGroupLoading = true;
+                this.http.post('/employee/listByGroup', {
+                    companyId: this.user.companyId
+                }, res => {
+                    this.employeeGroupLoading = false;
+                    if (res.code === "ok") {
+                        this.employeeGroupList = res.data || [];
+                        // 为每个节点添加唯一的treeNodeKey(核心逻辑)
+                        this.employeeGroupList.forEach(group => {
+                            // 分组的唯一key:前缀+原始ID
+                            group.treeNodeKey = `group_${group.id}`;
+                            // 人员的唯一key:前缀+原始ID(原始id保留不变)
+                            if (group.employeeList && group.employeeList.length) {
+                                group.employeeList.forEach(emp => {
+                                    emp.treeNodeKey = emp.id; // 仅新增字段,不修改emp.id
+                                });
+                            }
+                        });
+                        this.$nextTick(() => {
+                            this.$refs.employeeTree.setCheckedKeys(this.getDefaultCheckedKeys());
+                        });
+                    } else {
+                        this.$message({
+                            message: res.msg,
+                            type: "error"
+                        });
+                    }
+                }, error => {
+                    this.employeeGroupLoading = false;
+                    this.$message({
+                        message: error,
+                        type: "error"
+                    });
+                });
+            },
+            handleEmployeeCheckChange() {
+                const checkedNodes = this.$refs.employeeTree.getCheckedNodes(true);
+                // 树的第二层才是人员,employeeList 是第一层(分组)的属性
+                this.selectedEmployees = checkedNodes.filter(node => !node.employeeList); 
+            },
+            getDefaultCheckedKeys() {
+                if (this.currentDomainIndex == 'zhoBao') {
+                    if (this.zhoBao.selectedEmployees) {
+                        return this.zhoBao.selectedEmployees.map(emp => emp.id);
+                    }
+                } else {
+                    const domain = this.workForm.domains[this.currentDomainIndex];
+                    if (domain && domain.selectedEmployees) {
+                        return domain.selectedEmployees.map(emp => emp.id);
+                    }
+                }
+                return [];
+            },
+            confirmEmployeeSelection() {
+                // 将选中的人员名字拼接成字符串,回显到输入框
+                const employeeNames = this.selectedEmployees.map(emp => emp.name).join(',');
+                var str = '';
+                for (var i=0;i<this.selectedEmployees.length; i++) {
+                    var item = this.selectedEmployees[i];
+                    str += item.id + '-' + item.name;
+                    if (i < this.selectedEmployees.length-1) {
+                        str += ',';
+                    }
+                }
+                if (this.currentDomainIndex == 'zhoBao') {
+                    //按周填报的弹窗里面设置人员
+                    this.zhoBao.customTextShow = employeeNames;
+                    this.zhoBao.customText = str;
+                    this.zhoBao.selectedEmployees = this.selectedEmployees;
+                    this.zhoBao.customData = this.selectedEmployees.length;
+                } else {
+                    const domain = this.workForm.domains[this.currentDomainIndex];
+                    this.$set(domain, 'selectedEmployees', [...this.selectedEmployees]);
+                    this.$set(domain, 'customText', str);
+                    this.$set(domain, 'customTextShow', employeeNames);
+                    //设置相关人数
+                    this.$set(domain, 'customData', this.selectedEmployees.length);
+                }
+                this.employeeSelectDialog = false;
+            },
+            removeEmployee(index, employeeId) {
+                const domain = this.workForm.domains[index];
+                if (domain && domain.selectedEmployees) {
+                    const idx = domain.selectedEmployees.findIndex(emp => emp.id === employeeId);
+                    if (idx > -1) {
+                        domain.selectedEmployees.splice(idx, 1);
+                        // 更新输入框回显
+                        const employeeNames = domain.selectedEmployees.map(emp => emp.name).join(',');
+                        this.$set(domain, 'customText', employeeNames);
+                    }
+                }
+            },
             // 自定义事件
             selectCal(obj) {
                 if(obj.distinction == '1') {
@@ -10131,7 +10388,7 @@
                     this.usersListId = obj.id
                     this.showReportTimeLessThanCardTimeList();
                 } 
-            }
+            },
         },
         created() {
             this.getUsers()
@@ -10514,4 +10771,19 @@
         box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
         margin: 0 20px;
     }
-</style>
+    
+    /* AI解析按钮样式 - 蓝紫渐变科技感 */
+    .ai-parse-btn {
+        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
+        border: none !important;
+        opacity: 0.9;
+        transition: all 0.3s ease;
+        box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.4);
+        height:55px;
+    }
+
+    .ai-parse-btn:hover {
+        opacity: 1;
+        box-shadow: 0 6px 20px 0 rgba(102, 126, 234, 0.6);
+    }
+</style>